Skip to content

Commit 4008cc5

Browse files
committed
type: Annotate SpatialImage.affine, make generic so None can be tracked
1 parent 84294f4 commit 4008cc5

File tree

1 file changed

+18
-12
lines changed

1 file changed

+18
-12
lines changed

nibabel/spatialimages.py

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@
137137
from typing import Literal
138138

139139
import numpy as np
140+
import numpy.typing as npt
141+
from typing_extensions import TypeVar
140142

141143
from .casting import sctypes_aliases
142144
from .dataobj_images import DataobjImage
@@ -150,13 +152,17 @@
150152
import io
151153
from collections.abc import Sequence
152154

153-
import numpy.typing as npt
154-
155155
from .arrayproxy import ArrayLike
156156
from .fileholders import FileMap
157157

158-
SpatialImgT = ty.TypeVar('SpatialImgT', bound='SpatialImage')
159-
SpatialHdrT = ty.TypeVar('SpatialHdrT', bound='SpatialHeader')
158+
# Track whether the image is initialized with an affine or not
159+
# This will almost always be the case, but there are some exceptions
160+
# and some functions that will fail if the affine is not present
161+
Affine = npt.NDArray[np.floating]
162+
AffT = TypeVar('AffT', covariant=True, bound=ty.Union[Affine, None], default=Affine)
163+
SpatialImgT = TypeVar('SpatialImgT', bound='SpatialImage[Affine]')
164+
SpatialHdrT = TypeVar('SpatialHdrT', bound='SpatialHeader')
165+
AnySpatialImgT = TypeVar('AnySpatialImgT', bound='SpatialImage[Affine | None]')
160166

161167

162168
class HasDtype(ty.Protocol):
@@ -194,7 +200,7 @@ def __init__(
194200
data_dtype: npt.DTypeLike = np.float32,
195201
shape: Sequence[int] = (0,),
196202
zooms: Sequence[float] | None = None,
197-
):
203+
) -> None:
198204
self.set_data_dtype(data_dtype)
199205
self._zooms = ()
200206
self.set_data_shape(shape)
@@ -461,7 +467,7 @@ def slice_affine(self, slicer: object) -> np.ndarray:
461467
return self.img.affine.dot(transform)
462468

463469

464-
class SpatialImage(DataobjImage):
470+
class SpatialImage(DataobjImage, ty.Generic[AffT]):
465471
"""Template class for volumetric (3D/4D) images"""
466472

467473
header_class: type[SpatialHeader] = SpatialHeader
@@ -473,11 +479,11 @@ class SpatialImage(DataobjImage):
473479
def __init__(
474480
self,
475481
dataobj: ArrayLike,
476-
affine: np.ndarray | None,
482+
affine: AffT,
477483
header: FileBasedHeader | ty.Mapping | None = None,
478484
extra: ty.Mapping | None = None,
479485
file_map: FileMap | None = None,
480-
):
486+
) -> None:
481487
"""Initialize image
482488
483489
The image is a combination of (array-like, affine matrix, header), with
@@ -510,7 +516,7 @@ def __init__(
510516
# do need 4,4.
511517
# Copy affine to isolate from environment. Specify float type to
512518
# avoid surprising integer rounding when setting values into affine
513-
affine = np.array(affine, dtype=np.float64, copy=True)
519+
affine = np.array(affine, dtype=np.float64, copy=True) # type: ignore[assignment]
514520
if not affine.shape == (4, 4):
515521
raise ValueError('Affine should be shape 4,4')
516522
self._affine = affine
@@ -524,7 +530,7 @@ def __init__(
524530
self._data_cache = None
525531

526532
@property
527-
def affine(self):
533+
def affine(self) -> AffT:
528534
return self._affine
529535

530536
def update_header(self) -> None:
@@ -586,7 +592,7 @@ def set_data_dtype(self, dtype: npt.DTypeLike) -> None:
586592
self._header.set_data_dtype(dtype)
587593

588594
@classmethod
589-
def from_image(klass: type[SpatialImgT], img: SpatialImage | FileBasedImage) -> SpatialImgT:
595+
def from_image(klass: type[AnySpatialImgT], img: FileBasedImage) -> AnySpatialImgT:
590596
"""Class method to create new instance of own class from `img`
591597
592598
Parameters
@@ -629,7 +635,7 @@ def slicer(self: SpatialImgT) -> SpatialFirstSlicer[SpatialImgT]:
629635
"""
630636
return self.ImageSlicer(self)
631637

632-
def __getitem__(self, idx: object) -> None:
638+
def __getitem__(self, idx: object) -> ty.Never:
633639
"""No slicing or dictionary interface for images
634640
635641
Use the slicer attribute to perform cropping and subsampling at your

0 commit comments

Comments
 (0)