Skip to content

Commit 258d0cd

Browse files
authored
Merge pull request #1 from effigies/zstd_support
RF: Use optional_package to allow code to assume pyzstd is present
2 parents 823b97f + 6ba7dd8 commit 258d0cd

12 files changed

+46
-55
lines changed

nibabel/analyze.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,6 @@
9595
from .fileholders import copy_file_map
9696
from .batteryrunners import Report
9797
from .arrayproxy import ArrayProxy
98-
from .openers import HAVE_ZSTD
9998

10099
# Sub-parts of standard analyze header from
101100
# Mayo dbh.h file
@@ -907,9 +906,7 @@ class AnalyzeImage(SpatialImage):
907906
_meta_sniff_len = header_class.sizeof_hdr
908907
files_types = (('image', '.img'), ('header', '.hdr'))
909908
valid_exts = ('.img', '.hdr')
910-
_compressed_suffixes = ('.gz', '.bz2')
911-
if HAVE_ZSTD: # If pyzstd installed., add .zst suffix
912-
_compressed_suffixes = (*_compressed_suffixes, '.zst')
909+
_compressed_suffixes = ('.gz', '.bz2', '.zst')
913910

914911
makeable = True
915912
rw = True

nibabel/benchmarks/bench_fileslice.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,19 @@
1414
import numpy as np
1515

1616
from io import BytesIO
17-
from ..openers import ImageOpener, HAVE_ZSTD
17+
from ..openers import ImageOpener
1818
from ..fileslice import fileslice
1919
from ..rstutils import rst_table
2020
from ..tmpdirs import InTemporaryDirectory
21+
from ..optpkg import optional_package
2122

2223
SHAPE = (64, 64, 32, 100)
2324
ROW_NAMES = [f'axis {i}, len {dim}' for i, dim in enumerate(SHAPE)]
2425
COL_NAMES = ['mid int',
2526
'step 1',
2627
'half step 1',
2728
'step mid int']
29+
HAVE_ZSTD = optional_package("pyzstd")[1]
2830

2931

3032
def _slices_for_len(L):
@@ -104,11 +106,10 @@ def my_table(title, times, base):
104106
my_table('bz2 slice - raw (ratio)',
105107
np.dstack((bz2_times, bz2_times / bz2_base)),
106108
bz2_base)
107-
if HAVE_ZSTD:
108-
if zst:
109-
with InTemporaryDirectory():
110-
zst_times, zst_base = run_slices('data.zst', repeat)
111-
my_table('zst slice - raw (ratio)',
112-
np.dstack((zst_times, zst_times / zst_base)),
113-
zst_base)
109+
if zst and HAVE_ZSTD:
110+
with InTemporaryDirectory():
111+
zst_times, zst_base = run_slices('data.zst', repeat)
112+
my_table('zst slice - raw (ratio)',
113+
np.dstack((zst_times, zst_times / zst_base)),
114+
zst_base)
114115
sys.stdout.flush()

nibabel/brikhead.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@
4343
ImageDataError
4444
)
4545
from .volumeutils import Recoder
46-
from .openers import HAVE_ZSTD
4746

4847
# used for doc-tests
4948
filepath = os.path.dirname(os.path.realpath(__file__))
@@ -491,9 +490,7 @@ class AFNIImage(SpatialImage):
491490
header_class = AFNIHeader
492491
valid_exts = ('.brik', '.head')
493492
files_types = (('image', '.brik'), ('header', '.head'))
494-
_compressed_suffixes = ('.gz', '.bz2', '.Z')
495-
if HAVE_ZSTD: # If pyzstd installed., add .zst suffix
496-
_compressed_suffixes = (*_compressed_suffixes, '.zst')
493+
_compressed_suffixes = ('.gz', '.bz2', '.Z', '.zst')
497494
makeable = False
498495
rw = False
499496
ImageArrayProxy = AFNIArrayProxy

nibabel/loadsave.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,13 @@
1313
import numpy as np
1414

1515
from .filename_parser import splitext_addext, _stringify_path
16-
from .openers import ImageOpener, HAVE_ZSTD
16+
from .openers import ImageOpener
1717
from .filebasedimages import ImageFileError
1818
from .imageclasses import all_image_classes
1919
from .arrayproxy import is_proxy
2020
from .deprecated import deprecate_with_version
2121

22-
_compressed_suffixes = ('.gz', '.bz2')
23-
if HAVE_ZSTD: # If pyzstd installed., add .zst suffix
24-
_compressed_suffixes = (*_compressed_suffixes, '.zst')
22+
_compressed_suffixes = ('.gz', '.bz2', '.zst')
2523

2624

2725
def load(filename, **kwargs):

nibabel/minc1.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
from .spatialimages import SpatialHeader, SpatialImage
1818
from .fileslice import canonical_slicers
19-
from .openers import HAVE_ZSTD
2019

2120
_dt_dict = {
2221
('b', 'unsigned'): np.uint8,
@@ -317,9 +316,7 @@ class Minc1Image(SpatialImage):
317316
_meta_sniff_len = 4
318317
valid_exts = ('.mnc',)
319318
files_types = (('image', '.mnc'),)
320-
_compressed_suffixes = ('.gz', '.bz2')
321-
if HAVE_ZSTD: # If pyzstd installed., add .zst suffix
322-
_compressed_suffixes = (*_compressed_suffixes, '.zst')
319+
_compressed_suffixes = ('.gz', '.bz2', '.zst')
323320

324321
makeable = True
325322
rw = False

nibabel/openers.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
from os.path import splitext
1616
from distutils.version import StrictVersion
1717

18+
from nibabel.optpkg import optional_package
19+
1820
# is indexed_gzip present and modern?
1921
try:
2022
import indexed_gzip as igzip
@@ -39,13 +41,6 @@
3941
IndexedGzipFile = gzip.GzipFile
4042
HAVE_INDEXED_GZIP = False
4143

42-
# Enable .zst support if pyzstd installed.
43-
try:
44-
from pyzstd import ZstdFile
45-
HAVE_ZSTD = True
46-
except ImportError:
47-
HAVE_ZSTD = False
48-
4944

5045
def _gzip_open(filename, mode='rb', compresslevel=9, keep_open=False):
5146

@@ -62,6 +57,12 @@ def _gzip_open(filename, mode='rb', compresslevel=9, keep_open=False):
6257
return gzip_file
6358

6459

60+
def _zstd_open(filename, mode="r", *, level_or_option=None, zstd_dict=None):
61+
pyzstd = optional_package("pyzstd")[0]
62+
return pyzstd.ZstdFile(filename, mode,
63+
level_or_option=level_or_option, zstd_dict=zstd_dict)
64+
65+
6566
class Opener(object):
6667
r""" Class to accept, maybe open, and context-manage file-likes / filenames
6768
@@ -84,14 +85,13 @@ class Opener(object):
8485
"""
8586
gz_def = (_gzip_open, ('mode', 'compresslevel', 'keep_open'))
8687
bz2_def = (BZ2File, ('mode', 'buffering', 'compresslevel'))
88+
zstd_def = (_zstd_open, ('mode', 'level_or_option', 'zstd_dict'))
8789
compress_ext_map = {
8890
'.gz': gz_def,
8991
'.bz2': bz2_def,
92+
'.zst': zstd_def,
9093
None: (open, ('mode', 'buffering')) # default
9194
}
92-
if HAVE_ZSTD: # add zst to ext map, if library exists
93-
zstd_def = (ZstdFile, ('mode', 'level_or_option'))
94-
compress_ext_map['.zst'] = zstd_def
9595
#: default compression level when writing gz and bz2 files
9696
default_compresslevel = 1
9797
#: default option for zst files

nibabel/tests/test_analyze.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
from ..casting import as_int
3131
from ..tmpdirs import InTemporaryDirectory
3232
from ..arraywriters import WriterError
33-
from ..openers import HAVE_ZSTD
33+
from ..optpkg import optional_package
3434

3535
import pytest
3636
from numpy.testing import (assert_array_equal, assert_array_almost_equal)
@@ -41,6 +41,8 @@
4141
from .test_wrapstruct import _TestLabeledWrapStruct
4242
from . import test_spatialimages as tsi
4343

44+
HAVE_ZSTD = optional_package("pyzstd")[1]
45+
4446
header_file = os.path.join(data_path, 'analyze.hdr')
4547

4648
PIXDIM0_MSG = 'pixdim[1,2,3] should be non-zero; setting 0 dims to 1'

nibabel/tests/test_minc1.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from ..deprecated import ModuleProxy
2323
from .. import minc1
2424
from ..minc1 import Minc1File, Minc1Image, MincHeader
25-
from ..openers import HAVE_ZSTD
25+
from ..optpkg import optional_package
2626

2727
from ..tmpdirs import InTemporaryDirectory
2828
from ..deprecator import ExpiredDeprecationError
@@ -33,9 +33,7 @@
3333
from . import test_spatialimages as tsi
3434
from .test_fileslice import slicer_samples
3535

36-
# only import ZstdFile, if installed
37-
if HAVE_ZSTD:
38-
from ..openers import ZstdFile
36+
pyzstd, HAVE_ZSTD, _ = optional_package("pyzstd")
3937

4038
EG_FNAME = pjoin(data_path, 'tiny.mnc')
4139

@@ -178,7 +176,7 @@ def test_compressed(self):
178176
openers_exts = [(gzip.open, '.gz'),
179177
(bz2.BZ2File, '.bz2')]
180178
if HAVE_ZSTD: # add .zst to test if installed
181-
openers_exts += [(ZstdFile, '.zst')]
179+
openers_exts += [(pyzstd.ZstdFile, '.zst')]
182180
with InTemporaryDirectory():
183181
for opener, ext in openers_exts:
184182
fname = 'test.mnc' + ext

nibabel/tests/test_openers.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,17 @@
1818
ImageOpener,
1919
HAVE_INDEXED_GZIP,
2020
BZ2File,
21-
HAVE_ZSTD)
21+
)
2222
from ..tmpdirs import InTemporaryDirectory
2323
from ..volumeutils import BinOpener
24+
from ..optpkg import optional_package
2425

2526
import unittest
2627
from unittest import mock
2728
import pytest
2829
from ..testing import error_warnings
2930

30-
if HAVE_ZSTD:
31-
from ..openers import ZstdFile
31+
pyzstd, HAVE_ZSTD, _ = optional_package("pyzstd")
3232

3333

3434
class Lunk(object):
@@ -277,7 +277,7 @@ class StrictOpener(Opener):
277277
IndexedGzipFile = GzipFile
278278
assert isinstance(fobj.fobj, (GzipFile, IndexedGzipFile))
279279
elif lext == 'zst':
280-
assert isinstance(fobj.fobj, ZstdFile)
280+
assert isinstance(fobj.fobj, pyzstd.ZstdFile)
281281
else:
282282
assert isinstance(fobj.fobj, BZ2File)
283283

nibabel/tests/test_volumeutils.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,20 +45,19 @@
4545
_write_data,
4646
_ftype4scaled_finite,
4747
)
48-
from ..openers import Opener, BZ2File, HAVE_ZSTD
48+
from ..openers import Opener, BZ2File
4949
from ..casting import (floor_log2, type_info, OK_FLOATS, shared_range)
5050

5151
from ..deprecator import ExpiredDeprecationError
52+
from ..optpkg import optional_package
5253

5354
from numpy.testing import (assert_array_almost_equal,
5455
assert_array_equal)
5556
import pytest
5657

5758
from nibabel.testing import nullcontext, assert_dt_equal, assert_allclose_safely, suppress_warnings
5859

59-
# only import ZstdFile, if installed
60-
if HAVE_ZSTD:
61-
from ..openers import ZstdFile
60+
pyzstd, HAVE_ZSTD, _ = optional_package("pyzstd")
6261

6362
#: convenience variables for numpy types
6463
FLOAT_TYPES = np.sctypes['float']
@@ -76,7 +75,7 @@ def test__is_compressed_fobj():
7675
('.gz', gzip.open, True),
7776
('.bz2', BZ2File, True)]
7877
if HAVE_ZSTD:
79-
file_openers += [('.zst', ZstdFile, True)]
78+
file_openers += [('.zst', pyzstd.ZstdFile, True)]
8079
for ext, opener, compressed in file_openers:
8180
fname = 'test.bin' + ext
8281
for mode in ('wb', 'rb'):
@@ -100,7 +99,7 @@ def make_array(n, bytes):
10099
with InTemporaryDirectory():
101100
openers = [open, gzip.open, BZ2File]
102101
if HAVE_ZSTD:
103-
openers += [ZstdFile]
102+
openers += [pyzstd.ZstdFile]
104103
for n, opener in itertools.product(
105104
(256, 1024, 2560, 25600),
106105
openers):
@@ -266,7 +265,7 @@ def test_array_from_file_reread():
266265
with InTemporaryDirectory():
267266
openers = [open, gzip.open, bz2.BZ2File, BytesIO]
268267
if HAVE_ZSTD:
269-
openers += [ZstdFile]
268+
openers += [pyzstd.ZstdFile]
270269
for shape, opener, dtt, order in itertools.product(
271270
((64,), (64, 65), (64, 65, 66)),
272271
openers,

nibabel/volumeutils.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@
1919
import numpy as np
2020

2121
from .casting import shared_range, OK_FLOATS
22-
from .openers import Opener, BZ2File, IndexedGzipFile, HAVE_ZSTD
22+
from .openers import Opener, BZ2File, IndexedGzipFile
2323
from .deprecated import deprecate_with_version
2424
from .externals.oset import OrderedSet
25+
from .optpkg import optional_package
26+
27+
pyzstd, HAVE_ZSTD, _ = optional_package("pyzstd")
2528

2629
sys_is_le = sys.byteorder == 'little'
2730
native_code = sys_is_le and '<' or '>'
@@ -42,8 +45,7 @@
4245

4346
# Enable .zst support if pyzstd installed.
4447
if HAVE_ZSTD:
45-
from .openers import ZstdFile
46-
COMPRESSED_FILE_LIKES = (*COMPRESSED_FILE_LIKES, ZstdFile)
48+
COMPRESSED_FILE_LIKES = (*COMPRESSED_FILE_LIKES, pyzstd.ZstdFile)
4749

4850

4951
class Recoder(object):

tools/ci/env.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ REQUIREMENTS="-r requirements.txt"
55
# Minimum versions of minimum requirements
66
MIN_REQUIREMENTS="-r min-requirements.txt"
77

8-
DEFAULT_OPT_DEPENDS="scipy matplotlib pillow pydicom h5py indexed_gzip"
8+
DEFAULT_OPT_DEPENDS="scipy matplotlib pillow pydicom h5py indexed_gzip pyzstd"
99
# pydicom has skipped some important pre-releases, so enable a check against master
1010
PYDICOM_MASTER="git+https://github.com/pydicom/pydicom.git@master"
1111
# Minimum versions of optional requirements

0 commit comments

Comments
 (0)