From d4eede3e774212fa545f25008107e58c1fea54fd Mon Sep 17 00:00:00 2001 From: Dimitri Papadopoulos <3234522+DimitriPapadopoulos@users.noreply.github.com> Date: Wed, 9 Oct 2024 09:42:24 +0200 Subject: [PATCH 1/6] STY: Apply ruff rule RUF012 RUF012 Mutable class attributes should be annotated with `typing.ClassVar` Co-authored-by: Chris Markiewicz --- doc/tools/apigen.py | 2 +- nibabel/analyze.py | 5 ++++- nibabel/cifti2/cifti2.py | 8 -------- nibabel/nifti1.py | 2 +- nibabel/openers.py | 4 ++-- nibabel/wrapstruct.py | 4 +++- nibabel/xmlutils.py | 2 +- 7 files changed, 12 insertions(+), 15 deletions(-) diff --git a/doc/tools/apigen.py b/doc/tools/apigen.py index 336c81d8d..23fb6ff07 100644 --- a/doc/tools/apigen.py +++ b/doc/tools/apigen.py @@ -32,7 +32,7 @@ class ApiDocWriter: to Sphinx-parsable reST format""" # only separating first two levels - rst_section_levels = ['*', '=', '-', '~', '^'] + rst_section_levels = ('*', '=', '-', '~', '^') def __init__( self, diff --git a/nibabel/analyze.py b/nibabel/analyze.py index d02363c79..77ddbe30c 100644 --- a/nibabel/analyze.py +++ b/nibabel/analyze.py @@ -84,6 +84,8 @@ from __future__ import annotations +import typing as ty + import numpy as np from .arrayproxy import ArrayProxy @@ -92,6 +94,7 @@ from .fileholders import copy_file_map from .spatialimages import HeaderDataError, HeaderTypeError, SpatialHeader, SpatialImage from .volumeutils import ( + Recoder, apply_read_scaling, array_from_file, make_dt_codes, @@ -185,7 +188,7 @@ class AnalyzeHeader(LabeledWrapStruct, SpatialHeader): template_dtype = header_dtype _data_type_codes = data_type_codes # fields with recoders for their values - _field_recoders = {'datatype': data_type_codes} + _field_recoders: ty.ClassVar[dict[str, Recoder]] = {'datatype': data_type_codes} # default x flip default_x_flip = True diff --git a/nibabel/cifti2/cifti2.py b/nibabel/cifti2/cifti2.py index b2b67978b..3f3439ea4 100644 --- a/nibabel/cifti2/cifti2.py +++ b/nibabel/cifti2/cifti2.py @@ -1045,14 +1045,6 @@ class Cifti2MatrixIndicesMap(xml.XmlSerializable, MutableSequence): If it is a series, units """ - _valid_type_mappings_ = { - Cifti2BrainModel: ('CIFTI_INDEX_TYPE_BRAIN_MODELS',), - Cifti2Parcel: ('CIFTI_INDEX_TYPE_PARCELS',), - Cifti2NamedMap: ('CIFTI_INDEX_TYPE_LABELS',), - Cifti2Volume: ('CIFTI_INDEX_TYPE_SCALARS', 'CIFTI_INDEX_TYPE_SERIES'), - Cifti2Surface: ('CIFTI_INDEX_TYPE_SCALARS', 'CIFTI_INDEX_TYPE_SERIES'), - } - def __init__( self, applies_to_matrix_dimension, diff --git a/nibabel/nifti1.py b/nibabel/nifti1.py index f0bd91fc4..7cbe91d92 100644 --- a/nibabel/nifti1.py +++ b/nibabel/nifti1.py @@ -811,7 +811,7 @@ class Nifti1Header(SpmAnalyzeHeader): _data_type_codes = data_type_codes # fields with recoders for their values - _field_recoders = { + _field_recoders: ty.ClassVar[dict[str, Recoder]] = { 'datatype': data_type_codes, 'qform_code': xform_codes, 'sform_code': xform_codes, diff --git a/nibabel/openers.py b/nibabel/openers.py index 35b10c20a..e3217e189 100644 --- a/nibabel/openers.py +++ b/nibabel/openers.py @@ -131,7 +131,7 @@ class Opener: gz_def = (_gzip_open, ('mode', 'compresslevel', 'mtime', 'keep_open')) bz2_def = (BZ2File, ('mode', 'buffering', 'compresslevel')) zstd_def = (_zstd_open, ('mode', 'level_or_option', 'zstd_dict')) - compress_ext_map: dict[str | None, OpenerDef] = { + compress_ext_map: ty.ClassVar[dict[str | None, OpenerDef]] = { '.gz': gz_def, '.bz2': bz2_def, '.zst': zstd_def, @@ -141,7 +141,7 @@ class Opener: default_compresslevel = 1 #: default option for zst files default_zst_compresslevel = 3 - default_level_or_option = { + default_level_or_option: ty.ClassVar[dict[str, int | None]] = { 'rb': None, 'r': None, 'wb': default_zst_compresslevel, diff --git a/nibabel/wrapstruct.py b/nibabel/wrapstruct.py index 5ffe04bc7..77e1a5249 100644 --- a/nibabel/wrapstruct.py +++ b/nibabel/wrapstruct.py @@ -112,6 +112,8 @@ from __future__ import annotations +from typing import ClassVar + import numpy as np from . import imageglobals as imageglobals @@ -485,7 +487,7 @@ def _get_checks(klass): class LabeledWrapStruct(WrapStruct): """A WrapStruct with some fields having value labels for printing etc""" - _field_recoders: dict[str, Recoder] = {} # for recoding values for str + _field_recoders: ClassVar[dict[str, Recoder]] = {} # for recoding values for str def get_value_label(self, fieldname): """Returns label for coded field diff --git a/nibabel/xmlutils.py b/nibabel/xmlutils.py index 12fd30f22..878290399 100644 --- a/nibabel/xmlutils.py +++ b/nibabel/xmlutils.py @@ -49,7 +49,7 @@ class XmlParser: CharacterDataHandler """ - HANDLER_NAMES = ['StartElementHandler', 'EndElementHandler', 'CharacterDataHandler'] + HANDLER_NAMES = ('StartElementHandler', 'EndElementHandler', 'CharacterDataHandler') def __init__(self, encoding='utf-8', buffer_size=35000000, verbose=0): """ From be0f313e98449be34534ce23d89b614efb5782fc Mon Sep 17 00:00:00 2001 From: Dimitri Papadopoulos <3234522+DimitriPapadopoulos@users.noreply.github.com> Date: Tue, 8 Oct 2024 09:16:17 +0200 Subject: [PATCH 2/6] STY: Apply ruff rule RUF017 RUF017 Avoid quadratic list summation --- nibabel/tests/test_volumeutils.py | 6 +++++- pyproject.toml | 1 - 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/nibabel/tests/test_volumeutils.py b/nibabel/tests/test_volumeutils.py index 1bd44cbd0..4eb6b1d64 100644 --- a/nibabel/tests/test_volumeutils.py +++ b/nibabel/tests/test_volumeutils.py @@ -607,7 +607,11 @@ def test_a2f_nanpos(): def test_a2f_bad_scaling(): # Test that pathological scalers raise an error - NUMERICAL_TYPES = sum((sctypes[key] for key in ['int', 'uint', 'float', 'complex']), []) + NUMERICAL_TYPES = [ + x + for sublist in (sctypes[key] for key in ('int', 'uint', 'float', 'complex')) + for x in sublist + ] for in_type, out_type, slope, inter in itertools.product( NUMERICAL_TYPES, NUMERICAL_TYPES, diff --git a/pyproject.toml b/pyproject.toml index b62c0048a..32dd4199e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -162,7 +162,6 @@ ignore = [ "RUF005", "RUF012", # TODO: enable "RUF015", - "RUF017", # TODO: enable "UP027", # deprecated "UP038", # https://github.com/astral-sh/ruff/issues/7871 # https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules From 4764152fc6c3aea168c50eed71f4e3b236e66d13 Mon Sep 17 00:00:00 2001 From: Dimitri Papadopoulos <3234522+DimitriPapadopoulos@users.noreply.github.com> Date: Tue, 8 Oct 2024 08:43:24 +0200 Subject: [PATCH 3/6] STY: Apply ruff preview rule RUF021 RUF021 Parenthesize `a and b` expressions when chaining `and` and `or` together, to make the precedence clear Co-authored-by: Chris Markiewicz --- nibabel/cifti2/cifti2_axes.py | 4 ++-- nibabel/cmdline/dicomfs.py | 2 +- nibabel/tests/test_nifti1.py | 2 +- nibabel/volumeutils.py | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/nibabel/cifti2/cifti2_axes.py b/nibabel/cifti2/cifti2_axes.py index 32914be1b..0ee6dc055 100644 --- a/nibabel/cifti2/cifti2_axes.py +++ b/nibabel/cifti2/cifti2_axes.py @@ -634,8 +634,8 @@ def __eq__(self, other): return ( ( self.affine is None - or np.allclose(self.affine, other.affine) - and self.volume_shape == other.volume_shape + or (np.allclose(self.affine, other.affine) + and self.volume_shape == other.volume_shape) ) and self.nvertices == other.nvertices and np.array_equal(self.name, other.name) diff --git a/nibabel/cmdline/dicomfs.py b/nibabel/cmdline/dicomfs.py index 07aa51e2d..ae81940a1 100644 --- a/nibabel/cmdline/dicomfs.py +++ b/nibabel/cmdline/dicomfs.py @@ -231,7 +231,7 @@ def main(args=None): if opts.verbose: logger.addHandler(logging.StreamHandler(sys.stdout)) - logger.setLevel(opts.verbose > 1 and logging.DEBUG or logging.INFO) + logger.setLevel(logging.DEBUG if opts.verbose > 1 else logging.INFO) if len(files) != 2: sys.stderr.write(f'Please provide two arguments:\n{parser.usage}\n') diff --git a/nibabel/tests/test_nifti1.py b/nibabel/tests/test_nifti1.py index f0029681b..e7b226187 100644 --- a/nibabel/tests/test_nifti1.py +++ b/nibabel/tests/test_nifti1.py @@ -538,7 +538,7 @@ def test_slice_times(self): hdr.set_slice_duration(0.1) # We need a function to print out the Nones and floating point # values in a predictable way, for the tests below. - _stringer = lambda val: val is not None and f'{val:2.1f}' or None + _stringer = lambda val: (val is not None and f'{val:2.1f}') or None _print_me = lambda s: list(map(_stringer, s)) # The following examples are from the nifti1.h documentation. hdr['slice_code'] = slice_order_codes['sequential increasing'] diff --git a/nibabel/volumeutils.py b/nibabel/volumeutils.py index d0ebb46a7..67a3f9a58 100644 --- a/nibabel/volumeutils.py +++ b/nibabel/volumeutils.py @@ -35,8 +35,8 @@ DT = ty.TypeVar('DT', bound=np.generic) sys_is_le = sys.byteorder == 'little' -native_code = sys_is_le and '<' or '>' -swapped_code = sys_is_le and '>' or '<' +native_code = (sys_is_le and '<') or '>' +swapped_code = (sys_is_le and '>') or '<' _endian_codes = ( # numpy code, aliases ('<', 'little', 'l', 'le', 'L', 'LE'), From 21f33249335c1b4504dfd7a18350f2d071007d06 Mon Sep 17 00:00:00 2001 From: Dimitri Papadopoulos <3234522+DimitriPapadopoulos@users.noreply.github.com> Date: Tue, 8 Oct 2024 08:47:49 +0200 Subject: [PATCH 4/6] STY: Apply ruff preview rule RUF023 RUF023 `__slots__` is not sorted --- nibabel/pointset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nibabel/pointset.py b/nibabel/pointset.py index 889a8c70c..759a0b15e 100644 --- a/nibabel/pointset.py +++ b/nibabel/pointset.py @@ -178,7 +178,7 @@ def to_mask(self, shape=None) -> SpatialImage: class GridIndices: """Class for generating indices just-in-time""" - __slots__ = ('gridshape', 'dtype', 'shape') + __slots__ = ('dtype', 'gridshape', 'shape') ndim = 2 def __init__(self, shape, dtype=None): From c842d84248ae4d5021d2ed9366414093603e20e6 Mon Sep 17 00:00:00 2001 From: Dimitri Papadopoulos <3234522+DimitriPapadopoulos@users.noreply.github.com> Date: Tue, 8 Oct 2024 08:44:07 +0200 Subject: [PATCH 5/6] STY: Apply ruff preview rule RUF031 RUF031 Avoid parentheses for tuples in subscripts. --- nibabel/minc1.py | 2 +- nibabel/nicom/csareader.py | 2 +- nibabel/nicom/tests/test_csareader.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/nibabel/minc1.py b/nibabel/minc1.py index d0b9fd537..2d44736a2 100644 --- a/nibabel/minc1.py +++ b/nibabel/minc1.py @@ -80,7 +80,7 @@ def get_data_dtype(self): dtt = np.dtype(np.float64) else: signtype = self._image.signtype.decode('latin-1') - dtt = _dt_dict[(typecode, signtype)] + dtt = _dt_dict[typecode, signtype] return np.dtype(dtt).newbyteorder('>') def get_data_shape(self): diff --git a/nibabel/nicom/csareader.py b/nibabel/nicom/csareader.py index b98dae740..4982be834 100644 --- a/nibabel/nicom/csareader.py +++ b/nibabel/nicom/csareader.py @@ -63,7 +63,7 @@ def get_csa_header(dcm_data, csa_type='image'): return None element_no = section_start + element_offset try: - tag = dcm_data[(0x29, element_no)] + tag = dcm_data[0x29, element_no] except KeyError: # The element could be missing due to anonymization return None diff --git a/nibabel/nicom/tests/test_csareader.py b/nibabel/nicom/tests/test_csareader.py index f31f4a393..f43bf4ba2 100644 --- a/nibabel/nicom/tests/test_csareader.py +++ b/nibabel/nicom/tests/test_csareader.py @@ -35,7 +35,7 @@ def test_csa_header_read(): data2.add(element) assert csa.get_csa_header(data2, 'image') is None # Add back the marker - CSA works again - data2[(0x29, 0x10)] = DATA[(0x29, 0x10)] + data2[0x29, 0x10] = DATA[0x29, 0x10] assert csa.is_mosaic(csa.get_csa_header(data2, 'image')) From 50a7fb209aca4c6c9122e5e6cf2b8dd68e97151a Mon Sep 17 00:00:00 2001 From: Dimitri Papadopoulos Orfanos <3234522+DimitriPapadopoulos@users.noreply.github.com> Date: Wed, 9 Oct 2024 09:24:21 +0200 Subject: [PATCH 6/6] STY: Further simplification Co-authored-by: Chris Markiewicz --- nibabel/tests/test_nifti1.py | 3 +-- nibabel/tests/test_volumeutils.py | 6 +----- nibabel/volumeutils.py | 3 +-- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/nibabel/tests/test_nifti1.py b/nibabel/tests/test_nifti1.py index e7b226187..b54a336ea 100644 --- a/nibabel/tests/test_nifti1.py +++ b/nibabel/tests/test_nifti1.py @@ -538,8 +538,7 @@ def test_slice_times(self): hdr.set_slice_duration(0.1) # We need a function to print out the Nones and floating point # values in a predictable way, for the tests below. - _stringer = lambda val: (val is not None and f'{val:2.1f}') or None - _print_me = lambda s: list(map(_stringer, s)) + _print_me = lambda s: [val if val is None else f'{val:2.1f}' for val in s] # The following examples are from the nifti1.h documentation. hdr['slice_code'] = slice_order_codes['sequential increasing'] assert _print_me(hdr.get_slice_times()) == [ diff --git a/nibabel/tests/test_volumeutils.py b/nibabel/tests/test_volumeutils.py index 4eb6b1d64..86923cc9f 100644 --- a/nibabel/tests/test_volumeutils.py +++ b/nibabel/tests/test_volumeutils.py @@ -607,11 +607,7 @@ def test_a2f_nanpos(): def test_a2f_bad_scaling(): # Test that pathological scalers raise an error - NUMERICAL_TYPES = [ - x - for sublist in (sctypes[key] for key in ('int', 'uint', 'float', 'complex')) - for x in sublist - ] + NUMERICAL_TYPES = [tp for kind in ('int', 'uint', 'float', 'complex') for tp in sctypes[kind]] for in_type, out_type, slope, inter in itertools.product( NUMERICAL_TYPES, NUMERICAL_TYPES, diff --git a/nibabel/volumeutils.py b/nibabel/volumeutils.py index 67a3f9a58..992c16924 100644 --- a/nibabel/volumeutils.py +++ b/nibabel/volumeutils.py @@ -35,8 +35,7 @@ DT = ty.TypeVar('DT', bound=np.generic) sys_is_le = sys.byteorder == 'little' -native_code = (sys_is_le and '<') or '>' -swapped_code = (sys_is_le and '>') or '<' +native_code, swapped_code = ('<', '>') if sys_is_le else ('>', '<') _endian_codes = ( # numpy code, aliases ('<', 'little', 'l', 'le', 'L', 'LE'),