Skip to content

Commit a60d2b7

Browse files
authored
Merge pull request #1115 from nipy/enh/type_annotations
ENH: Add minimal type annotations and run mypy in CI
2 parents faf4566 + 85f4cb4 commit a60d2b7

28 files changed

+113
-105
lines changed

.github/workflows/misc.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
matrix:
2525
python-version: ["3.10"]
2626
install: ['pip']
27-
check: ['style', 'doctest']
27+
check: ['style', 'doctest', 'typing']
2828
pip-flags: ['']
2929
depends: ['REQUIREMENTS']
3030
env:

.pre-commit-config.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,15 @@ repos:
2525
hooks:
2626
- id: flake8
2727
exclude: "^(doc|nisext|tools)/"
28+
- repo: https://github.com/pre-commit/mirrors-mypy
29+
rev: v0.991
30+
hooks:
31+
- id: mypy
32+
# Sync with project.optional-dependencies.typing
33+
additional_dependencies:
34+
- pytest
35+
- types-setuptools
36+
- types-Pillow
37+
- pydicom
38+
# Sync with tool.mypy['exclude']
39+
exclude: "^(doc|nisext|tools)/|.*/tests/"

nibabel/analyze.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -81,14 +81,17 @@
8181
can be loaded with and without a default flip, so the saved zoom will not
8282
constrain the affine.
8383
"""
84+
from __future__ import annotations
85+
86+
from typing import Type
8487

8588
import numpy as np
8689

8790
from .arrayproxy import ArrayProxy
8891
from .arraywriters import ArrayWriter, WriterError, get_slope_inter, make_array_writer
8992
from .batteryrunners import Report
9093
from .fileholders import copy_file_map
91-
from .spatialimages import HeaderDataError, HeaderTypeError, SpatialImage
94+
from .spatialimages import HeaderDataError, HeaderTypeError, SpatialHeader, SpatialImage
9295
from .volumeutils import (
9396
apply_read_scaling,
9497
array_from_file,
@@ -131,7 +134,7 @@
131134
('glmax', 'i4'),
132135
('glmin', 'i4'),
133136
]
134-
data_history_dtd = [
137+
data_history_dtd: list[tuple[str, str] | tuple[str, str, tuple[int, ...]]] = [
135138
('descrip', 'S80'),
136139
('aux_file', 'S24'),
137140
('orient', 'S1'),
@@ -172,7 +175,7 @@
172175
data_type_codes = make_dt_codes(_dtdefs)
173176

174177

175-
class AnalyzeHeader(LabeledWrapStruct):
178+
class AnalyzeHeader(LabeledWrapStruct, SpatialHeader):
176179
"""Class for basic analyze header
177180
178181
Implements zoom-only setting of affine transform, and no image
@@ -892,11 +895,11 @@ def may_contain_header(klass, binaryblock):
892895
class AnalyzeImage(SpatialImage):
893896
"""Class for basic Analyze format image"""
894897

895-
header_class = AnalyzeHeader
898+
header_class: Type[AnalyzeHeader] = AnalyzeHeader
896899
_meta_sniff_len = header_class.sizeof_hdr
897-
files_types = (('image', '.img'), ('header', '.hdr'))
898-
valid_exts = ('.img', '.hdr')
899-
_compressed_suffixes = ('.gz', '.bz2', '.zst')
900+
files_types: tuple[tuple[str, str], ...] = (('image', '.img'), ('header', '.hdr'))
901+
valid_exts: tuple[str, ...] = ('.img', '.hdr')
902+
_compressed_suffixes: tuple[str, ...] = ('.gz', '.bz2', '.zst')
900903

901904
makeable = True
902905
rw = True

nibabel/benchmarks/bench_arrayproxy_slicing.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626

2727
# if memory_profiler is installed, we get memory usage results
2828
try:
29-
from memory_profiler import memory_usage
29+
from memory_profiler import memory_usage # type: ignore
3030
except ImportError:
3131
memory_usage = None
3232

nibabel/brikhead.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
am aware) always be >= 1. This permits sub-brick indexing common in AFNI
2828
programs (e.g., example4d+orig'[0]').
2929
"""
30-
3130
import os
3231
import re
3332
from copy import deepcopy

nibabel/casting.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
Most routines work round some numpy oddities in floating point precision and
44
casting. Others work round numpy casting to and from python ints
55
"""
6+
from __future__ import annotations
67

78
import warnings
89
from numbers import Integral
@@ -110,7 +111,7 @@ def float_to_int(arr, int_type, nan2zero=True, infmax=False):
110111

111112

112113
# Cache range values
113-
_SHARED_RANGES = {}
114+
_SHARED_RANGES: dict[tuple[type, type], tuple[np.number, np.number]] = {}
114115

115116

116117
def shared_range(flt_type, int_type):

nibabel/cmdline/dicomfs.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class dummy_fuse:
2525

2626

2727
try:
28-
import fuse
28+
import fuse # type: ignore
2929

3030
uid = os.getuid()
3131
gid = os.getgid()

nibabel/ecat.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050

5151
from .arraywriters import make_array_writer
5252
from .fileslice import canonical_slicers, predict_shape, slice2outax
53-
from .spatialimages import SpatialImage
53+
from .spatialimages import SpatialHeader, SpatialImage
5454
from .volumeutils import array_from_file, make_dt_codes, native_code, swapped_code
5555
from .wrapstruct import WrapStruct
5656

@@ -243,7 +243,7 @@
243243
patient_orient_neurological = [1, 3, 5, 7]
244244

245245

246-
class EcatHeader(WrapStruct):
246+
class EcatHeader(WrapStruct, SpatialHeader):
247247
"""Class for basic Ecat PET header
248248
249249
Sub-parts of standard Ecat File

nibabel/externals/netcdf.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -871,6 +871,7 @@ def __setattr__(self, attr, value):
871871
pass
872872
self.__dict__[attr] = value
873873

874+
@property
874875
def isrec(self):
875876
"""Returns whether the variable has a record dimension or not.
876877
@@ -881,16 +882,15 @@ def isrec(self):
881882
882883
"""
883884
return bool(self.data.shape) and not self._shape[0]
884-
isrec = property(isrec)
885885

886+
@property
886887
def shape(self):
887888
"""Returns the shape tuple of the data variable.
888889
889890
This is a read-only attribute and can not be modified in the
890891
same manner of other numpy arrays.
891892
"""
892893
return self.data.shape
893-
shape = property(shape)
894894

895895
def getValue(self):
896896
"""

nibabel/filebasedimages.py

Lines changed: 10 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@
77
#
88
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
99
"""Common interface for any image format--volume or surface, binary or xml."""
10+
from __future__ import annotations
1011

1112
import io
1213
from copy import deepcopy
14+
from typing import Type
1315
from urllib import request
1416

1517
from .fileholders import FileHolder
@@ -74,7 +76,6 @@ class FileBasedImage:
7476
7577
properties:
7678
77-
* shape
7879
* header
7980
8081
methods:
@@ -118,25 +119,6 @@ class FileBasedImage:
118119
119120
img.to_file_map()
120121
121-
You can get the data out again with::
122-
123-
img.get_fdata()
124-
125-
Less commonly, for some image types that support it, you might want to
126-
fetch out the unscaled array via the object containing the data::
127-
128-
unscaled_data = img.dataoobj.get_unscaled()
129-
130-
Analyze-type images (including nifti) support this, but others may not
131-
(MINC, for example).
132-
133-
Sometimes you might to avoid any loss of precision by making the
134-
data type the same as the input::
135-
136-
hdr = img.header
137-
hdr.set_data_dtype(data.dtype)
138-
img.to_filename(fname)
139-
140122
**Files interface**
141123
142124
The image has an attribute ``file_map``. This is a mapping, that has keys
@@ -158,20 +140,20 @@ class FileBasedImage:
158140
contain enough information so that an existing image instance can save
159141
itself back to the files pointed to in ``file_map``. When a file holder
160142
holds active file-like objects, then these may be affected by the
161-
initial file read; in this case, the contains file-like objects need to
143+
initial file read; in this case, the file-like objects need to
162144
carry the position at which a write (with ``to_file_map``) should place the
163145
data. The ``file_map`` contents should therefore be such, that this will
164146
work.
165147
"""
166148

167-
header_class = FileBasedHeader
168-
_meta_sniff_len = 0
169-
files_types = (('image', None),)
170-
valid_exts = ()
171-
_compressed_suffixes = ()
149+
header_class: Type[FileBasedHeader] = FileBasedHeader
150+
_meta_sniff_len: int = 0
151+
files_types: tuple[tuple[str, str | None], ...] = (('image', None),)
152+
valid_exts: tuple[str, ...] = ()
153+
_compressed_suffixes: tuple[str, ...] = ()
172154

173-
makeable = True # Used in test code
174-
rw = True # Used in test code
155+
makeable: bool = True # Used in test code
156+
rw: bool = True # Used in test code
175157

176158
def __init__(self, header=None, extra=None, file_map=None):
177159
"""Initialize image

0 commit comments

Comments
 (0)