Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master' into correct-bug-Ortho…
Browse files Browse the repository at this point in the history
…Slicer3D
  • Loading branch information
effigies committed Sep 5, 2024
2 parents 4f36bc7 + d15ec58 commit cdc788d
Show file tree
Hide file tree
Showing 37 changed files with 1,138 additions and 440 deletions.
13 changes: 11 additions & 2 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
[run]
branch = True
source = nibabel
include = */nibabel/*
omit =
*/externals/*
*/benchmarks/*
*/tests/*
nibabel/_version.py

[report]
exclude_also =
def __repr__
if (ty\.|typing\.)?TYPE_CHECKING:
class .*\((ty\.|typing\.)Protocol\):
@(ty\.|typing\.)overload
if 0:
if __name__ == .__main__.:
@(abc\.)?abstractmethod
raise NotImplementedError
13 changes: 10 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,9 @@ jobs:
- os: ubuntu-latest
python-version: 3.8
dependencies: 'min'
# NumPy 2.0
# NoGIL
- os: ubuntu-latest
python-version: '3.12'
python-version: '3.13-dev'
dependencies: 'dev'
exclude:
# x86 for Windows + Python<3.12
Expand Down Expand Up @@ -168,11 +168,18 @@ jobs:
submodules: recursive
fetch-depth: 0
- name: Set up Python ${{ matrix.python-version }}
if: "!endsWith(matrix.python-version, '-dev')"
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
architecture: ${{ matrix.architecture }}
allow-prereleases: true
- name: Set up Python ${{ matrix.python-version }}
if: endsWith(matrix.python-version, '-dev')
uses: deadsnakes/[email protected]
with:
python-version: ${{ matrix.python-version }}
nogil: true
- name: Display Python version
run: python -c "import sys; print(sys.version)"
- name: Install tox
Expand All @@ -182,7 +189,7 @@ jobs:
- name: Show tox config
run: tox c
- name: Run tox
run: tox -v --exit-and-dump-after 1200
run: tox -vv --exit-and-dump-after 1200
- uses: codecov/codecov-action@v4
if: ${{ always() }}
with:
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ repos:
rev: v0.3.4
hooks:
- id: ruff
args: [--fix, --show-fix, --exit-non-zero-on-fix]
args: [--fix, --show-fixes, --exit-non-zero-on-fix]
exclude: = ["doc", "tools"]
- id: ruff-format
exclude: = ["doc", "tools"]
Expand Down
27 changes: 27 additions & 0 deletions Changelog
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,33 @@ Eric Larson (EL), Demian Wassermann, Stephan Gerhard and Ross Markello (RM).

References like "pr/298" refer to github pull request numbers.

Upcoming release (To be determined)
===================================

New features
------------

Enhancements
------------
* Ability to read data from many multiframe DICOM files that previously generated errors

Bug fixes
---------
* Fixed multiframe DICOM issue where data could be flipped along slice dimension relative to the
affine
* Fixed multiframe DICOM issue where ``image_position`` and the translation component in the
``affine`` could be incorrect

Documentation
-------------

Maintenance
-----------

API changes and deprecations
----------------------------


5.2.1 (Monday 26 February 2024)
===============================

Expand Down
9 changes: 9 additions & 0 deletions doc/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
import tomli as tomllib

# Check for external Sphinx extensions we depend on
try:
import numpy as np
except ImportError:
raise RuntimeError('Need to install "numpy" package for doc build')
try:
import numpydoc
except ImportError:
Expand All @@ -45,6 +49,11 @@
'Need nibabel on Python PATH; consider "make htmldoc" from nibabel root directory'
)

from packaging.version import Version

if Version(np.__version__) >= Version('1.22'):
np.set_printoptions(legacy='1.21')

# -- General configuration ----------------------------------------------------

# We load the nibabel release info into a dict by explicit execution
Expand Down
4 changes: 3 additions & 1 deletion doc/tools/build_modref_templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import sys

# version comparison
from distutils.version import LooseVersion as V
from packaging.version import Version as V
from os.path import join as pjoin

# local imports
Expand Down Expand Up @@ -73,6 +73,8 @@ def abort(error):
if re.match('^_version_(major|minor|micro|extra)', v)
]
)

source_version = V(source_version)
print('***', source_version)

if source_version != installed_version:
Expand Down
2 changes: 1 addition & 1 deletion nibabel/_compression.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

from .optpkg import optional_package

if ty.TYPE_CHECKING: # pragma: no cover
if ty.TYPE_CHECKING:
import indexed_gzip # type: ignore[import]
import pyzstd

Expand Down
12 changes: 5 additions & 7 deletions nibabel/arrayproxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
KEEP_FILE_OPEN_DEFAULT = False


if ty.TYPE_CHECKING: # pragma: no cover
if ty.TYPE_CHECKING:
import numpy.typing as npt
from typing_extensions import Self # PY310

Expand All @@ -75,19 +75,17 @@ class ArrayLike(ty.Protocol):
shape: tuple[int, ...]

@property
def ndim(self) -> int: ... # pragma: no cover
def ndim(self) -> int: ...

# If no dtype is passed, any dtype might be returned, depending on the array-like
@ty.overload
def __array__(
self, dtype: None = ..., /
) -> np.ndarray[ty.Any, np.dtype[ty.Any]]: ... # pragma: no cover
def __array__(self, dtype: None = ..., /) -> np.ndarray[ty.Any, np.dtype[ty.Any]]: ...

# Any dtype might be passed, and *that* dtype must be returned
@ty.overload
def __array__(self, dtype: _DType, /) -> np.ndarray[ty.Any, _DType]: ... # pragma: no cover
def __array__(self, dtype: _DType, /) -> np.ndarray[ty.Any, _DType]: ...

def __getitem__(self, key, /) -> npt.NDArray: ... # pragma: no cover
def __getitem__(self, key, /) -> npt.NDArray: ...


class ArrayProxy(ArrayLike):
Expand Down
2 changes: 1 addition & 1 deletion nibabel/brikhead.py
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ def get_affine(self):
# AFNI default is RAI- == LPS+ == DICOM order. We need to flip RA sign
# to align with nibabel RAS+ system
affine = np.asarray(self.info['IJK_TO_DICOM_REAL']).reshape(3, 4)
affine = np.row_stack((affine * [[-1], [-1], [1]], [0, 0, 0, 1]))
affine = np.vstack((affine * [[-1], [-1], [1]], [0, 0, 0, 1]))
return affine

def get_data_scaling(self):
Expand Down
2 changes: 1 addition & 1 deletion nibabel/cmdline/dicomfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class dummy_fuse:
import nibabel as nib
import nibabel.dft as dft

encoding = locale.getdefaultlocale()[1]
encoding = locale.getlocale()[1]

fuse.fuse_python_api = (0, 2)

Expand Down
27 changes: 19 additions & 8 deletions nibabel/cmdline/nifti_dx.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
"""Print nifti diagnostics for header files"""

import sys
from optparse import OptionParser
from argparse import ArgumentParser

import nibabel as nib

Expand All @@ -21,15 +20,27 @@

def main(args=None):
"""Go go team"""
parser = OptionParser(
usage=f'{sys.argv[0]} [FILE ...]\n\n' + __doc__, version='%prog ' + nib.__version__
parser = ArgumentParser(description=__doc__)
parser.add_argument('--version', action='version', version=f'%(prog)s {nib.__version__}')
parser.add_argument(
'-1',
'--nifti1',
dest='header_class',
action='store_const',
const=nib.Nifti1Header,
default=nib.Nifti1Header,
)
(opts, files) = parser.parse_args(args=args)
parser.add_argument(
'-2', '--nifti2', dest='header_class', action='store_const', const=nib.Nifti2Header
)
parser.add_argument('files', nargs='*', metavar='FILE', help='Nifti file names')

args = parser.parse_args(args=args)

for fname in files:
for fname in args.files:
with nib.openers.ImageOpener(fname) as fobj:
hdr = fobj.read(nib.nifti1.header_dtype.itemsize)
result = nib.Nifti1Header.diagnose_binaryblock(hdr)
hdr = fobj.read(args.header_class.template_dtype.itemsize)
result = args.header_class.diagnose_binaryblock(hdr)
if len(result):
print(f'Picky header check output for "{fname}"\n')
print(result + '\n')
Expand Down
2 changes: 1 addition & 1 deletion nibabel/dataobj_images.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from .filebasedimages import FileBasedHeader, FileBasedImage
from .fileholders import FileMap

if ty.TYPE_CHECKING: # pragma: no cover
if ty.TYPE_CHECKING:
import numpy.typing as npt

from .filename_parser import FileSpec
Expand Down
2 changes: 1 addition & 1 deletion nibabel/deprecated.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from .deprecator import Deprecator
from .pkg_info import cmp_pkg_version

if ty.TYPE_CHECKING: # pragma: no cover
if ty.TYPE_CHECKING:
P = ty.ParamSpec('P')


Expand Down
2 changes: 1 addition & 1 deletion nibabel/deprecator.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import warnings
from textwrap import dedent

if ty.TYPE_CHECKING: # pragma: no cover
if ty.TYPE_CHECKING:
T = ty.TypeVar('T')
P = ty.ParamSpec('P')

Expand Down
2 changes: 1 addition & 1 deletion nibabel/ecat.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ def read_mlist(fileobj, endianness):
mlist_index += n_rows
if mlist_block_no <= 2: # should block_no in (1, 2) be an error?
break
return np.row_stack(mlists)
return np.vstack(mlists)


def get_frame_order(mlist):
Expand Down
14 changes: 7 additions & 7 deletions nibabel/filebasedimages.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from .filename_parser import TypesFilenamesError, _stringify_path, splitext_addext, types_filenames
from .openers import ImageOpener

if ty.TYPE_CHECKING: # pragma: no cover
if ty.TYPE_CHECKING:
from .filename_parser import ExtensionSpec, FileSpec

FileSniff = ty.Tuple[bytes, str]
Expand Down Expand Up @@ -54,13 +54,13 @@ def from_header(klass: type[HdrT], header: FileBasedHeader | ty.Mapping | None =

@classmethod
def from_fileobj(klass: type[HdrT], fileobj: io.IOBase) -> HdrT:
raise NotImplementedError # pragma: no cover
raise NotImplementedError

def write_to(self, fileobj: io.IOBase) -> None:
raise NotImplementedError # pragma: no cover
raise NotImplementedError

def __eq__(self, other: object) -> bool:
raise NotImplementedError # pragma: no cover
raise NotImplementedError

def __ne__(self, other: object) -> bool:
return not self == other
Expand Down Expand Up @@ -251,7 +251,7 @@ def from_filename(klass: type[ImgT], filename: FileSpec) -> ImgT:

@classmethod
def from_file_map(klass: type[ImgT], file_map: FileMap) -> ImgT:
raise NotImplementedError # pragma: no cover
raise NotImplementedError

@classmethod
def filespec_to_file_map(klass, filespec: FileSpec) -> FileMap:
Expand Down Expand Up @@ -308,7 +308,7 @@ def to_filename(self, filename: FileSpec, **kwargs) -> None:
self.to_file_map(**kwargs)

def to_file_map(self, file_map: FileMap | None = None, **kwargs) -> None:
raise NotImplementedError # pragma: no cover
raise NotImplementedError

@classmethod
def make_file_map(klass, mapping: ty.Mapping[str, str | io.IOBase] | None = None) -> FileMap:
Expand Down Expand Up @@ -373,7 +373,7 @@ def from_image(klass: type[ImgT], img: FileBasedImage) -> ImgT:
img : ``FileBasedImage`` instance
Image, of our own class
"""
raise NotImplementedError # pragma: no cover
raise NotImplementedError

@classmethod
def _sniff_meta_for(
Expand Down
2 changes: 1 addition & 1 deletion nibabel/filename_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import pathlib
import typing as ty

if ty.TYPE_CHECKING: # pragma: no cover
if ty.TYPE_CHECKING:
FileSpec = str | os.PathLike[str]
ExtensionSpec = tuple[str, str | None]

Expand Down
2 changes: 1 addition & 1 deletion nibabel/loadsave.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
_compressed_suffixes = ('.gz', '.bz2', '.zst')


if ty.TYPE_CHECKING: # pragma: no cover
if ty.TYPE_CHECKING:
from .filebasedimages import FileBasedImage
from .filename_parser import FileSpec

Expand Down
10 changes: 4 additions & 6 deletions nibabel/nicom/ascconv.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def assign2atoms(assign_ast, default_class=int):
prev_target_type = OrderedDict
elif isinstance(target, ast.Subscript):
if isinstance(target.slice, ast.Constant): # PY39
index = target.slice.n
index = target.slice.value
else: # PY38
index = target.slice.value.n
atoms.append(Atom(target, prev_target_type, index))
Expand Down Expand Up @@ -174,12 +174,10 @@ def obj_from_atoms(atoms, namespace):

def _get_value(assign):
value = assign.value
if isinstance(value, ast.Num):
return value.n
if isinstance(value, ast.Str):
return value.s
if isinstance(value, ast.Constant):
return value.value
if isinstance(value, ast.UnaryOp) and isinstance(value.op, ast.USub):
return -value.operand.n
return -value.operand.value
raise AscconvParseError(f'Unexpected RHS of assignment: {value}')


Expand Down
Loading

0 comments on commit cdc788d

Please sign in to comment.