137
137
from typing import Literal
138
138
139
139
import numpy as np
140
+ import numpy .typing as npt
141
+ from typing_extensions import TypeVar
140
142
141
143
from .casting import sctypes_aliases
142
144
from .dataobj_images import DataobjImage
150
152
import io
151
153
from collections .abc import Sequence
152
154
153
- import numpy .typing as npt
154
-
155
155
from .arrayproxy import ArrayLike
156
156
from .fileholders import FileMap
157
157
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]' )
160
166
161
167
162
168
class HasDtype (ty .Protocol ):
@@ -194,7 +200,7 @@ def __init__(
194
200
data_dtype : npt .DTypeLike = np .float32 ,
195
201
shape : Sequence [int ] = (0 ,),
196
202
zooms : Sequence [float ] | None = None ,
197
- ):
203
+ ) -> None :
198
204
self .set_data_dtype (data_dtype )
199
205
self ._zooms = ()
200
206
self .set_data_shape (shape )
@@ -461,7 +467,7 @@ def slice_affine(self, slicer: object) -> np.ndarray:
461
467
return self .img .affine .dot (transform )
462
468
463
469
464
- class SpatialImage (DataobjImage ):
470
+ class SpatialImage (DataobjImage , ty . Generic [ AffT ] ):
465
471
"""Template class for volumetric (3D/4D) images"""
466
472
467
473
header_class : type [SpatialHeader ] = SpatialHeader
@@ -473,11 +479,11 @@ class SpatialImage(DataobjImage):
473
479
def __init__ (
474
480
self ,
475
481
dataobj : ArrayLike ,
476
- affine : np . ndarray | None ,
482
+ affine : AffT ,
477
483
header : FileBasedHeader | ty .Mapping | None = None ,
478
484
extra : ty .Mapping | None = None ,
479
485
file_map : FileMap | None = None ,
480
- ):
486
+ ) -> None :
481
487
"""Initialize image
482
488
483
489
The image is a combination of (array-like, affine matrix, header), with
@@ -510,7 +516,7 @@ def __init__(
510
516
# do need 4,4.
511
517
# Copy affine to isolate from environment. Specify float type to
512
518
# 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]
514
520
if not affine .shape == (4 , 4 ):
515
521
raise ValueError ('Affine should be shape 4,4' )
516
522
self ._affine = affine
@@ -524,7 +530,7 @@ def __init__(
524
530
self ._data_cache = None
525
531
526
532
@property
527
- def affine (self ):
533
+ def affine (self ) -> AffT :
528
534
return self ._affine
529
535
530
536
def update_header (self ) -> None :
@@ -586,7 +592,7 @@ def set_data_dtype(self, dtype: npt.DTypeLike) -> None:
586
592
self ._header .set_data_dtype (dtype )
587
593
588
594
@classmethod
589
- def from_image (klass : type [SpatialImgT ], img : SpatialImage | FileBasedImage ) -> SpatialImgT :
595
+ def from_image (klass : type [AnySpatialImgT ], img : FileBasedImage ) -> AnySpatialImgT :
590
596
"""Class method to create new instance of own class from `img`
591
597
592
598
Parameters
@@ -629,7 +635,7 @@ def slicer(self: SpatialImgT) -> SpatialFirstSlicer[SpatialImgT]:
629
635
"""
630
636
return self .ImageSlicer (self )
631
637
632
- def __getitem__ (self , idx : object ) -> None :
638
+ def __getitem__ (self , idx : object ) -> ty . Never :
633
639
"""No slicing or dictionary interface for images
634
640
635
641
Use the slicer attribute to perform cropping and subsampling at your
0 commit comments