Skip to content

Commit 5814435

Browse files
committed
RF: Tag sniffs with file name
Currently doesn't occur, but possible that different images with a common extension could have different header extensions. This change checks that the header file name is the same before reusing a sniff.
1 parent c3518d1 commit 5814435

File tree

2 files changed

+40
-21
lines changed

2 files changed

+40
-21
lines changed

nibabel/spatialimages.py

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -874,7 +874,7 @@ def from_image(klass, img):
874874
extra=img.extra.copy())
875875

876876
@classmethod
877-
def _sniff_meta_for(klass, filename, sniff_nbytes):
877+
def _sniff_meta_for(klass, filename, sniff_nbytes, sniff=None):
878878
""" Sniff metadata for image represented by `filename`
879879
880880
Parameters
@@ -886,28 +886,39 @@ def _sniff_meta_for(klass, filename, sniff_nbytes):
886886
and read from that instead of `filename`.
887887
sniff_nbytes : int
888888
Number of bytes to read from the image or metadata file
889+
sniff : (bytes, fname), optional
890+
The result of a previous call to `_sniff_meta_for`. If fname
891+
matches the computed header file name, `sniff` is returned without
892+
rereading the file.
889893
890894
Returns
891895
-------
892-
meta_bytes : None or bytes
893-
None if we could not read the image or metadata file. `meta_bytes`
896+
sniff : None or (bytes, fname)
897+
None if we could not read the image or metadata file. `sniff[0]`
894898
is either length `sniff_nbytes` or the length of the image /
895-
metadata file, whichever is the shorter.
899+
metadata file, whichever is the shorter. `fname` is the name of
900+
the sniffed file.
896901
"""
897902
froot, ext, trailing = splitext_addext(filename,
898903
klass._compressed_suffixes)
899-
# Determine the metadata location, then sniff it
904+
# Determine the metadata location
900905
t_fnames = types_filenames(
901906
filename,
902907
klass.files_types,
903908
trailing_suffixes=klass._compressed_suffixes)
904909
meta_fname = t_fnames.get('header', filename)
910+
911+
# Do not re-sniff if it would be from the same file
912+
if sniff is not None and sniff[1] == meta_fname:
913+
return sniff
914+
915+
# Attempt to sniff from metadata location
905916
try:
906917
with ImageOpener(meta_fname, 'rb') as fobj:
907-
sniff = fobj.read(sniff_nbytes)
918+
binaryblock = fobj.read(sniff_nbytes)
908919
except IOError:
909920
return None
910-
return sniff
921+
return (binaryblock, meta_fname)
911922

912923
@classmethod
913924
def path_maybe_image(klass, filename, sniff=None, sniff_max=1024):
@@ -920,11 +931,13 @@ def path_maybe_image(klass, filename, sniff=None, sniff_max=1024):
920931
If `filename` points to an image data file, and the image type has
921932
a separate "header" file, we work out the name of the header file,
922933
and read from that instead of `filename`.
923-
sniff : None or bytes, optional
934+
sniff : None or (bytes, filename), optional
924935
Bytes content read from a previous call to this method, on another
925-
class. This allows us to read metadata bytes once from the image /
926-
or header, and pass this read set of bytes to other image classes,
927-
therefore saving a repeat read of the metadata. None forces this
936+
class, with metadata filename. This allows us to read metadata
937+
bytes once from the image or header, and pass this read set of
938+
bytes to other image classes, therefore saving a repeat read of the
939+
metadata. `filename` is used to validate that metadata would be
940+
read from the same file, re-reading if not. None forces this
928941
method to read the metadata.
929942
sniff_max : int, optional
930943
The maximum number of bytes to read from the metadata. If the
@@ -938,7 +951,7 @@ def path_maybe_image(klass, filename, sniff=None, sniff_max=1024):
938951
-------
939952
maybe_image : bool
940953
True if `filename` may be valid for an image of this class.
941-
sniff : None or bytes
954+
sniff : None or (bytes, filename)
942955
Read bytes content from found metadata. May be None if the file
943956
does not appear to have useful metadata.
944957
"""
@@ -948,14 +961,16 @@ def path_maybe_image(klass, filename, sniff=None, sniff_max=1024):
948961
return False, sniff
949962
if not hasattr(klass.header_class, 'may_contain_header'):
950963
return True, sniff
951-
if sniff is None or len(sniff) < klass._meta_sniff_len:
952-
sniff_nbytes = max(klass._meta_sniff_len, sniff_max)
953-
sniff = klass._sniff_meta_for(filename, sniff_nbytes)
954-
if sniff is None: # Can't sniff, won't sniff
955-
return False, None
956-
if len(sniff) < klass._meta_sniff_len:
964+
965+
# Force re-sniff on too-short sniff
966+
if sniff is not None and len(sniff[0]) < klass._meta_sniff_len:
967+
sniff = None
968+
sniff = klass._sniff_meta_for(filename,
969+
max(klass._meta_sniff_len, sniff_max),
970+
sniff)
971+
if sniff is None or len(sniff[0]) < klass._meta_sniff_len:
957972
return False, sniff
958-
return klass.header_class.may_contain_header(sniff), sniff
973+
return klass.header_class.may_contain_header(sniff[0]), sniff
959974

960975
def __getitem__(self):
961976
''' No slicing or dictionary interface for images

nibabel/tests/test_image_types.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ def check_img(img_path, img_klass, sniff_mode, sniff, expect_success,
5454
if sniff_mode == 'no_sniff':
5555
# Don't pass any sniff--not even "None"
5656
is_img, new_sniff = img_klass.path_maybe_image(img_path)
57+
elif sniff_mode in ('empty', 'irrelevant', 'bad_sniff'):
58+
# Add img_path to binaryblock sniff parameters
59+
is_img, new_sniff = img_klass.path_maybe_image(img_path, (sniff, img_path))
5760
else:
5861
# Pass a sniff, but don't reuse across images.
5962
is_img, new_sniff = img_klass.path_maybe_image(img_path, sniff)
@@ -64,7 +67,7 @@ def check_img(img_path, img_klass, sniff_mode, sniff, expect_success,
6467
msg)
6568
expected_sizeof_hdr = getattr(img_klass.header_class,
6669
'sizeof_hdr', 0)
67-
current_sizeof_hdr = 0 if new_sniff is None else len(new_sniff)
70+
current_sizeof_hdr = 0 if new_sniff is None else len(new_sniff[0])
6871
assert_true(current_sizeof_hdr >= expected_sizeof_hdr, new_msg)
6972

7073
# Check that the image type was recognized.
@@ -88,7 +91,8 @@ def check_img(img_path, img_klass, sniff_mode, sniff, expect_success,
8891
none=None, # pass None as the sniff, should query in fn
8992
empty=b'', # pass an empty sniff, should query in fn
9093
irrelevant=b'a' * (sizeof_hdr - 1), # A too-small sniff, query
91-
bad_sniff=b'a' * sizeof_hdr).items(): # Bad sniff, should fail
94+
bad_sniff=b'a' * sizeof_hdr, # Bad sniff, should fail
95+
).items():
9296

9397
for klass in img_klasses:
9498
if klass == expected_img_klass:

0 commit comments

Comments
 (0)