diff --git a/nibabel/analyze.py b/nibabel/analyze.py index e165112259..b275920a7c 100644 --- a/nibabel/analyze.py +++ b/nibabel/analyze.py @@ -81,6 +81,9 @@ can be loaded with and without a default flip, so the saved zoom will not constrain the affine. """ +from __future__ import annotations + +from typing import Type import numpy as np @@ -131,7 +134,7 @@ ('glmax', 'i4'), ('glmin', 'i4'), ] -data_history_dtd = [ +data_history_dtd: list[tuple[str, str] | tuple[str, str, tuple[int, ...]]] = [ ('descrip', 'S80'), ('aux_file', 'S24'), ('orient', 'S1'), @@ -892,11 +895,11 @@ def may_contain_header(klass, binaryblock): class AnalyzeImage(SpatialImage): """Class for basic Analyze format image""" - header_class = AnalyzeHeader + header_class: Type[AnalyzeHeader] = AnalyzeHeader _meta_sniff_len = header_class.sizeof_hdr - files_types = (('image', '.img'), ('header', '.hdr')) - valid_exts = ('.img', '.hdr') - _compressed_suffixes = ('.gz', '.bz2', '.zst') + files_types: tuple[tuple[str, str], ...] = (('image', '.img'), ('header', '.hdr')) + valid_exts: tuple[str, ...] = ('.img', '.hdr') + _compressed_suffixes: tuple[str, ...] = ('.gz', '.bz2', '.zst') makeable = True rw = True diff --git a/nibabel/benchmarks/bench_arrayproxy_slicing.py b/nibabel/benchmarks/bench_arrayproxy_slicing.py index d313a7db5e..958923d7ea 100644 --- a/nibabel/benchmarks/bench_arrayproxy_slicing.py +++ b/nibabel/benchmarks/bench_arrayproxy_slicing.py @@ -26,7 +26,7 @@ # if memory_profiler is installed, we get memory usage results try: - from memory_profiler import memory_usage + from memory_profiler import memory_usage # type: ignore except ImportError: memory_usage = None diff --git a/nibabel/brikhead.py b/nibabel/brikhead.py index 470ed16664..389c4d29df 100644 --- a/nibabel/brikhead.py +++ b/nibabel/brikhead.py @@ -27,6 +27,7 @@ am aware) always be >= 1. This permits sub-brick indexing common in AFNI programs (e.g., example4d+orig'[0]'). """ +from __future__ import annotations import os import re @@ -476,9 +477,9 @@ class AFNIImage(SpatialImage): True """ - header_class = AFNIHeader - valid_exts = ('.brik', '.head') - files_types = (('image', '.brik'), ('header', '.head')) + header_class: type = AFNIHeader + valid_exts: tuple[str, ...] = ('.brik', '.head') + files_types: tuple[tuple[str, str], ...] = (('image', '.brik'), ('header', '.head')) _compressed_suffixes = ('.gz', '.bz2', '.Z', '.zst') makeable = False rw = False diff --git a/nibabel/casting.py b/nibabel/casting.py index a17a25a2c8..e01dd40d57 100644 --- a/nibabel/casting.py +++ b/nibabel/casting.py @@ -3,6 +3,7 @@ Most routines work round some numpy oddities in floating point precision and casting. Others work round numpy casting to and from python ints """ +from __future__ import annotations import warnings from numbers import Integral @@ -110,7 +111,7 @@ def float_to_int(arr, int_type, nan2zero=True, infmax=False): # Cache range values -_SHARED_RANGES = {} +_SHARED_RANGES : dict[tuple[type, type], tuple[np.number, np.number]] = {} def shared_range(flt_type, int_type): diff --git a/nibabel/cmdline/dicomfs.py b/nibabel/cmdline/dicomfs.py index 8de1438544..85d7d8dcad 100644 --- a/nibabel/cmdline/dicomfs.py +++ b/nibabel/cmdline/dicomfs.py @@ -25,7 +25,7 @@ class dummy_fuse: try: - import fuse + import fuse # type: ignore uid = os.getuid() gid = os.getgid() diff --git a/nibabel/externals/netcdf.py b/nibabel/externals/netcdf.py index 2fddcf03d9..b8d1244c0c 100644 --- a/nibabel/externals/netcdf.py +++ b/nibabel/externals/netcdf.py @@ -871,6 +871,7 @@ def __setattr__(self, attr, value): pass self.__dict__[attr] = value + @property def isrec(self): """Returns whether the variable has a record dimension or not. @@ -881,8 +882,8 @@ def isrec(self): """ return bool(self.data.shape) and not self._shape[0] - isrec = property(isrec) + @property def shape(self): """Returns the shape tuple of the data variable. @@ -890,7 +891,6 @@ def shape(self): same manner of other numpy arrays. """ return self.data.shape - shape = property(shape) def getValue(self): """ diff --git a/nibabel/filebasedimages.py b/nibabel/filebasedimages.py index 938a17d7c3..739b11f474 100644 --- a/nibabel/filebasedimages.py +++ b/nibabel/filebasedimages.py @@ -7,6 +7,7 @@ # ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## """Common interface for any image format--volume or surface, binary or xml.""" +from __future__ import annotations import io from copy import deepcopy @@ -144,14 +145,14 @@ class FileBasedImage: work. """ - header_class = FileBasedHeader - _meta_sniff_len = 0 - files_types = (('image', None),) - valid_exts = () - _compressed_suffixes = () + header_class: type = FileBasedHeader + _meta_sniff_len: int = 0 + files_types: tuple[tuple[str, str | None], ...] = (('image', None),) + valid_exts: tuple[str, ...] = () + _compressed_suffixes: tuple[str, ...] = () - makeable = True # Used in test code - rw = True # Used in test code + makeable: bool = True # Used in test code + rw: bool = True # Used in test code def __init__(self, header=None, extra=None, file_map=None): """Initialize image diff --git a/nibabel/gifti/gifti.py b/nibabel/gifti/gifti.py index c80fbf2e22..c10cb9f06d 100644 --- a/nibabel/gifti/gifti.py +++ b/nibabel/gifti/gifti.py @@ -11,10 +11,12 @@ The Gifti specification was (at time of writing) available as a PDF download from http://www.nitrc.org/projects/gifti/ """ +from __future__ import annotations import base64 import sys import warnings +from typing import Type import numpy as np @@ -577,7 +579,7 @@ class GiftiImage(xml.XmlSerializable, SerializableImage): # The parser will in due course be a GiftiImageParser, but we can't set # that now, because it would result in a circular import. We set it after # the class has been defined, at the end of the class definition. - parser = None + parser: Type[xml.XmlParser] def __init__( self, @@ -832,7 +834,7 @@ def _to_xml_element(self): GIFTI.append(dar._to_xml_element()) return GIFTI - def to_xml(self, enc='utf-8'): + def to_xml(self, enc='utf-8') -> bytes: """Return XML corresponding to image content""" header = b""" @@ -840,9 +842,10 @@ def to_xml(self, enc='utf-8'): return header + super().to_xml(enc) # Avoid the indirection of going through to_file_map - to_bytes = to_xml + def to_bytes(self, enc='utf-8'): + return self.to_xml(enc=enc) - def to_file_map(self, file_map=None): + def to_file_map(self, file_map=None, enc='utf-8'): """Save the current image to the specified file_map Parameters @@ -858,7 +861,7 @@ def to_file_map(self, file_map=None): if file_map is None: file_map = self.file_map with file_map['image'].get_prepare_fileobj('wb') as f: - f.write(self.to_xml()) + f.write(self.to_xml(enc=enc)) @classmethod def from_file_map(klass, file_map, buffer_size=35000000, mmap=True): diff --git a/nibabel/minc1.py b/nibabel/minc1.py index fb183277bc..02ba33954a 100644 --- a/nibabel/minc1.py +++ b/nibabel/minc1.py @@ -7,6 +7,7 @@ # ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## """Read MINC1 format images""" +from __future__ import annotations from numbers import Integral @@ -305,11 +306,11 @@ class Minc1Image(SpatialImage): load. """ - header_class = Minc1Header + header_class: type = Minc1Header _meta_sniff_len = 4 valid_exts = ('.mnc',) files_types = (('image', '.mnc'),) - _compressed_suffixes = ('.gz', '.bz2', '.zst') + _compressed_suffixes: tuple[str, ...] = ('.gz', '.bz2', '.zst') makeable = True rw = False diff --git a/nibabel/minc2.py b/nibabel/minc2.py index 1fffae0c86..c06892b0cf 100644 --- a/nibabel/minc2.py +++ b/nibabel/minc2.py @@ -25,6 +25,8 @@ mincstats my_funny.mnc """ +from __future__ import annotations + import numpy as np from .minc1 import Minc1File, Minc1Image, MincError, MincHeader @@ -148,14 +150,14 @@ class Minc2Image(Minc1Image): """ # MINC2 does not do compressed whole files - _compressed_suffixes = () + _compressed_suffixes: tuple[str, ...] = () header_class = Minc2Header @classmethod def from_file_map(klass, file_map, *, mmap=True, keep_file_open=None): # Import of h5py might take awhile for MPI-enabled builds # So we are importing it here "on demand" - import h5py + import h5py # type: ignore holder = file_map['image'] if holder.filename is None: diff --git a/nibabel/nicom/dicomwrappers.py b/nibabel/nicom/dicomwrappers.py index 7e6bea9009..572957f391 100755 --- a/nibabel/nicom/dicomwrappers.py +++ b/nibabel/nicom/dicomwrappers.py @@ -127,8 +127,6 @@ class Wrapper: is_multiframe = False b_matrix = None q_vector = None - b_value = None - b_vector = None def __init__(self, dcm_data): """Initialize wrapper diff --git a/nibabel/nifti1.py b/nibabel/nifti1.py index a10686145b..9e6d4a55e1 100644 --- a/nibabel/nifti1.py +++ b/nibabel/nifti1.py @@ -10,8 +10,11 @@ NIfTI1 format defined at http://nifti.nimh.nih.gov/nifti-1/ """ +from __future__ import annotations + import warnings from io import BytesIO +from typing import Type import numpy as np import numpy.linalg as npl @@ -86,8 +89,8 @@ # datatypes not in analyze format, with codes if have_binary128(): # Only enable 128 bit floats if we really have IEEE binary 128 longdoubles - _float128t = np.longdouble - _complex256t = np.longcomplex + _float128t: Type[np.generic] = np.longdouble + _complex256t: Type[np.generic] = np.longcomplex else: _float128t = np.void _complex256t = np.void @@ -1813,7 +1816,7 @@ class Nifti1PairHeader(Nifti1Header): class Nifti1Pair(analyze.AnalyzeImage): """Class for NIfTI1 format image, header pair""" - header_class = Nifti1PairHeader + header_class: Type[Nifti1Header] = Nifti1PairHeader _meta_sniff_len = header_class.sizeof_hdr rw = True @@ -1844,9 +1847,7 @@ def __init__(self, dataobj, affine, header=None, extra=None, file_map=None, dtyp self._affine2header() # Copy docstring - __init__.__doc__ = ( - analyze.AnalyzeImage.__init__.__doc__ - + """ + __init__.__doc__ = f"""{analyze.AnalyzeImage.__init__.__doc__} Notes ----- @@ -1859,7 +1860,6 @@ def __init__(self, dataobj, affine, header=None, extra=None, file_map=None, dtyp :meth:`set_qform` methods can be used to update the codes after an image has been created - see those methods, and the :ref:`manual ` for more details. """ - ) def update_header(self): """Harmonize header with image data and affine diff --git a/nibabel/openers.py b/nibabel/openers.py index 4a1b911c95..d75839fe1a 100644 --- a/nibabel/openers.py +++ b/nibabel/openers.py @@ -20,7 +20,7 @@ # is indexed_gzip present and modern? try: - import indexed_gzip as igzip + import indexed_gzip as igzip # type: ignore version = igzip.__version__ diff --git a/nibabel/parrec.py b/nibabel/parrec.py index 27ade56ae9..7c594dcb45 100644 --- a/nibabel/parrec.py +++ b/nibabel/parrec.py @@ -1338,7 +1338,7 @@ def from_filename( strict_sort=strict_sort, ) - load = from_filename + load = from_filename # type: ignore -load = PARRECImage.load +load = PARRECImage.from_filename diff --git a/nibabel/pydicom_compat.py b/nibabel/pydicom_compat.py index 9ee2553c5a..c8944ed4a6 100644 --- a/nibabel/pydicom_compat.py +++ b/nibabel/pydicom_compat.py @@ -7,7 +7,7 @@ without error, and always defines: * have_dicom : True if we can import pydicom or dicom; -* pydicom : pydicom module or dicom module or None of not importable; +* pydicom : pydicom module or dicom module or None if not importable; * read_file : ``read_file`` function if pydicom or dicom module is importable else None; * tag_for_keyword : ``tag_for_keyword`` function if pydicom or dicom module @@ -19,26 +19,27 @@ A deprecated copy is available here for backward compatibility. """ +from __future__ import annotations +from .optpkg import optional_package +from typing import Callable +from types import ModuleType # Module has (apparently) unused imports; stop flake8 complaining # flake8: noqa from .deprecated import deprecate_with_version -have_dicom = True -pydicom = read_file = tag_for_keyword = Sequence = None +pydicom, have_dicom, _ = optional_package('pydicom') -try: - import pydicom -except ImportError: - have_dicom = False -else: # pydicom module available +read_file: Callable | None = None +tag_for_keyword: Callable | None = None +Sequence: type | None = None + +if have_dicom: # Values not imported by default - import pydicom.values + import pydicom.values # type: ignore from pydicom.dicomio import read_file from pydicom.sequence import Sequence - -if have_dicom: tag_for_keyword = pydicom.datadict.tag_for_keyword diff --git a/nibabel/spatialimages.py b/nibabel/spatialimages.py index 794bb750e6..471c06a076 100644 --- a/nibabel/spatialimages.py +++ b/nibabel/spatialimages.py @@ -129,6 +129,7 @@ >>> np.all(img3.get_fdata(dtype=np.float32) == data) True """ +from __future__ import annotations import numpy as np @@ -400,7 +401,7 @@ def slice_affine(self, slicer): class SpatialImage(DataobjImage): """Template class for volumetric (3D/4D) images""" - header_class = SpatialHeader + header_class: type = SpatialHeader ImageSlicer = SpatialFirstSlicer def __init__(self, dataobj, affine, header=None, extra=None, file_map=None): diff --git a/nibabel/spm99analyze.py b/nibabel/spm99analyze.py index 12e3cb658d..a089bedb02 100644 --- a/nibabel/spm99analyze.py +++ b/nibabel/spm99analyze.py @@ -274,7 +274,7 @@ def from_file_map(klass, file_map, *, mmap=True, keep_file_open=None): contents = matf.read() if len(contents) == 0: return ret - import scipy.io as sio + import scipy.io as sio # type: ignore mats = sio.loadmat(BytesIO(contents)) if 'mat' in mats: # this overrides a 'M', and includes any flip diff --git a/nibabel/testing/np_features.py b/nibabel/testing/np_features.py index c0739a8502..df6bd72350 100644 --- a/nibabel/testing/np_features.py +++ b/nibabel/testing/np_features.py @@ -1,24 +1,15 @@ """Look for changes in numpy behavior over versions """ - +from functools import lru_cache import numpy as np -def memmap_after_ufunc(): +@lru_cache(maxsize=None) +def memmap_after_ufunc() -> bool: """Return True if ufuncs on memmap arrays always return memmap arrays This should be True for numpy < 1.12, False otherwise. - - Memoize after first call. We do this to avoid having to call this when - importing nibabel.testing, because we cannot depend on the source file - being present - see gh-571. """ - if memmap_after_ufunc.result is not None: - return memmap_after_ufunc.result with open(__file__, 'rb') as fobj: mm_arr = np.memmap(fobj, mode='r', shape=(10,), dtype=np.uint8) - memmap_after_ufunc.result = isinstance(mm_arr + 1, np.memmap) - return memmap_after_ufunc.result - - -memmap_after_ufunc.result = None + return isinstance(mm_arr + 1, np.memmap) diff --git a/nibabel/volumeutils.py b/nibabel/volumeutils.py index b339b6bab5..225062b2cb 100644 --- a/nibabel/volumeutils.py +++ b/nibabel/volumeutils.py @@ -7,6 +7,7 @@ # ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## """Utility functions for analyze-like formats""" +from __future__ import annotations import gzip import sys @@ -29,7 +30,7 @@ native_code = sys_is_le and '<' or '>' swapped_code = sys_is_le and '>' or '<' -endian_codes = ( # numpy code, aliases +_endian_codes = ( # numpy code, aliases ('<', 'little', 'l', 'le', 'L', 'LE'), ('>', 'big', 'BIG', 'b', 'be', 'B', 'BE'), (native_code, 'native', 'n', 'N', '=', '|', 'i', 'I'), @@ -41,7 +42,7 @@ default_compresslevel = 1 #: file-like classes known to hold compressed data -COMPRESSED_FILE_LIKES = (gzip.GzipFile, BZ2File, IndexedGzipFile) +COMPRESSED_FILE_LIKES: tuple[type, ...] = (gzip.GzipFile, BZ2File, IndexedGzipFile) # Enable .zst support if pyzstd installed. if HAVE_ZSTD: @@ -220,7 +221,7 @@ def value_set(self, name=None): # Endian code aliases -endian_codes = Recoder(endian_codes) +endian_codes = Recoder(_endian_codes) class DtypeMapper: diff --git a/nibabel/wrapstruct.py b/nibabel/wrapstruct.py index bf29e0828a..567c82e5ce 100644 --- a/nibabel/wrapstruct.py +++ b/nibabel/wrapstruct.py @@ -109,11 +109,13 @@ nib.imageglobals.logger = logger """ +from __future__ import annotations + import numpy as np from . import imageglobals as imageglobals from .batteryrunners import BatteryRunner -from .volumeutils import endian_codes, native_code, pretty_mapping, swapped_code +from .volumeutils import endian_codes, native_code, pretty_mapping, swapped_code, Recoder class WrapStructError(Exception): @@ -482,7 +484,7 @@ def _get_checks(klass): class LabeledWrapStruct(WrapStruct): """A WrapStruct with some fields having value labels for printing etc""" - _field_recoders = {} # for recoding values for str + _field_recoders : dict[str, Recoder] = {} # for recoding values for str def get_value_label(self, fieldname): """Returns label for coded field diff --git a/setup.cfg b/setup.cfg index 370ced2cbb..f63822b73f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -17,7 +17,7 @@ tag_prefix = parentdir_prefix = [mypy] -python_version = 3.7 +python_version = 3.8 exclude =(?x)( /tests )