Skip to content

Commit ed95d8d

Browse files
authored
Merge pull request #1213 from effigies/typ/loadsave
TYP: Annotate loadsave and image header classes
2 parents 40e31e8 + 9f189c6 commit ed95d8d

15 files changed

+50
-33
lines changed

nibabel/analyze.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,6 @@
8383
"""
8484
from __future__ import annotations
8585

86-
from typing import Type
87-
8886
import numpy as np
8987

9088
from .arrayproxy import ArrayProxy
@@ -895,7 +893,8 @@ def may_contain_header(klass, binaryblock):
895893
class AnalyzeImage(SpatialImage):
896894
"""Class for basic Analyze format image"""
897895

898-
header_class: Type[AnalyzeHeader] = AnalyzeHeader
896+
header_class: type[AnalyzeHeader] = AnalyzeHeader
897+
header: AnalyzeHeader
899898
_meta_sniff_len = header_class.sizeof_hdr
900899
files_types: tuple[tuple[str, str], ...] = (('image', '.img'), ('header', '.hdr'))
901900
valid_exts: tuple[str, ...] = ('.img', '.hdr')

nibabel/brikhead.py

+1
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,7 @@ class AFNIImage(SpatialImage):
475475
"""
476476

477477
header_class = AFNIHeader
478+
header: AFNIHeader
478479
valid_exts = ('.brik', '.head')
479480
files_types = (('image', '.brik'), ('header', '.head'))
480481
_compressed_suffixes = ('.gz', '.bz2', '.Z', '.zst')

nibabel/cifti2/cifti2.py

+1
Original file line numberDiff line numberDiff line change
@@ -1411,6 +1411,7 @@ class Cifti2Image(DataobjImage, SerializableImage):
14111411
"""Class for single file CIFTI-2 format image"""
14121412

14131413
header_class = Cifti2Header
1414+
header: Cifti2Header
14141415
valid_exts = Nifti2Image.valid_exts
14151416
files_types = Nifti2Image.files_types
14161417
makeable = False

nibabel/ecat.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -751,7 +751,7 @@ class EcatImage(SpatialImage):
751751
valid_exts = ('.v',)
752752
files_types = (('image', '.v'), ('header', '.v'))
753753

754-
_header: EcatHeader
754+
header: EcatHeader
755755
_subheader: EcatSubHeader
756756

757757
ImageArrayProxy = EcatImageArrayProxy

nibabel/filebasedimages.py

+1-3
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
import io
1313
import typing as ty
1414
from copy import deepcopy
15-
from typing import Type
1615
from urllib import request
1716

1817
from ._compression import COMPRESSION_ERRORS
@@ -158,8 +157,7 @@ class FileBasedImage:
158157
work.
159158
"""
160159

161-
header_class: Type[FileBasedHeader] = FileBasedHeader
162-
_header: FileBasedHeader
160+
header_class: type[FileBasedHeader] = FileBasedHeader
163161
_meta_sniff_len: int = 0
164162
files_types: tuple[ExtensionSpec, ...] = (('image', None),)
165163
valid_exts: tuple[str, ...] = ()

nibabel/freesurfer/mghformat.py

+1
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,7 @@ class MGHImage(SpatialImage, SerializableImage):
462462
"""Class for MGH format image"""
463463

464464
header_class = MGHHeader
465+
header: MGHHeader
465466
valid_exts = ('.mgh', '.mgz')
466467
# Register that .mgz extension signals gzip compression
467468
ImageOpener.compress_ext_map['.mgz'] = ImageOpener.gz_def

nibabel/imageclasses.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,13 @@
77
#
88
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
99
"""Define supported image classes and names"""
10+
from __future__ import annotations
11+
1012
from .analyze import AnalyzeImage
1113
from .brikhead import AFNIImage
1214
from .cifti2 import Cifti2Image
15+
from .dataobj_images import DataobjImage
16+
from .filebasedimages import FileBasedImage
1317
from .freesurfer import MGHImage
1418
from .gifti import GiftiImage
1519
from .minc1 import Minc1Image
@@ -21,7 +25,7 @@
2125
from .spm99analyze import Spm99AnalyzeImage
2226

2327
# Ordered by the load/save priority.
24-
all_image_classes = [
28+
all_image_classes: list[type[FileBasedImage]] = [
2529
Nifti1Pair,
2630
Nifti1Image,
2731
Nifti2Pair,
@@ -41,7 +45,7 @@
4145
# Image classes known to require spatial axes to be first in index ordering.
4246
# When adding an image class, consider whether the new class should be listed
4347
# here.
44-
KNOWN_SPATIAL_FIRST = (
48+
KNOWN_SPATIAL_FIRST: tuple[type[FileBasedImage], ...] = (
4549
Nifti1Pair,
4650
Nifti1Image,
4751
Nifti2Pair,
@@ -55,7 +59,7 @@
5559
)
5660

5761

58-
def spatial_axes_first(img):
62+
def spatial_axes_first(img: DataobjImage) -> bool:
5963
"""True if spatial image axes for `img` always precede other axes
6064
6165
Parameters

nibabel/loadsave.py

+25-17
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@
88
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
99
# module imports
1010
"""Utilities to load and save image objects"""
11+
from __future__ import annotations
12+
1113
import os
14+
import typing as ty
1215

1316
import numpy as np
1417

@@ -22,7 +25,18 @@
2225
_compressed_suffixes = ('.gz', '.bz2', '.zst')
2326

2427

25-
def _signature_matches_extension(filename):
28+
if ty.TYPE_CHECKING: # pragma: no cover
29+
from .filebasedimages import FileBasedImage
30+
from .filename_parser import FileSpec
31+
32+
P = ty.ParamSpec('P')
33+
34+
class Signature(ty.TypedDict):
35+
signature: bytes
36+
format_name: str
37+
38+
39+
def _signature_matches_extension(filename: FileSpec) -> tuple[bool, str]:
2640
"""Check if signature aka magic number matches filename extension.
2741
2842
Parameters
@@ -42,7 +56,7 @@ def _signature_matches_extension(filename):
4256
the empty string otherwise.
4357
4458
"""
45-
signatures = {
59+
signatures: dict[str, Signature] = {
4660
'.gz': {'signature': b'\x1f\x8b', 'format_name': 'gzip'},
4761
'.bz2': {'signature': b'BZh', 'format_name': 'bzip2'},
4862
'.zst': {'signature': b'\x28\xb5\x2f\xfd', 'format_name': 'ztsd'},
@@ -64,7 +78,7 @@ def _signature_matches_extension(filename):
6478
return False, f'File {filename} is not a {format_name} file'
6579

6680

67-
def load(filename, **kwargs):
81+
def load(filename: FileSpec, **kwargs) -> FileBasedImage:
6882
r"""Load file given filename, guessing at file type
6983
7084
Parameters
@@ -126,7 +140,7 @@ def guessed_image_type(filename):
126140
raise ImageFileError(f'Cannot work out file type of "{filename}"')
127141

128142

129-
def save(img, filename, **kwargs):
143+
def save(img: FileBasedImage, filename: FileSpec, **kwargs) -> None:
130144
r"""Save an image to file adapting format to `filename`
131145
132146
Parameters
@@ -161,19 +175,17 @@ def save(img, filename, **kwargs):
161175
from .nifti1 import Nifti1Image, Nifti1Pair
162176
from .nifti2 import Nifti2Image, Nifti2Pair
163177

164-
klass = None
165-
converted = None
166-
178+
converted: FileBasedImage
167179
if type(img) == Nifti1Image and lext in ('.img', '.hdr'):
168-
klass = Nifti1Pair
180+
converted = Nifti1Pair.from_image(img)
169181
elif type(img) == Nifti2Image and lext in ('.img', '.hdr'):
170-
klass = Nifti2Pair
182+
converted = Nifti2Pair.from_image(img)
171183
elif type(img) == Nifti1Pair and lext == '.nii':
172-
klass = Nifti1Image
184+
converted = Nifti1Image.from_image(img)
173185
elif type(img) == Nifti2Pair and lext == '.nii':
174-
klass = Nifti2Image
186+
converted = Nifti2Image.from_image(img)
175187
else: # arbitrary conversion
176-
valid_klasses = [klass for klass in all_image_classes if ext in klass.valid_exts]
188+
valid_klasses = [klass for klass in all_image_classes if lext in klass.valid_exts]
177189
if not valid_klasses: # if list is empty
178190
raise ImageFileError(f'Cannot work out file type of "{filename}"')
179191

@@ -186,13 +198,9 @@ def save(img, filename, **kwargs):
186198
break
187199
except Exception as e:
188200
err = e
189-
# ... and if none of them work, raise an error.
190-
if converted is None:
201+
else:
191202
raise err
192203

193-
# Here, we either have a klass or a converted image.
194-
if converted is None:
195-
converted = klass.from_image(img)
196204
converted.to_filename(filename, **kwargs)
197205

198206

nibabel/minc1.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
from __future__ import annotations
1111

1212
from numbers import Integral
13-
from typing import Type
1413

1514
import numpy as np
1615

@@ -307,7 +306,8 @@ class Minc1Image(SpatialImage):
307306
load.
308307
"""
309308

310-
header_class: Type[MincHeader] = Minc1Header
309+
header_class: type[MincHeader] = Minc1Header
310+
header: MincHeader
311311
_meta_sniff_len: int = 4
312312
valid_exts: tuple[str, ...] = ('.mnc',)
313313
files_types: tuple[tuple[str, str], ...] = (('image', '.mnc'),)

nibabel/minc2.py

+1
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ class Minc2Image(Minc1Image):
150150
# MINC2 does not do compressed whole files
151151
_compressed_suffixes = ()
152152
header_class = Minc2Header
153+
header: Minc2Header
153154

154155
@classmethod
155156
def from_file_map(klass, file_map, *, mmap=True, keep_file_open=None):

nibabel/nifti1.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515
import warnings
1616
from io import BytesIO
17-
from typing import Type
1817

1918
import numpy as np
2019
import numpy.linalg as npl
@@ -90,8 +89,8 @@
9089
# datatypes not in analyze format, with codes
9190
if have_binary128():
9291
# Only enable 128 bit floats if we really have IEEE binary 128 longdoubles
93-
_float128t: Type[np.generic] = np.longdouble
94-
_complex256t: Type[np.generic] = np.longcomplex
92+
_float128t: type[np.generic] = np.longdouble
93+
_complex256t: type[np.generic] = np.longcomplex
9594
else:
9695
_float128t = np.void
9796
_complex256t = np.void
@@ -1817,7 +1816,8 @@ class Nifti1PairHeader(Nifti1Header):
18171816
class Nifti1Pair(analyze.AnalyzeImage):
18181817
"""Class for NIfTI1 format image, header pair"""
18191818

1820-
header_class: Type[Nifti1Header] = Nifti1PairHeader
1819+
header_class: type[Nifti1Header] = Nifti1PairHeader
1820+
header: Nifti1Header
18211821
_meta_sniff_len = header_class.sizeof_hdr
18221822
rw = True
18231823

nibabel/parrec.py

+1
Original file line numberDiff line numberDiff line change
@@ -1253,6 +1253,7 @@ class PARRECImage(SpatialImage):
12531253
"""PAR/REC image"""
12541254

12551255
header_class = PARRECHeader
1256+
header: PARRECHeader
12561257
valid_exts = ('.rec', '.par')
12571258
files_types = (('image', '.rec'), ('header', '.par'))
12581259

nibabel/spatialimages.py

+1
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,7 @@ class SpatialImage(DataobjImage):
476476
ImageSlicer: type[SpatialFirstSlicer] = SpatialFirstSlicer
477477

478478
_header: SpatialHeader
479+
header: SpatialHeader
479480

480481
def __init__(
481482
self,

nibabel/spm2analyze.py

+1
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ class Spm2AnalyzeImage(spm99.Spm99AnalyzeImage):
128128
"""Class for SPM2 variant of basic Analyze image"""
129129

130130
header_class = Spm2AnalyzeHeader
131+
header: Spm2AnalyzeHeader
131132

132133

133134
load = Spm2AnalyzeImage.from_filename

nibabel/spm99analyze.py

+1
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ class Spm99AnalyzeImage(analyze.AnalyzeImage):
227227
"""Class for SPM99 variant of basic Analyze image"""
228228

229229
header_class = Spm99AnalyzeHeader
230+
header: Spm99AnalyzeHeader
230231
files_types = (('image', '.img'), ('header', '.hdr'), ('mat', '.mat'))
231232
has_affine = True
232233
makeable = True

0 commit comments

Comments
 (0)