diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index f65ab1441f..0000000000 --- a/.coveragerc +++ /dev/null @@ -1,18 +0,0 @@ -[run] -branch = True -source = nibabel -omit = - */externals/* - */benchmarks/* - 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 diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs deleted file mode 100644 index 7769a5f080..0000000000 --- a/.git-blame-ignore-revs +++ /dev/null @@ -1,18 +0,0 @@ -# Sun Jan 12 12:22:13 2025 -0500 - markiewicz@stanford.edu - sty: ruff format [git-blame-ignore-rev] -40e41208a0f04063b3c4e373a65da1a2a6a275b5 -# Sun Jan 12 11:51:49 2025 -0500 - markiewicz@stanford.edu - STY: ruff format [git-blame-ignore-rev] -7e5d584910c67851dcfcd074ff307122689b61f5 -# Sun Jan 1 12:38:02 2023 -0500 - effigies@gmail.com - STY: Run pre-commit config on all files -d14c1cf282a9c3b19189f490f10c35f5739e24d1 -# Thu Dec 29 22:53:17 2022 -0500 - effigies@gmail.com - STY: Reduce array().astype() and similar constructs -bf298113da99079c9c7b5e1690e41879828cd472 -# Thu Dec 29 22:32:46 2022 -0500 - effigies@gmail.com - STY: pyupgrade --py37-plus -4481a4c2640bd4be6e9c468e550d01aae448ab99 -# Fri Dec 30 11:01:19 2022 -0500 - effigies@gmail.com - STY: Run vanilla blue -6b0ddd23b1da1df7ca9ae275673f82bfa20a754c -# Thu Dec 29 21:46:13 2022 -0500 - markiewicz@stanford.edu - STY: Manual, blue-compatible touchups -263fca9bf6d4ca314a5a322b4824d6f53d0589df -# Thu Dec 29 21:32:00 2022 -0500 - effigies@gmail.com - STY: isort -0ab2856cac4d4baae7ab3e2f6d58421db55d807f -# Thu Dec 29 21:30:29 2022 -0500 - effigies@gmail.com - STY: blue -1a8dd302ff85b1136c81d492509b80e7748339f0 diff --git a/.git_archival.txt b/.git_archival.txt deleted file mode 100644 index 62556c5202..0000000000 --- a/.git_archival.txt +++ /dev/null @@ -1,4 +0,0 @@ -node: $Format:%H$ -node-date: $Format:%cI$ -describe-name: $Format:%(describe:match=[0-9]*)$ -ref-names: $Format:%D$ diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 919c815795..0000000000 --- a/.gitattributes +++ /dev/null @@ -1,2 +0,0 @@ -.git_archival.txt export-subst -nibabel/pkg_info.py export-subst diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md deleted file mode 100644 index 1a1f6027ee..0000000000 --- a/.github/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,107 +0,0 @@ -# Community Guidelines - -Nibabel is a [NIPY](https://nipy.org) project, and we strive to adhere to the -[NIPY code of conduct](https://nipy.org/conduct.html), reproduced below. - -The NIPY community is a community of practice devoted to the use of the Python programming language -in the analysis of neuroimaging data. The following code of conduct is a guideline for our behavior -as we participate in this community. - -It is based on, and heavily inspired by a reading of the Python community code of conduct, the -Apache foundation code of conduct, the Debian code of conduct, and the Ten Principles of Burning -Man. - -## The code of conduct for the NIPY community - -The Neuroimaging in Python (NIPY) community is made up of members with a diverse set of skills, -personalities, background, and experiences. We welcome these differences because they are the -source of diverse ideas, solutions and decisions about our work. Decisions we make affect users, -colleagues, and through scientific results, the general public. We take these consequences -seriously when making decisions. When you are working with members of the community, we ask -you to follow these guidelines, which help steer our interactions and help keep NIPY a positive, -successful, and growing community. - -### A member of the NIPY community is: - -#### Open - -Members of the community are open to collaboration. Be it on the reuse of data, on the -implementation of methods, on finding technical solutions, establishing best practices, and -otherwise. We are accepting of all who wish to take part in our activities, fostering an -environment where anyone can participate and everyone can make a difference. - -#### Collaborative - -Our work will be used by other people, and in turn we will depend on the work of others. When we -make something for the benefit of others, we are willing to explain to others how it works, so that -they can build on the work to make it even better. We are willing to provide constructive criticism -on the work of others and accept criticism of our own work, as the experiences and skill sets of -other members contribute to the whole of our efforts. - -#### Inquisitive - -Nobody knows everything! Asking questions early avoids many problems later, so questions are -encouraged, though they may be directed to the appropriate forum. Those who are asked should be -responsive and helpful, within the context of our shared goal of improving neuroimaging practice. - -#### Considerate - -Members of the community are considerate of their peers. We are thoughtful when addressing the -efforts of others, keeping in mind that often-times the labor was completed simply for the good of -the community. We are attentive in our communications, whether in person or online, and we are -tactful when approaching differing views. - -#### Careful in the words we choose - -We value courtesy, kindness and inclusiveness in all our interactions. Therefore, we take -responsibility for our own speech. In particular, we avoid: - - * Personal insults. - * Violent threats or language directed against another person. - * Sexist, racist, or otherwise discriminatory jokes and language. - * Any form of sexual or violent material. - * Sharing private content, such as emails sent privately or non-publicly, or unlogged forums such - as IRC channel history. - * Excessive or unnecessary profanity. - * Repeated harassment of others. In general, if someone asks you to stop, then stop. - * Advocating for, or encouraging, any of the above behaviour. - -#### Concise - -Keep in mind that what you write once will be read by many others. Writing a short email means -people can understand the conversation as efficiently as possible. Even short emails should always -strive to be empathetic, welcoming, friendly and patient. When a long explanation is necessary, -consider adding a summary. - -Try to bring new ideas to a conversation, so that each message adds something unique to the -conversation. Keep in mind that, when using email, the rest of the thread still contains the other -messages with arguments that have already been made. - -Try to stay on topic, especially in discussions that are already fairly long and complex. - -#### Respectful - -Members of the community are respectful. We are respectful of others, their positions, their -skills, their commitments, and their efforts. We are respectful of the volunteer and professional -efforts that permeate the NIPY community. We are respectful of the processes set forth in the -community, and we work within them. When we disagree, we are courteous and kind in raising our -issues. - -## Incident Reporting - -We put great value on respectful, friendly and helpful communication. - -If you feel that any of our Nibabel communications lack respect, or are unfriendly or unhelpful, -please try the following steps: - -* If you feel able, please let the person who has sent the email or comment that you found it - disrespectful / unhelpful / unfriendly, and why; - -* If you don't feel able to do that, or that didn't work, please contact Chris Markiewicz directly - by email (), and he will do his best to resolve it. - If you don't feel comfortable contacting Chris, please email Matthew Brett - () instead. - -## Attribution - -The vast majority of the above was taken from the NIPY Code of Conduct. diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md deleted file mode 100644 index 81687ac149..0000000000 --- a/.github/CONTRIBUTING.md +++ /dev/null @@ -1,16 +0,0 @@ -# Contributing to NiBabel - -Welcome to the NiBabel repository! -We're excited you're here and want to contribute. - -Please see the [NiBabel Developer Guidelines][link_devguide] on our -on our [documentation website][link_docs]. - -These guidelines are designed to make it as easy as possible to get involved. -If you have any questions that aren't discussed in our documentation, or it's -difficult to find what you're looking for, please let us know by opening an -[issue][link_issues]! - -[link_docs]: https://nipy.org/nibabel -[link_devguide]: https://nipy.org/nibabel/devel/devguide.html -[link_issues]: https://github.com/poldracklab/fmriprep/issues diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 6c9e83fcbf..0000000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,10 +0,0 @@ -version: 2 -updates: - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "monthly" - groups: - actions-infrastructure: - patterns: - - "actions/*" diff --git a/.github/workflows/README b/.github/workflows/README deleted file mode 100644 index 9f6144ab6e..0000000000 --- a/.github/workflows/README +++ /dev/null @@ -1,5 +0,0 @@ -These files implement NiBabel CI for GitHub actions. -The testing logic is implemented in tools/ci/*.sh, and these files adapt -the generic logic to the details of GH. - -Each file should differ only by strategy, and env and steps should be identical. diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 416ce03f54..0000000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,272 +0,0 @@ -name: Build and test - -# This file tests the claimed support range of NiBabel including -# -# * Operating systems: Linux, Windows (x64 & x86), OSX -# * Dependencies: minimum requirements, optional requirements -# * Installation methods: setup.py, sdist, wheel, archive - -on: - push: - branches: - - master - - maint/* - tags: - - "*" - pull_request: - branches: - - master - - maint/* - schedule: - - cron: '0 0 * * 1' - # Allow job to be triggered manually from GitHub interface - workflow_dispatch: - -defaults: - run: - shell: bash - -# Force tox and pytest to use color -env: - FORCE_COLOR: true - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -permissions: - contents: read - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - uses: actions/setup-python@v5 - with: - python-version: 3 - - run: pip install --upgrade build twine - - name: Build sdist and wheel - run: python -m build - - run: twine check dist/* - - name: Build git archive - run: mkdir archive && git archive -v -o archive/nibabel-archive.tgz HEAD - - name: Upload sdist and wheel artifacts - uses: actions/upload-artifact@v4 - with: - name: dist - path: dist/ - - name: Upload git archive artifact - uses: actions/upload-artifact@v4 - with: - name: archive - path: archive/ - - test-package: - runs-on: ubuntu-latest - needs: [build] - strategy: - matrix: - package: ['wheel', 'sdist', 'archive'] - steps: - - name: Download sdist and wheel artifacts - if: matrix.package != 'archive' - uses: actions/download-artifact@v4 - with: - name: dist - path: dist/ - - name: Download git archive artifact - if: matrix.package == 'archive' - uses: actions/download-artifact@v4 - with: - name: archive - path: archive/ - - uses: actions/setup-python@v5 - with: - python-version: 3 - - name: Display Python version - run: python -c "import sys; print(sys.version)" - - name: Update pip - run: pip install --upgrade pip - - name: Install wheel - if: matrix.package == 'wheel' - run: pip install dist/nibabel-*.whl - - name: Install sdist - if: matrix.package == 'sdist' - run: pip install dist/nibabel-*.tar.gz - - name: Install archive - if: matrix.package == 'archive' - run: pip install archive/nibabel-archive.tgz - - run: python -c 'import nibabel; print(nibabel.__version__)' - - name: Install minimum test dependencies - run: pip install nibabel[test] - - name: Run tests - run: pytest --doctest-modules --doctest-plus -v --pyargs nibabel -n auto - - test: - # Check each OS, all supported Python, minimum versions and latest releases - runs-on: ${{ matrix.os }} - continue-on-error: ${{ matrix.dependencies == 'pre' }} - strategy: - fail-fast: false - matrix: - os: ['ubuntu-latest', 'windows-latest', 'macos-13', 'macos-latest'] - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.13t"] - architecture: ['x86', 'x64', 'arm64'] - dependencies: ['full', 'pre'] - include: - # Basic dependencies only - - os: ubuntu-latest - python-version: "3.9" - architecture: 'x64' - dependencies: 'none' - # Absolute minimum dependencies - - os: ubuntu-latest - python-version: "3.9" - architecture: 'x64' - dependencies: 'min' - exclude: - # Use ubuntu-latest to cover the whole range of Python. For Windows - # and OSX, checking oldest and newest should be sufficient. - - os: windows-latest - python-version: "3.10" - - os: windows-latest - python-version: "3.11" - - os: windows-latest - python-version: "3.12" - - os: macos-13 - python-version: "3.10" - - os: macos-13 - python-version: "3.11" - - os: macos-13 - python-version: "3.12" - - os: macos-latest - python-version: "3.10" - - os: macos-latest - python-version: "3.11" - - os: macos-latest - python-version: "3.12" - - ## Unavailable architectures - # x86 is available for Windows - - os: ubuntu-latest - architecture: x86 - - os: macos-latest - architecture: x86 - - os: macos-13 - architecture: x86 - # arm64 is available for macos-14+ - - os: ubuntu-latest - architecture: arm64 - - os: windows-latest - architecture: arm64 - - os: macos-13 - architecture: arm64 - # x64 is not available for macos-14+ - - os: macos-latest - architecture: x64 - - ## Reduced support - # Drop pre tests for macos-13 - - os: macos-13 - dependencies: pre - # Drop pre tests for SPEC-0-unsupported Python versions - - python-version: '3.9' - dependencies: pre - - python-version: '3.10' - dependencies: pre - - env: - DEPENDS: ${{ matrix.dependencies }} - ARCH: ${{ !contains(fromJSON('["none", "min"]'), matrix.dependencies) && matrix.architecture }} - - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - fetch-depth: 0 - - name: Install the latest version of uv - uses: astral-sh/setup-uv@v6 - - name: Set up Python ${{ matrix.python-version }} - if: "!endsWith(matrix.python-version, 't')" - 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, 't') - run: | - echo "UV_PYTHON=${IMPL}-${VERSION}-${OS%-*}-${ARCH}-${LIBC}" >> $GITHUB_ENV - source $GITHUB_ENV - uv python install $UV_PYTHON - env: - IMPL: cpython - VERSION: ${{ matrix.python-version }} - # uv expects linux|macos|windows, we can drop the -* but need to rename ubuntu - OS: ${{ matrix.os == 'ubuntu-latest' && 'linux' || matrix.os }} - # uv expects x86, x86_64, aarch64 (among others) - ARCH: ${{ matrix.architecture == 'x64' && 'x86_64' || - matrix.architecture == 'arm64' && 'aarch64' || - matrix.architecture }} - # windows and macos have no options, gnu is the only option for the archs - LIBC: ${{ matrix.os == 'ubuntu-latest' && 'gnu' || 'none' }} - - name: Display Python version - run: python -c "import sys; print(sys.version)" - - name: Install tox - run: | - uv tool install -v tox --with=git+https://github.com/effigies/tox-gh-actions@abiflags --with=tox-uv - - name: Show tox config - run: tox c - - name: Run tox - run: tox -vv --exit-and-dump-after 1200 - - uses: codecov/codecov-action@v5 - if: ${{ always() }} - with: - files: cov.xml - token: ${{ secrets.CODECOV_TOKEN }} - - name: Upload pytest test results - uses: actions/upload-artifact@v4 - with: - name: pytest-results-${{ matrix.os }}-${{ matrix.python-version }}-${{ matrix.dependencies }}-${{ matrix.architecture }} - path: test-results.xml - if: ${{ always() }} - - checks: - runs-on: 'ubuntu-latest' - continue-on-error: true - strategy: - matrix: - check: ['style', 'doctest', 'typecheck', 'spellcheck'] - - steps: - - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: 3 - - name: Display Python version - run: python -c "import sys; print(sys.version)" - - name: Show tox config - run: pipx run tox c - - name: Show tox config (this call) - run: pipx run tox c -e ${{ matrix.check }} - - name: Run check - run: pipx run tox -e ${{ matrix.check }} - - publish: - runs-on: ubuntu-latest - environment: "Package deployment" - needs: [test, test-package] - permissions: - # Required for trusted publishing - id-token: write - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') - steps: - - uses: actions/download-artifact@v4 - with: - name: dist - path: dist/ - - uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.gitignore b/.gitignore deleted file mode 100644 index e413527d13..0000000000 --- a/.gitignore +++ /dev/null @@ -1,84 +0,0 @@ -# See also nibabel/.gitignore for test data rules that -# are meant to make you think before you `git add --force` - -# Editor temporary/working/backup files # -######################################### -.#* -[#]*# -*~ -*$ -*.bak -*.diff -*.org -.project -*.rej -.settings/ -.*.sw[nop] -.sw[nop] -*.tmp -.project -.pydevproject -*.py.orig -.DS_Store -.idea/ - -# Not sure what the next one is for -*.kpf - -# Makefile target file markers -*-stamp - -# Compiled source # -################### -*.a -*.com -*.class -*.dll -*.exe -*.o -*.py[oc] -*.so - -# Python files # -################ -build/ -_build -MANIFEST -dist/ -*.egg-info/ -.shelf -.tox/ -.coverage* -cov.xml -test-results.xml -.ropeproject/ -htmlcov/ -.*_cache/ - -# Logs and databases # -###################### -*.log -*.sql -*.sqlite -*.sqlite3 - -# OS generated files # -###################### -.gdb_history -.DS_Store? -ehthumbs.db -Icon? -Thumbs.db - -# Things specific to this project # -################################### -doc/source/reference -doc/source/generated -venv/ -.buildbot.patch -.vscode -for_testing/ - -# Generated by setuptools_scm # -############################### -_version.py diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 20e97c2ebb..0000000000 --- a/.gitmodules +++ /dev/null @@ -1,24 +0,0 @@ -[submodule "nibabel-data/nitest-balls1"] - path = nibabel-data/nitest-balls1 - url = https://github.com/yarikoptic/nitest-balls1 -[submodule "nibabel-data/nitest-minc2"] - path = nibabel-data/nitest-minc2 - url = https://github.com/matthew-brett/nitest-minc2.git -[submodule "nipy-ecattest"] - path = nibabel-data/nipy-ecattest - url = https://github.com/effigies/nipy-ecattest -[submodule "nibabel-data/nitest-freesurfer"] - path = nibabel-data/nitest-freesurfer - url = https://bitbucket.org/nipy/nitest-freesurfer.git -[submodule "nibabel-data/parrec_oblique"] - path = nibabel-data/parrec_oblique - url = https://github.com/grlee77/parrec_oblique.git -[submodule "nibabel-data/nitest-cifti2"] - path = nibabel-data/nitest-cifti2 - url = https://github.com/demianw/nibabel-nitest-cifti2.git -[submodule "nibabel-data/nitest-dicom"] - path = nibabel-data/nitest-dicom - url = https://github.com/effigies/nitest-dicom -[submodule "nibabel-data/dcm_qa_xa30"] - path = nibabel-data/dcm_qa_xa30 - url = https://github.com/neurolabusc/dcm_qa_xa30.git diff --git a/.mailmap b/.mailmap deleted file mode 100644 index 43932c865b..0000000000 --- a/.mailmap +++ /dev/null @@ -1,88 +0,0 @@ -# Prevent git from showing duplicate names with commands like "git shortlog" -# See the manpage of git-shortlog for details. -# The syntax is: -# -# Good Name [[Bad Name] ] -# -# If multiple names are mapped to the good email, a line without any bad -# emails will consolidate these names. -# Likewise, any name mapped to a bad email will be converted to the good name. -# -# A contributor with three emails and inconsistent names could be mapped like this: -# -# Good Name -# Good Name -# Good Name -# -# If a contributor uses an email that is not unique to them, you will need their -# name. -# -# Good Name -# Good Name Good Name -# -# This file is up-to-date if the command git log --format="%aN <%aE>" | sort -u -# gives no duplicates. -Alexandre Gramfort -Anibal Sólon -Ariel Rokem -B. Nolan Nichols -Basile Pinsard -Basile Pinsard -Ben Cipollini -Benjamin C Darwin -Bertrand Thirion -Cameron Riddell <31414128+CRiddler@users.noreply.github.com> -Christian Haselgrove -Christopher J. Markiewicz -Christopher J. Markiewicz -Christopher J. Markiewicz -Cindee Madison -Demian Wassermann -Dimitri Papadopoulos Orfanos -Dimitri Papadopoulos Orfanos <3234522+DimitriPapadopoulos@users.noreply.github.com> -Eric Larson -Fabian Perez -Fernando Pérez-García -Félix C. Morency -Gael Varoquaux -Gregory R. Lee -Ian Nimmo-Smith -Jaakko Leppäkangas -Jacob Roberts -Jakub Kaczmarzyk -Jasper J.F. van den Bosch -Jean-Baptiste Poline -Jérôme Dockès -Jon Haitz Legarreta -Jonathan Daniel <36337649+jond01@users.noreply.github.com> -Kesshi Jordan -Kevin S. Hahn -Konstantinos Raktivan -Krish Subramaniam -Krzysztof J. Gorgolewski -Krzysztof J. Gorgolewski -Marc-Alexandre Côté -Mathias Goncalves -Mathias Goncalves -Mathieu Scheltienne -Matthew Cieslak -Michael Hanke -Michael Hanke -Michiel Cottaar -Michiel Cottaar -Ly Nguyen -Oliver P. Hinds -Or Duek -Oscar Esteban -Paul McCarthy -Paul McCarthy -Reinder Vos de Wael -Roberto Guidotti -Roberto Guidotti -Satrajit Ghosh -Serge Koudoro -Stephan Gerhard Stephan Gerhard -Thomas Roos -Venkateswara Reddy Reddam -Yaroslav O. Halchenko -Yaroslav O. Halchenko diff --git a/nibabel/cifti2/tests/__init__.py b/.nojekyll similarity index 100% rename from nibabel/cifti2/tests/__init__.py rename to .nojekyll diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index 2e6c466f99..0000000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,43 +0,0 @@ -exclude: ".*/data/.*" -repos: - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 - hooks: - - id: trailing-whitespace - - id: end-of-file-fixer - - id: check-yaml - - id: check-json - - id: check-toml - - id: check-added-large-files - - id: check-case-conflict - - id: check-merge-conflict - - id: check-vcs-permalinks - - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.9.6 - hooks: - - id: ruff - args: [ --fix ] - exclude: = ["doc", "tools"] - - id: ruff-format - exclude: = ["doc", "tools"] - - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.15.0 - hooks: - - id: mypy - # Sync with project.optional-dependencies.typing - additional_dependencies: - - pytest - - types-setuptools - - types-Pillow - - pydicom - - numpy - - pyzstd - - importlib_resources - args: ["nibabel"] - pass_filenames: false - - repo: https://github.com/codespell-project/codespell - rev: v2.4.1 - hooks: - - id: codespell - additional_dependencies: - - tomli diff --git a/.readthedocs.yaml b/.readthedocs.yaml deleted file mode 100644 index 1b2c531171..0000000000 --- a/.readthedocs.yaml +++ /dev/null @@ -1,21 +0,0 @@ -version: 2 - -build: - os: ubuntu-lts-latest - tools: - python: latest - jobs: - pre_create_environment: - - asdf plugin add uv - - asdf install uv latest - - asdf global uv latest - create_environment: - - uv venv $READTHEDOCS_VIRTUALENV_PATH - install: - # Use a cache dir in the same mount to halve the install time - - VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH uv pip install --cache-dir $READTHEDOCS_VIRTUALENV_PATH/../../uv_cache .[doc] - pre_build: - - ( cd doc; python tools/build_modref_templates.py nibabel source/reference False ) - -sphinx: - configuration: doc/source/conf.py diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 8f80973f4f..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,48 +0,0 @@ -# vim ft=yaml -# Multiple lines can be made a single "virtual line" because of how Travis -# munges each line before executing it to print out the exit status. It's okay -# for it to be on multiple physical lines, so long as you remember: - There -# can't be any leading "-"s - All newlines will be removed, so use ";"s - -os: linux -arch: arm64 -dist: focal -language: python -cache: pip - -env: - global: - # Note that DEPENDS, OPTIONAL_DEPENDS and EXTRA_PIP_FLAGS are - # indirect and (when non-empty) refer to variables in tools/ci/env.sh - - DEPENDS="REQUIREMENTS" - - OPTIONAL_DEPENDS="DEFAULT_OPT_DEPENDS" - - EXTRA_PIP_FLAGS="" - - INSTALL_TYPE="pip" - - CHECK_TYPE="test" - -python: - - 3.7 - - 3.8 - - 3.9 - - "3.10" - -# Set up virtual environment, build package, build from depends -before_install: - - travis_retry tools/ci/create_venv.sh - - source tools/ci/build_archive.sh - - travis_retry tools/ci/install_dependencies.sh - -# command to install dependencies -install: - - tools/ci/install.sh - -# command to run tests, e.g. python setup.py test -script: - - tools/ci/check.sh - -after_script: - - travis_retry python3 -m pip install codecov - - codecov - -notifications: - webhooks: http://nipy.bic.berkeley.edu:54856/travis diff --git a/.zenodo.json b/.zenodo.json deleted file mode 100644 index 250611d54d..0000000000 --- a/.zenodo.json +++ /dev/null @@ -1,412 +0,0 @@ -{ - "creators": [ - { - "affiliation": "School of Psychology, University of Birmingham, Birmingham, UK", - "name": "Brett, Matthew", - "orcid": "0000-0001-5500-2546" - }, - { - "affiliation": "Stanford University", - "name": "Markiewicz, Christopher J.", - "orcid": "0000-0002-6533-164X" - }, - { - "affiliation": "Otto-von-Guericke-University Magdeburg, Germany", - "name": "Hanke, Michael", - "orcid": "0000-0001-6398-6370" - }, - { - "affiliation": "Microsoft Research, Montr\u00e9al, Qu\u00e9bec, Canada", - "name": "C\u00f4t\u00e9, Marc-Alexandre", - "orcid": "0000-0002-5147-7859" - }, - { - "affiliation": "UC San Diego", - "name": "Cipollini, Ben", - "orcid": "0000-0002-7782-0790" - }, - { - "affiliation": "CEA", - "name": "Papadopoulos Orfanos, Dimitri", - "orcid": "0000-0002-1242-8990" - }, - { - "name": "McCarthy, Paul" - }, - { - "affiliation": "MIT", - "name": "Jarecka, Dorota", - "orcid": "0000-0001-8282-2988" - }, - { - "affiliation": "Center for Open Neuroscience, Dartmouth College", - "name": "Cheng, Christopher P.", - "orcid": "0000-0001-9112-9464" - }, - { - "affiliation": "University of Washington: Seattle, WA, United States", - "name": "Larson, Eric", - "orcid": "0000-0003-4782-5360" - }, - { - "affiliation": "Dartmouth College: Hanover, NH, United States", - "name": "Halchenko, Yaroslav O.", - "orcid": "0000-0003-3456-2493" - }, - { - "affiliation": "Wellcome Centre for Integrative Neuroimaging, University of Oxford, UK", - "name": "Cottaar, Michiel", - "orcid": "0000-0003-4679-7724" - }, - { - "affiliation": "MIT, HMS", - "name": "Ghosh, Satrajit", - "orcid": "0000-0002-5312-6729" - }, - { - "affiliation": "Athena EPI, Inria Sophia-Antipolis", - "name": "Wassermann, Demian", - "orcid": "0000-0001-5194-6056" - }, - { - "affiliation": "Institute of Neuroinformatics, ETH/University of Zurich", - "name": "Gerhard, Stephan", - "orcid": "0000-0003-4454-6171" - }, - { - "affiliation": "Deptartment of Radiology, University of Cincinnati College of Medicine, Cincinnati, OH", - "name": "Lee, Gregory R.", - "orcid": "0000-0001-8895-2740" - }, - { - "name": "Baratz, Zvi", - "orcid": "0000-0001-7159-1387" - }, - { - "name": "Moloney, Brendan" - }, - { - "name": "Wang, Hao-Ting", - "orcid": "0000-0003-4078-2038" - }, - { - "affiliation": "Harvard University - Psychology", - "name": "Kastman, Erik", - "orcid": "0000-0001-7221-9042" - }, - { - "affiliation": "MIT", - "name": "Kaczmarzyk, Jakub", - "orcid": "0000-0002-5544-7577" - }, - { - "affiliation": "Department of Computer Science, Aalto University, Espoo, Finland and Department of Neuroscience, Imaging and Clinical Sciences, University G. D'Annunzio, Chieti, Italy", - "name": "Guidotti, Roberto", - "orcid": "0000-0002-0807-6005" - }, - { - "name": "Daniel, Jonathan" - }, - { - "name": "Duek, Or" - }, - { - "affiliation": "The University of Washington eScience Institute", - "name": "Rokem, Ariel", - "orcid": "0000-0003-0679-1985" - }, - { - "affiliation": "Human Neuroscience Platform, Fondation Campus Biotech Geneva, Geneva, Switzerland", - "name": "Mathieu Scheltienne", - "orcid": "0000-0001-8316-7436" - }, - { - "name": "Madison, Cindee" - }, - { - "name": "S\u00f3lon, Anibal" - }, - { - "name": "Morency, F\u00e9lix C." - }, - { - "affiliation": "MIT", - "name": "Goncalves, Mathias", - "orcid": "0000-0002-7252-7771" - }, - { - "affiliation": "Montreal Neurological Institute and Hospital", - "name": "Markello, Ross", - "orcid": "0000-0003-1057-1336" - }, - { - "affiliation": "Department of Psychology, University of California Davis, CA, USA", - "name": "Riddell, Cameron", - "orcid": "0000-0001-8950-0375" - }, - { - "name": "Burns, Christopher" - }, - { - "affiliation": "Berkeley Institute for Data Science, UC Berkeley", - "name": "Millman, Jarrod", - "orcid": "0000-0002-5263-5070" - }, - { - "affiliation": "CNRS LTCI, Telecom ParisTech, Universit\u00e9 Paris-Saclay", - "name": "Gramfort, Alexandre", - "orcid": "0000-0001-9791-4404" - }, - { - "name": "Lepp\u00e4kangas, Jaakko" - }, - { - "name": "van den Bosch, Jasper J.F." - }, - { - "name": "Vincent, Robert D." - }, - { - "affiliation": "Center for Magnetic Resonance Research, University of Minnesota", - "name": "Braun, Henry", - "orcid": "0000-0001-7003-9822" - }, - { - "name": "Subramaniam, Krish" - }, - { - "name": "Van, Andrew" - }, - { - "affiliation": "Brigham and Women's Hospital, Mass General Brigham/Harvard Medical School", - "name": "Legarreta, Jon Haitz", - "orcid": "0000-0002-9661-1396" - }, - { - "affiliation": "Google", - "name": "Gorgolewski, Krzysztof J.", - "orcid": "0000-0003-3321-7583" - }, - { - "affiliation": "Rotman Research Institute, Baycrest Health Sciences, Toronto, ON, Canada", - "name": "Raamana, Pradeep Reddy", - "orcid": "0000-0003-4662-0558" - }, - { - "affiliation": "University of Geneva, Switzerland", - "name": "Klug, Julian", - "orcid": "0000-0002-4849-9811" - }, - { - "name": "Vos de Wael, Reinder" - }, - { - "affiliation": "SRI International", - "name": "Nichols, B. Nolan", - "orcid": "0000-0003-1099-3328" - }, - { - "name": "Baker, Eric M." - }, - { - "name": "Koudoro, Serge" - }, - { - "name": "Hayashi, Soichi" - }, - { - "name": "Pinsard, Basile" - }, - { - "name": "Haselgrove, Christian" - }, - { - "name": "Hymers, Mark" - }, - { - "affiliation": "Department of Psychology, Stanford University, CA, USA", - "name": "Esteban, Oscar", - "orcid": "0000-0001-8435-6191" - }, - { - "affiliation": "University College London", - "name": "P\u00e9rez-Garc\u00eda, Fernando", - "orcid": "0000-0001-9090-3024" - }, - { - "name": "Becq, Guillaume" - }, - { - "name": "Dock\u00e8s, J\u00e9r\u00f4me" - }, - { - "name": "Oosterhof, Nikolaas N." - }, - { - "name": "Amirbekian, Bago" - }, - { - "name": "Christian, Horea" - }, - { - "name": "Nimmo-Smith, Ian" - }, - { - "name": "Nguyen, Ly" - }, - { - "name": "Suter, Peter" - }, - { - "affiliation": "BrainSpec, Boston, MA", - "name": "Reddigari, Samir", - "orcid": "0000-0003-1472-5881" - }, - { - "name": "St-Jean, Samuel" - }, - { - "name": "Panfilov, Egor", - "orcid": "0000-0002-2500-6375" - }, - { - "name": "Garyfallidis, Eleftherios" - }, - { - "affiliation": "INRIA", - "name": "Varoquaux, Gael", - "orcid": "0000-0003-1076-5122" - }, - { - "affiliation": "Polytechnique Montr\u00e9al, Montr\u00e9al, CA", - "name": "Newton, Joshua", - "orcid": "0009-0005-6963-3812" - }, - { - "name": "Hahn, Kevin S." - }, - { - "affiliation": "Charite Universitatsmedizin Berlin, Germany", - "name": "Waller, Lea", - "orcid": "0000-0002-3239-6957" - }, - { - "name": "Hinds, Oliver P." - }, - { - "name": "Sandro" - }, - { - "name": "Fauber, Bennet" - }, - { - "name": "Dewey, Blake" - }, - { - "name": "Perez, Fabian" - }, - { - "name": "Roberts, Jacob" - }, - { - "affiliation": "McGill University", - "name": "Poline, Jean-Baptiste", - "orcid": "0000-0002-9794-749X" - }, - { - "affiliation": "University College London, London, UK", - "name": "Stutters, Jon", - "orcid": "0000-0002-9151-0844" - }, - { - "affiliation": "University of California, San Francisco", - "name": "Jordan, Kesshi", - "orcid": "0000-0001-6313-0580" - }, - { - "affiliation": "Department of Neuropsychiatry, University of Pennsylvania", - "name": "Cieslak, Matthew", - "orcid": "0000-0002-1931-4734" - }, - { - "name": "Moreno, Miguel Estevan" - }, - { - "name": "Hrn\u010diar, Tom\u00e1\u0161" - }, - { - "name": "Haenel, Valentin" - }, - { - "name": "Schwartz, Yannick" - }, - { - "affiliation": "Hospital for Sick Children", - "name": "Darwin, Benjamin C" - }, - { - "affiliation": "INRIA", - "name": "Thirion, Bertrand", - "orcid": "0000-0001-5018-7895" - }, - { - "name": "Gauthier, Carl" - }, - { - "name": "Solovey, Igor" - }, - { - "affiliation": "Athinoula A. Martinos Center for Biomedical Imaging, Charlestown, MA", - "name": "Gonzalez, Ivan", - "orcid": "0000-0002-6451-6909" - }, - { - "name": "Palasubramaniam, Jath" - }, - { - "name": "Lecher, Justin" - }, - { - "affiliation": "TIB \u2013 Leibniz Information Centre for Science and Technology and University Library, Hannover, Germany", - "name": "Leinweber, Katrin", - "orcid": "0000-0001-5135-5758" - }, - { - "affiliation": "National Technical University of Athens, Greece", - "name": "Raktivan, Konstantinos" - }, - { - "name": "Cal\u00e1bkov\u00e1, Mark\u00e9ta" - }, - { - "affiliation": "Friedrich-Alexander-Universit\u00e4t Erlangen-N\u00fcrnberg, Erlangen, Germany", - "name": "Fischer, Peter", - "orcid": "0000-0003-3242-9867" - }, - { - "name": "Gervais, Philippe" - }, - { - "name": "Gadde, Syam" - }, - { - "name": "Ballinger, Thomas" - }, - { - "name": "Roos, Thomas" - }, - { - "affiliation": "National Institute of Mental Health and Neuro-Sciences, India", - "name": "Reddam, Venkateswara Reddy", - "orcid": "0000-0001-6817-2966" - }, - { - "name": "freec84" - } - ], - "keywords": [ - "neuroimaging" - ], - "license": "mit-license", - "upload_type": "software" -} diff --git a/AUTHOR b/AUTHOR deleted file mode 100644 index 932dabddb4..0000000000 --- a/AUTHOR +++ /dev/null @@ -1,7 +0,0 @@ -Matthew Brett -Michael Hanke -Ben Cipollini -Marc-Alexandre Côté -Chris Markiewicz -Stephan Gerhard -Eric Larson diff --git a/Makefile b/Makefile deleted file mode 100644 index 689ad6a75f..0000000000 --- a/Makefile +++ /dev/null @@ -1,276 +0,0 @@ -COVERAGE_REPORT=coverage -HTML_DIR=build/html -LATEX_DIR=build/latex -WWW_DIR=build/website -DOCSRC_DIR=doc -PROJECT=nibabel -# -# The Python executable to be used -# -PYTHON ?= python -NOSETESTS = $(PYTHON) $(shell which nosetests) - -# -# Determine details on the Python/system -# - -PYVER := $(shell $(PYTHON) -V 2>&1 | cut -d ' ' -f 2,2 | cut -d '.' -f 1,2) -DISTUTILS_PLATFORM := \ - $(shell \ - $(PYTHON) -c "import distutils.util; print(distutils.util.get_platform())") - -# Helpers for version handling. -# Note: can't be ':='-ed since location of invocation might vary -DEBCHANGELOG_VERSION = $(shell dpkg-parsechangelog | egrep ^Version | cut -d ' ' -f 2,2 | cut -d '-' -f 1,1) -SETUPPY_VERSION = $(shell $(PYTHON) setup.py -V) -# -# Automatic development version -# -#yields: LastTagName_CommitsSinceThat_AbbrvHash -DEV_VERSION := $(shell git describe --abbrev=4 HEAD |sed -e 's/-/+/g' |cut -d '/' -f 2,2) - -# By default we are releasing with setup.py version -RELEASE_VERSION ?= $(SETUPPY_VERSION) - -# -# Building -# - -all: build - -build: - $(PYTHON) setup.py config --noisy - $(PYTHON) setup.py build - - -# -# Cleaning -# - -clean: - $(MAKE) -C doc clean - -rm -rf build - -rm *-stamp - -distclean: clean - -rm MANIFEST - -rm $(COVERAGE_REPORT) - @find . -name '*.py[co]' \ - -o -name '*.a' \ - -o -name '*,cover' \ - -o -name '.coverage' \ - -o -iname '*~' \ - -o -iname '*.kcache' \ - -o -iname '*.pstats' \ - -o -iname '*.prof' \ - -o -iname '#*#' | xargs -L10 rm -f - -rm -r dist - -rm build-stamp - -rm -r .tox -# -rm tests/data/*.hdr.* tests/data/*.img.* tests/data/something.nii \ -# tests/data/noise* tests/data/None.nii - - -# -# Little helpers -# - -$(WWW_DIR): - if [ ! -d $(WWW_DIR) ]; then mkdir -p $(WWW_DIR); fi - -.git-blame-ignore-revs: - git log --grep "\[git-blame-ignore-rev\]" --pretty=format:"# %ad - %ae - %s%n%H" \ - > .git-blame-ignore-revs - echo >> .git-blame-ignore-revs - -# -# Tests -# - -test: unittest testmanual - - -ut-%: build - @PYTHONPATH=.:$(PYTHONPATH) $(NOSETESTS) nibabel/tests/test_$*.py - - -unittest: build - @PYTHONPATH=.:$(PYTHONPATH) $(NOSETESTS) nibabel --with-doctest - -testmanual: build - @cd doc/source && PYTHONPATH=../..:$(PYTHONPATH) $(NOSETESTS) --with-doctest --doctest-extension=.rst . dicom - - -coverage: build - @PYTHONPATH=.:$(PYTHONPATH) $(NOSETESTS) --with-coverage --cover-package=nibabel - - -# -# Documentation -# - -htmldoc: build - cd $(DOCSRC_DIR) && PYTHONPATH=$(CURDIR):$(PYTHONPATH) $(MAKE) html - - -pdfdoc: build - cd $(DOCSRC_DIR) && PYTHONPATH=$(CURDIR):$(PYTHONPATH) $(MAKE) latex - cd $(LATEX_DIR) && $(MAKE) all-pdf - - -gitwash-update: build - cd $(DOCSRC_DIR) && PYTHONPATH=$(CURDIR):$(PYTHONPATH) $(MAKE) gitwash-update - -# -# Website -# - -html: html-stamp -html-stamp: $(WWW_DIR) htmldoc - cp -r $(HTML_DIR)/* $(WWW_DIR) - touch $@ - -pdf: pdf-stamp -pdf-stamp: $(WWW_DIR) pdfdoc - cp $(LATEX_DIR)/*.pdf $(WWW_DIR) - touch $@ - -website: website-stamp -website-stamp: $(WWW_DIR) html-stamp pdf-stamp - cp -r $(HTML_DIR)/* $(WWW_DIR) - touch $@ - -upload-html: html-stamp - ./tools/upload-gh-pages.sh $(WWW_DIR) $(PROJECT) - -# -# Sources -# - -pylint: distclean - # do distclean first to silence SWIG's sins - PYTHONPATH=.:$(PYTHONPATH) pylint --rcfile doc/misc/pylintrc nibabel - - -# -# Distributions -# - -# Check either everything was committed -check-nodirty: - # Need to run in clean tree. If fails: commit or clean first - [ "x$$(git diff)" = "x" ] -# || $(error "") - -check-debian: - # Need to run in a Debian packaging branch - [ -d debian ] - -check-debian-version: check-debian - # Does debian version correspond to setup.py version? - [ "$(DEBCHANGELOG_VERSION)" = "$(SETUPPY_VERSION)" ] - -embed-dev-version: check-nodirty - # change upstream version - sed -i -e "s/$(SETUPPY_VERSION)/$(DEV_VERSION)/g" setup.py nibabel/__init__.py - # change package name - sed -i -e "s/= 'nibabel',/= 'nibabel-snapshot',/g" setup.py - -deb-dev-autochangelog: check-debian - # removed -snapshot from pkg name for now - $(MAKE) check-debian-version || \ - dch --newversion $(DEV_VERSION)-1 --package nibabel-snapshot \ - --allow-lower-version "NiBabel development snapshot." - -deb-mergedev: - git merge --no-commit origin/dist/debian/dev - -orig-src: distclean distclean - # clean existing dist dir first to have a single source tarball to process - -rm -rf dist - # let python create the source tarball - $(PYTHON) setup.py sdist --formats=gztar - # rename to proper Debian orig source tarball and move upwards - # to keep it out of the Debian diff - tbname=$$(basename $$(ls -1 dist/*tar.gz)) ; ln -s $${tbname} ../nibabel-snapshot_$(DEV_VERSION).orig.tar.gz - mv dist/*tar.gz .. - # clean leftover - rm MANIFEST - -devel-src: check-nodirty - -rm -rf dist - git clone -l . dist/nibabel-snapshot - RELEASE_VERSION=$(DEV_VERSION) \ - $(MAKE) -C dist/nibabel-snapshot -f ../../Makefile embed-dev-version orig-src - mv dist/*tar.gz .. - rm -rf dist - -devel-dsc: check-nodirty - -rm -rf dist - git clone -l . dist/nibabel-snapshot - RELEASE_VERSION=$(DEV_VERSION) \ - $(MAKE) -C dist/nibabel-snapshot -f ../../Makefile embed-dev-version orig-src deb-mergedev deb-dev-autochangelog - # create the dsc -- NOT using deb-src since it would clean the hell first - cd dist && dpkg-source -i'\.(gbp.conf|git\.*)' -b nibabel-snapshot - mv dist/*.gz dist/*dsc .. - rm -rf dist - -# make Debian source package -# # DO NOT depend on orig-src here as it would generate a source tarball in a -# Debian branch and might miss patches! -deb-src: check-debian distclean - cd .. && dpkg-source -i'\.(gbp.conf|git\.*)' -b $(CURDIR) - - -bdist_rpm: - $(PYTHON) setup.py bdist_rpm \ - --doc-files "doc" \ - --packager "nibabel authors " - --vendor "nibabel authors " - - -# build MacOS installer -- depends on patched bdist_mpkg for Leopard -bdist_mpkg: - $(PYTHON) tools/mpkg_wrapper.py setup.py install - -sdist-venv: clean - rm -rf dist venv - unset PYTHONPATH && $(PYTHON) setup.py sdist --formats=zip - virtualenv --system-site-packages --python=$(PYTHON) venv - . venv/bin/activate && pip install --ignore-installed nose - mkdir venv/tmp - cd venv/tmp && unzip ../../dist/*.zip - . venv/bin/activate && cd venv/tmp/nibabel* && python setup.py install - unset PYTHONPATH && . venv/bin/activate && cd venv && pytest --doctest-modules --doctest-plus --pyargs nibabel - -source-release: distclean - $(PYTHON) -m compileall . - make distclean - $(PYTHON) setup.py sdist --formats=gztar,zip - -venv-tests: - # I use this for python2.5 because the sdist-tests target doesn't work - # (the tester routine uses a 2.6 feature) - make distclean - - rm -rf $(VIRTUAL_ENV)/lib/python$(PYVER)/site-packages/nibabel - $(PYTHON) setup.py install - cd .. && nosetests $(VIRTUAL_ENV)/lib/python$(PYVER)/site-packages/nibabel - -tox-fresh: - # tox tests with fresh-installed virtualenvs. Needs network. And - # pytox, obviously. - tox -c tox.ini - -tox-stale: - # tox tests with MB's already-installed virtualenvs (numpy and nose - # installed) - tox -e python25,python26,python27,python32,np-1.2.1 - -refresh-readme: - $(PYTHON) tools/refresh_readme.py - -rm-orig: - # Remove .orig temporary diff files generated by git - find . -name "*.orig" -print | grep -v "fsaverage" | xargs rm - -.PHONY: orig-src pylint all build .git-blame-ignore-revs diff --git a/Makefile.win b/Makefile.win deleted file mode 100644 index 00c15ea031..0000000000 --- a/Makefile.win +++ /dev/null @@ -1,27 +0,0 @@ -# Makefile NiBabel under Windows using a standard Python distribution - -installer: - # now the installer - python setup.py bdist_wininst - -# -# Cleaning -# - -clean: - -rmdir /S /Q - -del /S *.a *.o *.gch *.pyd - -# -# Testing -# - -unittest: - @set PYTHONPATH=$(CURDIR) & nosetests nibabel - - -# -# Trailer -# - -.PHONY: all diff --git a/README.rst b/README.rst deleted file mode 100644 index 2043c1d220..0000000000 --- a/README.rst +++ /dev/null @@ -1,171 +0,0 @@ -.. -*- rest -*- -.. vim:syntax=rst - -.. Use raw location to ensure image shows up on PyPI -.. image:: https://raw.githubusercontent.com/nipy/nibabel/master/doc/pics/logo.png - :target: https://nipy.org/nibabel - :alt: NiBabel logo - -.. list-table:: - :widths: 20 80 - :header-rows: 0 - - * - Code - - - .. image:: https://img.shields.io/pypi/pyversions/nibabel.svg - :target: https://pypi.python.org/pypi/nibabel/ - :alt: PyPI - Python Version - .. image:: https://img.shields.io/badge/code%20style-blue-blue.svg - :target: https://blue.readthedocs.io/en/latest/ - :alt: code style: blue - .. image:: https://img.shields.io/badge/imports-isort-1674b1 - :target: https://pycqa.github.io/isort/ - :alt: imports: isort - .. image:: https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white - :target: https://github.com/pre-commit/pre-commit - :alt: pre-commit - - * - Tests - - - .. image:: https://github.com/nipy/NiBabel/actions/workflows/stable.yml/badge.svg - :target: https://github.com/nipy/NiBabel/actions/workflows/stable.yml - :alt: stable tests - .. image:: https://codecov.io/gh/nipy/NiBabel/branch/master/graph/badge.svg - :target: https://codecov.io/gh/nipy/NiBabel - :alt: codecov badge - - * - PyPI - - - .. image:: https://img.shields.io/pypi/v/nibabel.svg - :target: https://pypi.python.org/pypi/nibabel/ - :alt: PyPI version - .. image:: https://img.shields.io/pypi/dm/nibabel.svg - :target: https://pypistats.org/packages/nibabel - :alt: PyPI - Downloads - - * - Packages - - - .. image:: https://img.shields.io/conda/vn/conda-forge/nibabel - :target: https://anaconda.org/conda-forge/nibabel - :alt: Conda package - .. image:: https://repology.org/badge/version-for-repo/debian_unstable/nibabel.svg?header=Debian%20Unstable - :target: https://repology.org/project/nibabel/versions - :alt: Debian Unstable package - .. image:: https://repology.org/badge/version-for-repo/aur/python:nibabel.svg?header=Arch%20%28%41%55%52%29 - :target: https://repology.org/project/python:nibabel/versions - :alt: Arch (AUR) - .. image:: https://repology.org/badge/version-for-repo/gentoo_ovl_science/nibabel.svg?header=Gentoo%20%28%3A%3Ascience%29 - :target: https://repology.org/project/nibabel/versions - :alt: Gentoo (::science) - .. image:: https://repology.org/badge/version-for-repo/nix_unstable/python:nibabel.svg?header=nixpkgs%20unstable - :target: https://repology.org/project/python:nibabel/versions - :alt: nixpkgs unstable - - * - License & DOI - - - .. image:: https://img.shields.io/pypi/l/nibabel.svg - :target: https://github.com/nipy/nibabel/blob/master/COPYING - :alt: License - .. image:: https://zenodo.org/badge/DOI/10.5281/zenodo.591597.svg - :target: https://doi.org/10.5281/zenodo.591597 - :alt: Zenodo DOI - -.. Following contents should be copied from LONG_DESCRIPTION in nibabel/info.py - - -Read and write access to common neuroimaging file formats, including: -ANALYZE_ (plain, SPM99, SPM2 and later), GIFTI_, NIfTI1_, NIfTI2_, `CIFTI-2`_, -MINC1_, MINC2_, `AFNI BRIK/HEAD`_, ECAT_ and Philips PAR/REC. -In addition, NiBabel also supports FreeSurfer_'s MGH_, geometry, annotation and -morphometry files, and provides some limited support for DICOM_. - -NiBabel's API gives full or selective access to header information (metadata), -and image data is made available via NumPy arrays. For more information, see -NiBabel's `documentation site`_ and `API reference`_. - -.. _API reference: https://nipy.org/nibabel/api.html -.. _AFNI BRIK/HEAD: https://afni.nimh.nih.gov/pub/dist/src/README.attributes -.. _ANALYZE: http://www.grahamwideman.com/gw/brain/analyze/formatdoc.htm -.. _CIFTI-2: https://www.nitrc.org/projects/cifti/ -.. _DICOM: http://medical.nema.org/ -.. _documentation site: http://nipy.org/nibabel -.. _ECAT: http://xmedcon.sourceforge.net/Docs/Ecat -.. _Freesurfer: https://surfer.nmr.mgh.harvard.edu -.. _GIFTI: https://www.nitrc.org/projects/gifti -.. _MGH: https://surfer.nmr.mgh.harvard.edu/fswiki/FsTutorial/MghFormat -.. _MINC1: - https://en.wikibooks.org/wiki/MINC/Reference/MINC1_File_Format_Reference -.. _MINC2: - https://en.wikibooks.org/wiki/MINC/Reference/MINC2.0_File_Format_Reference -.. _NIfTI1: http://nifti.nimh.nih.gov/nifti-1/ -.. _NIfTI2: http://nifti.nimh.nih.gov/nifti-2/ - -Installation -============ - -To install NiBabel's `current release`_ with ``pip``, run:: - - pip install nibabel - -To install the latest development version, run:: - - pip install git+https://github.com/nipy/nibabel - -When working on NiBabel itself, it may be useful to install in "editable" mode:: - - git clone https://github.com/nipy/nibabel.git - pip install -e ./nibabel - -For more information on previous releases, see the `release archive`_ or -`development changelog`_. - -.. _current release: https://pypi.python.org/pypi/NiBabel -.. _release archive: https://github.com/nipy/NiBabel/releases -.. _development changelog: https://nipy.org/nibabel/changelog.html - -Testing -======= - -During development, we recommend using tox_ to run nibabel tests:: - - git clone https://github.com/nipy/nibabel.git - cd nibabel - tox - -To test an installed version of nibabel, install the test dependencies -and run pytest_:: - - pip install nibabel[test] - pytest --pyargs nibabel - -For more information, consult the `developer guidelines`_. - -.. _tox: https://tox.wiki -.. _pytest: https://docs.pytest.org -.. _developer guidelines: https://nipy.org/nibabel/devel/devguide.html - -Mailing List -============ - -Please send any questions or suggestions to the `neuroimaging mailing list -`_. - -License -======= - -NiBabel is licensed under the terms of the `MIT license -`__. -Some code included with NiBabel is licensed under the `BSD license`_. -For more information, please see the COPYING_ file. - -.. _BSD license: https://opensource.org/licenses/BSD-3-Clause -.. _COPYING: https://github.com/nipy/nibabel/blob/master/COPYING - -Citation -======== - -NiBabel releases have a Zenodo_ `Digital Object Identifier`_ (DOI) badge at -the top of the release notes. Click on the badge for more information. - -.. _Digital Object Identifier: https://en.wikipedia.org/wiki/Digital_object_identifier -.. _zenodo: https://zenodo.org diff --git a/TODO b/TODO deleted file mode 100644 index 71a7416acd..0000000000 --- a/TODO +++ /dev/null @@ -1,17 +0,0 @@ -.. -*- rest -*- -.. vim:syntax=rst - -Stephan's TODO --------------- -* testing for endianness correctly (e.g. while writing)? - -Matthew's TODO --------------- - -* Move file_map to {filename, fileobj, (filename, offset), (fileobj, offset) - with suitable change warning -* consider deprecating data_dtype methods for images. -* new data package model -* output_space property for images -* dirty attribute for images -* consider adding io_implementation to images to do I/O diff --git a/doc/source/notebooks/cross_product_error.ipynb b/_downloads/01d26ddfe66a9569b4cf79881141dece/cross_product_error.ipynb similarity index 100% rename from doc/source/notebooks/cross_product_error.ipynb rename to _downloads/01d26ddfe66a9569b4cf79881141dece/cross_product_error.ipynb diff --git a/_downloads/15ee5f982d34b67854edc69ebd5e3772/neuro_radio_conventions-2_01.hires.png b/_downloads/15ee5f982d34b67854edc69ebd5e3772/neuro_radio_conventions-2_01.hires.png new file mode 100644 index 0000000000..c1476f539e Binary files /dev/null and b/_downloads/15ee5f982d34b67854edc69ebd5e3772/neuro_radio_conventions-2_01.hires.png differ diff --git a/_downloads/2174665309d4572bf9425c62abdb3e82/coordinate_systems-3_01.hires.png b/_downloads/2174665309d4572bf9425c62abdb3e82/coordinate_systems-3_01.hires.png new file mode 100644 index 0000000000..c1476f539e Binary files /dev/null and b/_downloads/2174665309d4572bf9425c62abdb3e82/coordinate_systems-3_01.hires.png differ diff --git a/_downloads/5b2b00af981a01f3cc5e81b34ccc8fdb/neuro_radio_conventions-2_00.png b/_downloads/5b2b00af981a01f3cc5e81b34ccc8fdb/neuro_radio_conventions-2_00.png new file mode 100644 index 0000000000..f36e814ba9 Binary files /dev/null and b/_downloads/5b2b00af981a01f3cc5e81b34ccc8fdb/neuro_radio_conventions-2_00.png differ diff --git a/_downloads/6be0680a63be0340a581f954a2ec30b9/neuro_radio_conventions-2_01.png b/_downloads/6be0680a63be0340a581f954a2ec30b9/neuro_radio_conventions-2_01.png new file mode 100644 index 0000000000..d7ac71097c Binary files /dev/null and b/_downloads/6be0680a63be0340a581f954a2ec30b9/neuro_radio_conventions-2_01.png differ diff --git a/_downloads/79cc4a050731c618885fe142bc9dfcaf/neuro_radio_conventions-2_00.pdf b/_downloads/79cc4a050731c618885fe142bc9dfcaf/neuro_radio_conventions-2_00.pdf new file mode 100644 index 0000000000..3f559255aa Binary files /dev/null and b/_downloads/79cc4a050731c618885fe142bc9dfcaf/neuro_radio_conventions-2_00.pdf differ diff --git a/doc/source/notebooks/ata_error.ipynb b/_downloads/83473e2bf68165d7691882d14e07c1fe/ata_error.ipynb similarity index 100% rename from doc/source/notebooks/ata_error.ipynb rename to _downloads/83473e2bf68165d7691882d14e07c1fe/ata_error.ipynb diff --git a/_downloads/9e0340951323f17d19212a1b25b97064/neuro_radio_conventions-2_00.hires.png b/_downloads/9e0340951323f17d19212a1b25b97064/neuro_radio_conventions-2_00.hires.png new file mode 100644 index 0000000000..5f186232ed Binary files /dev/null and b/_downloads/9e0340951323f17d19212a1b25b97064/neuro_radio_conventions-2_00.hires.png differ diff --git a/doc/source/dicom/derivations/spm_dicom_orient.py b/_downloads/a0359552c75a1df40c301397f03c7556/spm_dicom_orient.py similarity index 100% rename from doc/source/dicom/derivations/spm_dicom_orient.py rename to _downloads/a0359552c75a1df40c301397f03c7556/spm_dicom_orient.py diff --git a/_downloads/a8f112aa4c254d4394f4e73b75e10047/coordinate_systems-2.pdf b/_downloads/a8f112aa4c254d4394f4e73b75e10047/coordinate_systems-2.pdf new file mode 100644 index 0000000000..e612477b65 Binary files /dev/null and b/_downloads/a8f112aa4c254d4394f4e73b75e10047/coordinate_systems-2.pdf differ diff --git a/_downloads/ae8bde362c4868fea3617fb37893ca61/neuro_radio_conventions-2_01.pdf b/_downloads/ae8bde362c4868fea3617fb37893ca61/neuro_radio_conventions-2_01.pdf new file mode 100644 index 0000000000..1c72867fe3 Binary files /dev/null and b/_downloads/ae8bde362c4868fea3617fb37893ca61/neuro_radio_conventions-2_01.pdf differ diff --git a/_downloads/b5b25fc5e75398a75b3dee5d03dee056/coordinate_systems-2.png b/_downloads/b5b25fc5e75398a75b3dee5d03dee056/coordinate_systems-2.png new file mode 100644 index 0000000000..f36e814ba9 Binary files /dev/null and b/_downloads/b5b25fc5e75398a75b3dee5d03dee056/coordinate_systems-2.png differ diff --git a/_downloads/bb31c24d40cb6a247054fb507ce53e36/coordinate_systems-2.hires.png b/_downloads/bb31c24d40cb6a247054fb507ce53e36/coordinate_systems-2.hires.png new file mode 100644 index 0000000000..5f186232ed Binary files /dev/null and b/_downloads/bb31c24d40cb6a247054fb507ce53e36/coordinate_systems-2.hires.png differ diff --git a/doc/source/downloads/someones_anatomy.nii.gz b/_downloads/c16214e490de2a223655d30f4ba78f15/someones_anatomy.nii.gz similarity index 100% rename from doc/source/downloads/someones_anatomy.nii.gz rename to _downloads/c16214e490de2a223655d30f4ba78f15/someones_anatomy.nii.gz diff --git a/_downloads/ce40033060c439c27eb234896af2b1e6/coordinate_systems-3_01.png b/_downloads/ce40033060c439c27eb234896af2b1e6/coordinate_systems-3_01.png new file mode 100644 index 0000000000..d7ac71097c Binary files /dev/null and b/_downloads/ce40033060c439c27eb234896af2b1e6/coordinate_systems-3_01.png differ diff --git a/_downloads/dc0e6623b8086a1f47ed26af34e7c6ba/coordinate_systems-3_00.hires.png b/_downloads/dc0e6623b8086a1f47ed26af34e7c6ba/coordinate_systems-3_00.hires.png new file mode 100644 index 0000000000..5f186232ed Binary files /dev/null and b/_downloads/dc0e6623b8086a1f47ed26af34e7c6ba/coordinate_systems-3_00.hires.png differ diff --git a/_downloads/e6b45cf94b78c71339be16615ab48886/coordinate_systems-3_00.pdf b/_downloads/e6b45cf94b78c71339be16615ab48886/coordinate_systems-3_00.pdf new file mode 100644 index 0000000000..e612477b65 Binary files /dev/null and b/_downloads/e6b45cf94b78c71339be16615ab48886/coordinate_systems-3_00.pdf differ diff --git a/_downloads/ea07875cb182b05530c1caaa47b509a8/coordinate_systems-3_01.pdf b/_downloads/ea07875cb182b05530c1caaa47b509a8/coordinate_systems-3_01.pdf new file mode 100644 index 0000000000..6b394c0cc2 Binary files /dev/null and b/_downloads/ea07875cb182b05530c1caaa47b509a8/coordinate_systems-3_01.pdf differ diff --git a/_downloads/f421d045ed7d77d5be5b5aa28544d030/coordinate_systems-3_00.png b/_downloads/f421d045ed7d77d5be5b5aa28544d030/coordinate_systems-3_00.png new file mode 100644 index 0000000000..f36e814ba9 Binary files /dev/null and b/_downloads/f421d045ed7d77d5be5b5aa28544d030/coordinate_systems-3_00.png differ diff --git a/doc/source/downloads/someones_epi.nii.gz b/_downloads/f76cc5a46e5368e2c779868abc49e497/someones_epi.nii.gz similarity index 100% rename from doc/source/downloads/someones_epi.nii.gz rename to _downloads/f76cc5a46e5368e2c779868abc49e497/someones_epi.nii.gz diff --git a/doc/source/devel/biaps/biap_flowchart.png b/_images/biap_flowchart.png similarity index 100% rename from doc/source/devel/biaps/biap_flowchart.png rename to _images/biap_flowchart.png diff --git a/doc/source/gitwash/branch_dropdown.png b/_images/branch_dropdown.png similarity index 100% rename from doc/source/gitwash/branch_dropdown.png rename to _images/branch_dropdown.png diff --git a/_images/coordinate_systems-2.png b/_images/coordinate_systems-2.png new file mode 100644 index 0000000000..f36e814ba9 Binary files /dev/null and b/_images/coordinate_systems-2.png differ diff --git a/_images/coordinate_systems-3_00.png b/_images/coordinate_systems-3_00.png new file mode 100644 index 0000000000..f36e814ba9 Binary files /dev/null and b/_images/coordinate_systems-3_00.png differ diff --git a/_images/coordinate_systems-3_01.png b/_images/coordinate_systems-3_01.png new file mode 100644 index 0000000000..d7ac71097c Binary files /dev/null and b/_images/coordinate_systems-3_01.png differ diff --git a/doc/source/gitwash/forking_button.png b/_images/forking_button.png similarity index 100% rename from doc/source/gitwash/forking_button.png rename to _images/forking_button.png diff --git a/doc/source/images/illustrating_affine.png b/_images/illustrating_affine.png similarity index 100% rename from doc/source/images/illustrating_affine.png rename to _images/illustrating_affine.png diff --git a/doc/source/images/localizer.png b/_images/localizer.png similarity index 100% rename from doc/source/images/localizer.png rename to _images/localizer.png diff --git a/doc/source/dicom/mosaic_grid.png b/_images/mosaic_grid.png similarity index 100% rename from doc/source/dicom/mosaic_grid.png rename to _images/mosaic_grid.png diff --git a/_images/neuro_radio_conventions-2_00.png b/_images/neuro_radio_conventions-2_00.png new file mode 100644 index 0000000000..f36e814ba9 Binary files /dev/null and b/_images/neuro_radio_conventions-2_00.png differ diff --git a/_images/neuro_radio_conventions-2_01.png b/_images/neuro_radio_conventions-2_01.png new file mode 100644 index 0000000000..d7ac71097c Binary files /dev/null and b/_images/neuro_radio_conventions-2_01.png differ diff --git a/doc/source/gitwash/pull_button.png b/_images/pull_button.png similarity index 100% rename from doc/source/gitwash/pull_button.png rename to _images/pull_button.png diff --git a/doc/source/images/rorden_radio_neuro.jpg b/_images/rorden_radio_neuro.jpg similarity index 100% rename from doc/source/images/rorden_radio_neuro.jpg rename to _images/rorden_radio_neuro.jpg diff --git a/doc/source/api.rst b/_sources/api.rst.txt similarity index 100% rename from doc/source/api.rst rename to _sources/api.rst.txt diff --git a/Changelog b/_sources/changelog.rst.txt similarity index 98% rename from Changelog rename to _sources/changelog.rst.txt index f75ac8bc29..f72a6a8874 100644 --- a/Changelog +++ b/_sources/changelog.rst.txt @@ -25,29 +25,6 @@ Eric Larson (EL), Demian Wassermann, Stephan Gerhard and Ross Markello (RM). References like "pr/298" refer to github pull request numbers. -5.3.2 (Wednesday 23 October 2024) -================================= - -Bug-fix release in the 5.3.x series. - -Bug fixes ---------- -* Restore MRS extension type to Nifti1Extension to maintain backwards compatibility. - (pr/1380) (CM) - - -5.3.1 (Tuesday 15 October 2024) -=============================== - -Bug-fix release in the 5.3.x series. - -Bug fixes ---------- -* Restore access to private attribute ``Nifti1Extension._content`` to unbreak subclasses - that did not use public accessor methods. (pr/1378) (CM, reviewed by Basile Pinsard) -* Remove test order dependency in ``test_api_validators`` (pr/1377) (CM) - - 5.3.0 (Tuesday 8 October 2024) ============================== @@ -57,9 +34,9 @@ NiBabel 6.0 will drop support for Numpy 1.x. New features ------------ -* Update NIfTI extension protocol to include ``.content : bytes``, ``.text : str`` and - ``.json() : dict`` properties/methods for accessing extension contents. - Exceptions will be raised on ``.text`` and ``.json()`` if conversion fails. (pr/1336) (CM) +* Update NIfTI extension protocol to include ``.content : bytes``, ``.text : str`` and ``.json : dict`` + properties for accessing extension contents. Exceptions will be raised on ``.text`` and ``.json`` if + conversion fails. (pr/1336) (CM) Enhancements ------------ diff --git a/doc/source/coordinate_systems.rst b/_sources/coordinate_systems.rst.txt similarity index 100% rename from doc/source/coordinate_systems.rst rename to _sources/coordinate_systems.rst.txt diff --git a/doc/source/devel/add_image_format.rst b/_sources/devel/add_image_format.rst.txt similarity index 100% rename from doc/source/devel/add_image_format.rst rename to _sources/devel/add_image_format.rst.txt diff --git a/doc/source/devel/add_test_data.rst b/_sources/devel/add_test_data.rst.txt similarity index 100% rename from doc/source/devel/add_test_data.rst rename to _sources/devel/add_test_data.rst.txt diff --git a/doc/source/devel/advanced_testing.rst b/_sources/devel/advanced_testing.rst.txt similarity index 100% rename from doc/source/devel/advanced_testing.rst rename to _sources/devel/advanced_testing.rst.txt diff --git a/doc/source/devel/biaps/biap_0000.rst b/_sources/devel/biaps/biap_0000.rst.txt similarity index 100% rename from doc/source/devel/biaps/biap_0000.rst rename to _sources/devel/biaps/biap_0000.rst.txt diff --git a/doc/source/devel/biaps/biap_0001.rst b/_sources/devel/biaps/biap_0001.rst.txt similarity index 100% rename from doc/source/devel/biaps/biap_0001.rst rename to _sources/devel/biaps/biap_0001.rst.txt diff --git a/doc/source/devel/biaps/biap_0002.rst b/_sources/devel/biaps/biap_0002.rst.txt similarity index 100% rename from doc/source/devel/biaps/biap_0002.rst rename to _sources/devel/biaps/biap_0002.rst.txt diff --git a/doc/source/devel/biaps/biap_0003.rst b/_sources/devel/biaps/biap_0003.rst.txt similarity index 100% rename from doc/source/devel/biaps/biap_0003.rst rename to _sources/devel/biaps/biap_0003.rst.txt diff --git a/doc/source/devel/biaps/biap_0004.rst b/_sources/devel/biaps/biap_0004.rst.txt similarity index 100% rename from doc/source/devel/biaps/biap_0004.rst rename to _sources/devel/biaps/biap_0004.rst.txt diff --git a/doc/source/devel/biaps/biap_0005.rst b/_sources/devel/biaps/biap_0005.rst.txt similarity index 100% rename from doc/source/devel/biaps/biap_0005.rst rename to _sources/devel/biaps/biap_0005.rst.txt diff --git a/doc/source/devel/biaps/biap_0006.rst b/_sources/devel/biaps/biap_0006.rst.txt similarity index 100% rename from doc/source/devel/biaps/biap_0006.rst rename to _sources/devel/biaps/biap_0006.rst.txt diff --git a/doc/source/devel/biaps/biap_0007.rst b/_sources/devel/biaps/biap_0007.rst.txt similarity index 100% rename from doc/source/devel/biaps/biap_0007.rst rename to _sources/devel/biaps/biap_0007.rst.txt diff --git a/doc/source/devel/biaps/biap_0008.rst b/_sources/devel/biaps/biap_0008.rst.txt similarity index 100% rename from doc/source/devel/biaps/biap_0008.rst rename to _sources/devel/biaps/biap_0008.rst.txt diff --git a/doc/source/devel/biaps/biap_0009.rst b/_sources/devel/biaps/biap_0009.rst.txt similarity index 100% rename from doc/source/devel/biaps/biap_0009.rst rename to _sources/devel/biaps/biap_0009.rst.txt diff --git a/doc/source/devel/biaps/biap_template.rst b/_sources/devel/biaps/biap_template.rst.txt similarity index 100% rename from doc/source/devel/biaps/biap_template.rst rename to _sources/devel/biaps/biap_template.rst.txt diff --git a/doc/source/devel/biaps/index.rst b/_sources/devel/biaps/index.rst.txt similarity index 100% rename from doc/source/devel/biaps/index.rst rename to _sources/devel/biaps/index.rst.txt diff --git a/doc/source/devel/bv_formats.rst b/_sources/devel/bv_formats.rst.txt similarity index 100% rename from doc/source/devel/bv_formats.rst rename to _sources/devel/bv_formats.rst.txt diff --git a/doc/source/devel/core_developer.rst b/_sources/devel/core_developer.rst.txt similarity index 100% rename from doc/source/devel/core_developer.rst rename to _sources/devel/core_developer.rst.txt diff --git a/doc/source/devel/data_pkg_discuss.rst b/_sources/devel/data_pkg_discuss.rst.txt similarity index 100% rename from doc/source/devel/data_pkg_discuss.rst rename to _sources/devel/data_pkg_discuss.rst.txt diff --git a/doc/source/devel/devdiscuss.rst b/_sources/devel/devdiscuss.rst.txt similarity index 100% rename from doc/source/devel/devdiscuss.rst rename to _sources/devel/devdiscuss.rst.txt diff --git a/doc/source/devel/devguide.rst b/_sources/devel/devguide.rst.txt similarity index 100% rename from doc/source/devel/devguide.rst rename to _sources/devel/devguide.rst.txt diff --git a/doc/source/devel/governance.rst b/_sources/devel/governance.rst.txt similarity index 100% rename from doc/source/devel/governance.rst rename to _sources/devel/governance.rst.txt diff --git a/doc/source/devel/image_design.rst b/_sources/devel/image_design.rst.txt similarity index 100% rename from doc/source/devel/image_design.rst rename to _sources/devel/image_design.rst.txt diff --git a/doc/source/devel/index.rst b/_sources/devel/index.rst.txt similarity index 100% rename from doc/source/devel/index.rst rename to _sources/devel/index.rst.txt diff --git a/doc/source/devel/make_release.rst b/_sources/devel/make_release.rst.txt similarity index 100% rename from doc/source/devel/make_release.rst rename to _sources/devel/make_release.rst.txt diff --git a/doc/source/devel/modified_images.rst b/_sources/devel/modified_images.rst.txt similarity index 100% rename from doc/source/devel/modified_images.rst rename to _sources/devel/modified_images.rst.txt diff --git a/doc/source/devel/roadmap.rst b/_sources/devel/roadmap.rst.txt similarity index 100% rename from doc/source/devel/roadmap.rst rename to _sources/devel/roadmap.rst.txt diff --git a/doc/source/devel/scaling.rst b/_sources/devel/scaling.rst.txt similarity index 100% rename from doc/source/devel/scaling.rst rename to _sources/devel/scaling.rst.txt diff --git a/doc/source/devel/spm_use.rst b/_sources/devel/spm_use.rst.txt similarity index 100% rename from doc/source/devel/spm_use.rst rename to _sources/devel/spm_use.rst.txt diff --git a/doc/source/dicom/dcm2nii_algorithms.rst b/_sources/dicom/dcm2nii_algorithms.rst.txt similarity index 100% rename from doc/source/dicom/dcm2nii_algorithms.rst rename to _sources/dicom/dcm2nii_algorithms.rst.txt diff --git a/doc/source/dicom/dicom.rst b/_sources/dicom/dicom.rst.txt similarity index 100% rename from doc/source/dicom/dicom.rst rename to _sources/dicom/dicom.rst.txt diff --git a/doc/source/dicom/dicom_fields.rst b/_sources/dicom/dicom_fields.rst.txt similarity index 100% rename from doc/source/dicom/dicom_fields.rst rename to _sources/dicom/dicom_fields.rst.txt diff --git a/doc/source/dicom/dicom_info.rst b/_sources/dicom/dicom_info.rst.txt similarity index 100% rename from doc/source/dicom/dicom_info.rst rename to _sources/dicom/dicom_info.rst.txt diff --git a/doc/source/dicom/dicom_intro.rst b/_sources/dicom/dicom_intro.rst.txt similarity index 100% rename from doc/source/dicom/dicom_intro.rst rename to _sources/dicom/dicom_intro.rst.txt diff --git a/doc/source/dicom/dicom_mosaic.rst b/_sources/dicom/dicom_mosaic.rst.txt similarity index 100% rename from doc/source/dicom/dicom_mosaic.rst rename to _sources/dicom/dicom_mosaic.rst.txt diff --git a/doc/source/dicom/dicom_niftiheader.rst b/_sources/dicom/dicom_niftiheader.rst.txt similarity index 100% rename from doc/source/dicom/dicom_niftiheader.rst rename to _sources/dicom/dicom_niftiheader.rst.txt diff --git a/doc/source/dicom/dicom_orientation.rst b/_sources/dicom/dicom_orientation.rst.txt similarity index 100% rename from doc/source/dicom/dicom_orientation.rst rename to _sources/dicom/dicom_orientation.rst.txt diff --git a/doc/source/dicom/siemens_csa.rst b/_sources/dicom/siemens_csa.rst.txt similarity index 100% rename from doc/source/dicom/siemens_csa.rst rename to _sources/dicom/siemens_csa.rst.txt diff --git a/doc/source/dicom/spm_dicom.rst b/_sources/dicom/spm_dicom.rst.txt similarity index 100% rename from doc/source/dicom/spm_dicom.rst rename to _sources/dicom/spm_dicom.rst.txt diff --git a/doc/source/gettingstarted.rst b/_sources/gettingstarted.rst.txt similarity index 100% rename from doc/source/gettingstarted.rst rename to _sources/gettingstarted.rst.txt diff --git a/doc/source/gitwash/configure_git.rst b/_sources/gitwash/configure_git.rst.txt similarity index 100% rename from doc/source/gitwash/configure_git.rst rename to _sources/gitwash/configure_git.rst.txt diff --git a/doc/source/gitwash/development_workflow.rst b/_sources/gitwash/development_workflow.rst.txt similarity index 100% rename from doc/source/gitwash/development_workflow.rst rename to _sources/gitwash/development_workflow.rst.txt diff --git a/doc/source/gitwash/following_latest.rst b/_sources/gitwash/following_latest.rst.txt similarity index 100% rename from doc/source/gitwash/following_latest.rst rename to _sources/gitwash/following_latest.rst.txt diff --git a/doc/source/gitwash/forking_hell.rst b/_sources/gitwash/forking_hell.rst.txt similarity index 100% rename from doc/source/gitwash/forking_hell.rst rename to _sources/gitwash/forking_hell.rst.txt diff --git a/doc/source/gitwash/git_development.rst b/_sources/gitwash/git_development.rst.txt similarity index 100% rename from doc/source/gitwash/git_development.rst rename to _sources/gitwash/git_development.rst.txt diff --git a/doc/source/gitwash/git_install.rst b/_sources/gitwash/git_install.rst.txt similarity index 100% rename from doc/source/gitwash/git_install.rst rename to _sources/gitwash/git_install.rst.txt diff --git a/doc/source/gitwash/git_intro.rst b/_sources/gitwash/git_intro.rst.txt similarity index 100% rename from doc/source/gitwash/git_intro.rst rename to _sources/gitwash/git_intro.rst.txt diff --git a/doc/source/gitwash/git_resources.rst b/_sources/gitwash/git_resources.rst.txt similarity index 100% rename from doc/source/gitwash/git_resources.rst rename to _sources/gitwash/git_resources.rst.txt diff --git a/doc/source/gitwash/index.rst b/_sources/gitwash/index.rst.txt similarity index 100% rename from doc/source/gitwash/index.rst rename to _sources/gitwash/index.rst.txt diff --git a/doc/source/gitwash/maintainer_workflow.rst b/_sources/gitwash/maintainer_workflow.rst.txt similarity index 100% rename from doc/source/gitwash/maintainer_workflow.rst rename to _sources/gitwash/maintainer_workflow.rst.txt diff --git a/doc/source/gitwash/patching.rst b/_sources/gitwash/patching.rst.txt similarity index 100% rename from doc/source/gitwash/patching.rst rename to _sources/gitwash/patching.rst.txt diff --git a/doc/source/gitwash/set_up_fork.rst b/_sources/gitwash/set_up_fork.rst.txt similarity index 100% rename from doc/source/gitwash/set_up_fork.rst rename to _sources/gitwash/set_up_fork.rst.txt diff --git a/doc/source/image_orientation.rst b/_sources/image_orientation.rst.txt similarity index 100% rename from doc/source/image_orientation.rst rename to _sources/image_orientation.rst.txt diff --git a/doc/source/images_and_memory.rst b/_sources/images_and_memory.rst.txt similarity index 100% rename from doc/source/images_and_memory.rst rename to _sources/images_and_memory.rst.txt diff --git a/doc/source/index.rst b/_sources/index.rst.txt similarity index 100% rename from doc/source/index.rst rename to _sources/index.rst.txt diff --git a/doc/source/installation.rst b/_sources/installation.rst.txt similarity index 100% rename from doc/source/installation.rst rename to _sources/installation.rst.txt diff --git a/COPYING b/_sources/legal.rst.txt similarity index 100% rename from COPYING rename to _sources/legal.rst.txt diff --git a/doc/source/manual.rst b/_sources/manual.rst.txt similarity index 100% rename from doc/source/manual.rst rename to _sources/manual.rst.txt diff --git a/doc/source/neuro_radio_conventions.rst b/_sources/neuro_radio_conventions.rst.txt similarity index 100% rename from doc/source/neuro_radio_conventions.rst rename to _sources/neuro_radio_conventions.rst.txt diff --git a/doc/source/nibabel_images.rst b/_sources/nibabel_images.rst.txt similarity index 100% rename from doc/source/nibabel_images.rst rename to _sources/nibabel_images.rst.txt diff --git a/doc/source/nifti_images.rst b/_sources/nifti_images.rst.txt similarity index 100% rename from doc/source/nifti_images.rst rename to _sources/nifti_images.rst.txt diff --git a/doc/source/notebooks/index.rst b/_sources/notebooks/index.rst.txt similarity index 100% rename from doc/source/notebooks/index.rst rename to _sources/notebooks/index.rst.txt diff --git a/doc/source/old/ioimplementation.rst b/_sources/old/ioimplementation.rst.txt similarity index 100% rename from doc/source/old/ioimplementation.rst rename to _sources/old/ioimplementation.rst.txt diff --git a/_sources/reference/index.rst.txt b/_sources/reference/index.rst.txt new file mode 100644 index 0000000000..f33bc46ba8 --- /dev/null +++ b/_sources/reference/index.rst.txt @@ -0,0 +1,67 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +API Reference +============= + +.. toctree:: + + nibabel.rst + nibabel._compression.rst + nibabel.affines.rst + nibabel.analyze.rst + nibabel.arrayproxy.rst + nibabel.arraywriters.rst + nibabel.batteryrunners.rst + nibabel.benchmarks.rst + nibabel.brikhead.rst + nibabel.caret.rst + nibabel.casting.rst + nibabel.cifti2.rst + nibabel.cmdline.rst + nibabel.data.rst + nibabel.dataobj_images.rst + nibabel.deprecated.rst + nibabel.deprecator.rst + nibabel.dft.rst + nibabel.ecat.rst + nibabel.environment.rst + nibabel.eulerangles.rst + nibabel.filebasedimages.rst + nibabel.fileholders.rst + nibabel.filename_parser.rst + nibabel.fileslice.rst + nibabel.fileutils.rst + nibabel.freesurfer.rst + nibabel.funcs.rst + nibabel.gifti.rst + nibabel.imageclasses.rst + nibabel.imageglobals.rst + nibabel.imagestats.rst + nibabel.loadsave.rst + nibabel.minc1.rst + nibabel.minc2.rst + nibabel.mriutils.rst + nibabel.nicom.rst + nibabel.nifti1.rst + nibabel.nifti2.rst + nibabel.onetime.rst + nibabel.openers.rst + nibabel.optpkg.rst + nibabel.orientations.rst + nibabel.parrec.rst + nibabel.pointset.rst + nibabel.processing.rst + nibabel.pydicom_compat.rst + nibabel.quaternions.rst + nibabel.rstutils.rst + nibabel.spaces.rst + nibabel.spatialimages.rst + nibabel.spm2analyze.rst + nibabel.spm99analyze.rst + nibabel.streamlines.rst + nibabel.tmpdirs.rst + nibabel.tripwire.rst + nibabel.viewers.rst + nibabel.volumeutils.rst + nibabel.wrapstruct.rst + nibabel.xmlutils.rst diff --git a/_sources/reference/nibabel._compression.rst.txt b/_sources/reference/nibabel._compression.rst.txt new file mode 100644 index 0000000000..9f5e27d051 --- /dev/null +++ b/_sources/reference/nibabel._compression.rst.txt @@ -0,0 +1,13 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`_compression` +=================== +.. automodule:: nibabel._compression + +.. currentmodule:: nibabel._compression +.. autosummary:: + + + +.. currentmodule:: nibabel._compression + diff --git a/_sources/reference/nibabel.affines.rst.txt b/_sources/reference/nibabel.affines.rst.txt new file mode 100644 index 0000000000..938f3bc0a0 --- /dev/null +++ b/_sources/reference/nibabel.affines.rst.txt @@ -0,0 +1,74 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`affines` +============== +.. automodule:: nibabel.affines + +.. currentmodule:: nibabel.affines +.. autosummary:: + + AffineError + append_diag + apply_affine + dot_reduce + from_matvec + obliquity + rescale_affine + to_matvec + voxel_sizes + + +.. currentmodule:: nibabel.affines + + +:class:`AffineError` +~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: AffineError + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + +append_diag +~~~~~~~~~~~ + +.. autofunction:: append_diag + +apply_affine +~~~~~~~~~~~~ + +.. autofunction:: apply_affine + +dot_reduce +~~~~~~~~~~ + +.. autofunction:: dot_reduce + +from_matvec +~~~~~~~~~~~ + +.. autofunction:: from_matvec + +obliquity +~~~~~~~~~ + +.. autofunction:: obliquity + +rescale_affine +~~~~~~~~~~~~~~ + +.. autofunction:: rescale_affine + +to_matvec +~~~~~~~~~ + +.. autofunction:: to_matvec + +voxel_sizes +~~~~~~~~~~~ + +.. autofunction:: voxel_sizes + diff --git a/_sources/reference/nibabel.analyze.rst.txt b/_sources/reference/nibabel.analyze.rst.txt new file mode 100644 index 0000000000..3668e1fc49 --- /dev/null +++ b/_sources/reference/nibabel.analyze.rst.txt @@ -0,0 +1,39 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`analyze` +============== +.. automodule:: nibabel.analyze + +.. currentmodule:: nibabel.analyze +.. autosummary:: + + AnalyzeHeader + AnalyzeImage + + +.. currentmodule:: nibabel.analyze + + +:class:`AnalyzeHeader` +~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: AnalyzeHeader + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`AnalyzeImage` +~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: AnalyzeImage + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + diff --git a/_sources/reference/nibabel.arrayproxy.rst.txt b/_sources/reference/nibabel.arrayproxy.rst.txt new file mode 100644 index 0000000000..0a0d9302e1 --- /dev/null +++ b/_sources/reference/nibabel.arrayproxy.rst.txt @@ -0,0 +1,57 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`arrayproxy` +================= +.. automodule:: nibabel.arrayproxy + +.. currentmodule:: nibabel.arrayproxy +.. autosummary:: + + ArrayLike + ArrayProxy + get_obj_dtype + is_proxy + reshape_dataobj + + +.. currentmodule:: nibabel.arrayproxy + + +:class:`ArrayLike` +~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: ArrayLike + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`ArrayProxy` +~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: ArrayProxy + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + +get_obj_dtype +~~~~~~~~~~~~~ + +.. autofunction:: get_obj_dtype + +is_proxy +~~~~~~~~ + +.. autofunction:: is_proxy + +reshape_dataobj +~~~~~~~~~~~~~~~ + +.. autofunction:: reshape_dataobj + diff --git a/_sources/reference/nibabel.arraywriters.rst.txt b/_sources/reference/nibabel.arraywriters.rst.txt new file mode 100644 index 0000000000..2955ce2225 --- /dev/null +++ b/_sources/reference/nibabel.arraywriters.rst.txt @@ -0,0 +1,90 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`arraywriters` +=================== +.. automodule:: nibabel.arraywriters + +.. currentmodule:: nibabel.arraywriters +.. autosummary:: + + ArrayWriter + ScalingError + SlopeArrayWriter + SlopeInterArrayWriter + WriterError + get_slope_inter + make_array_writer + + +.. currentmodule:: nibabel.arraywriters + + +:class:`ArrayWriter` +~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: ArrayWriter + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`ScalingError` +~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: ScalingError + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`SlopeArrayWriter` +~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: SlopeArrayWriter + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`SlopeInterArrayWriter` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: SlopeInterArrayWriter + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`WriterError` +~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: WriterError + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + +get_slope_inter +~~~~~~~~~~~~~~~ + +.. autofunction:: get_slope_inter + +make_array_writer +~~~~~~~~~~~~~~~~~ + +.. autofunction:: make_array_writer + diff --git a/_sources/reference/nibabel.batteryrunners.rst.txt b/_sources/reference/nibabel.batteryrunners.rst.txt new file mode 100644 index 0000000000..238ffca2da --- /dev/null +++ b/_sources/reference/nibabel.batteryrunners.rst.txt @@ -0,0 +1,39 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`batteryrunners` +===================== +.. automodule:: nibabel.batteryrunners + +.. currentmodule:: nibabel.batteryrunners +.. autosummary:: + + BatteryRunner + Report + + +.. currentmodule:: nibabel.batteryrunners + + +:class:`BatteryRunner` +~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: BatteryRunner + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`Report` +~~~~~~~~~~~~~~~ + + +.. autoclass:: Report + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + diff --git a/_sources/reference/nibabel.benchmarks.rst.txt b/_sources/reference/nibabel.benchmarks.rst.txt new file mode 100644 index 0000000000..9b6a516f68 --- /dev/null +++ b/_sources/reference/nibabel.benchmarks.rst.txt @@ -0,0 +1,133 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`benchmarks` +================= +.. automodule:: nibabel.benchmarks + +.. currentmodule:: nibabel.benchmarks +.. autosummary:: + + +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +Module: :mod:`benchmarks.bench_array_to_file` +--------------------------------------------- +.. automodule:: nibabel.benchmarks.bench_array_to_file + +.. currentmodule:: nibabel.benchmarks.bench_array_to_file +.. autosummary:: + + bench_array_to_file + +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +Module: :mod:`benchmarks.bench_arrayproxy_slicing` +-------------------------------------------------- +.. automodule:: nibabel.benchmarks.bench_arrayproxy_slicing + +.. currentmodule:: nibabel.benchmarks.bench_arrayproxy_slicing +.. autosummary:: + + bench_arrayproxy_slicing + +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +Module: :mod:`benchmarks.bench_fileslice` +----------------------------------------- +.. automodule:: nibabel.benchmarks.bench_fileslice + +.. currentmodule:: nibabel.benchmarks.bench_fileslice +.. autosummary:: + + bench_fileslice + run_slices + +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +Module: :mod:`benchmarks.bench_finite_range` +-------------------------------------------- +.. automodule:: nibabel.benchmarks.bench_finite_range + +.. currentmodule:: nibabel.benchmarks.bench_finite_range +.. autosummary:: + + bench_finite_range + +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +Module: :mod:`benchmarks.bench_load_save` +----------------------------------------- +.. automodule:: nibabel.benchmarks.bench_load_save + +.. currentmodule:: nibabel.benchmarks.bench_load_save +.. autosummary:: + + bench_load_save + +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +Module: :mod:`benchmarks.butils` +-------------------------------- +.. automodule:: nibabel.benchmarks.butils + +.. currentmodule:: nibabel.benchmarks.butils +.. autosummary:: + + print_git_title + + +.. currentmodule:: nibabel.benchmarks + + +.. currentmodule:: nibabel.benchmarks.bench_array_to_file + +bench_array_to_file +~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: bench_array_to_file + + +.. currentmodule:: nibabel.benchmarks.bench_arrayproxy_slicing + +bench_arrayproxy_slicing +~~~~~~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: bench_arrayproxy_slicing + + +.. currentmodule:: nibabel.benchmarks.bench_fileslice + +bench_fileslice +~~~~~~~~~~~~~~~ + +.. autofunction:: bench_fileslice + +run_slices +~~~~~~~~~~ + +.. autofunction:: run_slices + + +.. currentmodule:: nibabel.benchmarks.bench_finite_range + +bench_finite_range +~~~~~~~~~~~~~~~~~~ + +.. autofunction:: bench_finite_range + + +.. currentmodule:: nibabel.benchmarks.bench_load_save + +bench_load_save +~~~~~~~~~~~~~~~ + +.. autofunction:: bench_load_save + + +.. currentmodule:: nibabel.benchmarks.butils + +print_git_title +~~~~~~~~~~~~~~~ + +.. autofunction:: print_git_title + diff --git a/_sources/reference/nibabel.brikhead.rst.txt b/_sources/reference/nibabel.brikhead.rst.txt new file mode 100644 index 0000000000..3c2143f099 --- /dev/null +++ b/_sources/reference/nibabel.brikhead.rst.txt @@ -0,0 +1,84 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`brikhead` +=============== +.. automodule:: nibabel.brikhead + +.. currentmodule:: nibabel.brikhead +.. autosummary:: + + AFNIArrayProxy + AFNIHeader + AFNIHeaderError + AFNIImage + AFNIImageError + parse_AFNI_header + + +.. currentmodule:: nibabel.brikhead + + +:class:`AFNIArrayProxy` +~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: AFNIArrayProxy + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`AFNIHeader` +~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: AFNIHeader + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`AFNIHeaderError` +~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: AFNIHeaderError + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`AFNIImage` +~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: AFNIImage + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`AFNIImageError` +~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: AFNIImageError + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + +parse_AFNI_header +~~~~~~~~~~~~~~~~~ + +.. autofunction:: parse_AFNI_header + diff --git a/_sources/reference/nibabel.caret.rst.txt b/_sources/reference/nibabel.caret.rst.txt new file mode 100644 index 0000000000..d955ba66b7 --- /dev/null +++ b/_sources/reference/nibabel.caret.rst.txt @@ -0,0 +1,26 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`caret` +============ +.. automodule:: nibabel.caret + +.. currentmodule:: nibabel.caret +.. autosummary:: + + CaretMetaData + + +.. currentmodule:: nibabel.caret + + +:class:`CaretMetaData` +~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: CaretMetaData + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + diff --git a/_sources/reference/nibabel.casting.rst.txt b/_sources/reference/nibabel.casting.rst.txt new file mode 100644 index 0000000000..0e1225f753 --- /dev/null +++ b/_sources/reference/nibabel.casting.rst.txt @@ -0,0 +1,141 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`casting` +============== +.. automodule:: nibabel.casting + +.. currentmodule:: nibabel.casting +.. autosummary:: + + CastingError + FloatingError + able_int_type + as_int + best_float + ceil_exact + float_to_int + floor_exact + floor_log2 + have_binary128 + int_abs + int_to_float + longdouble_lte_float64 + longdouble_precision_improved + ok_floats + on_powerpc + shared_range + type_info + ulp + + +.. currentmodule:: nibabel.casting + + +:class:`CastingError` +~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: CastingError + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`FloatingError` +~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: FloatingError + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + +able_int_type +~~~~~~~~~~~~~ + +.. autofunction:: able_int_type + +as_int +~~~~~~ + +.. autofunction:: as_int + +best_float +~~~~~~~~~~ + +.. autofunction:: best_float + +ceil_exact +~~~~~~~~~~ + +.. autofunction:: ceil_exact + +float_to_int +~~~~~~~~~~~~ + +.. autofunction:: float_to_int + +floor_exact +~~~~~~~~~~~ + +.. autofunction:: floor_exact + +floor_log2 +~~~~~~~~~~ + +.. autofunction:: floor_log2 + +have_binary128 +~~~~~~~~~~~~~~ + +.. autofunction:: have_binary128 + +int_abs +~~~~~~~ + +.. autofunction:: int_abs + +int_to_float +~~~~~~~~~~~~ + +.. autofunction:: int_to_float + +longdouble_lte_float64 +~~~~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: longdouble_lte_float64 + +longdouble_precision_improved +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: longdouble_precision_improved + +ok_floats +~~~~~~~~~ + +.. autofunction:: ok_floats + +on_powerpc +~~~~~~~~~~ + +.. autofunction:: on_powerpc + +shared_range +~~~~~~~~~~~~ + +.. autofunction:: shared_range + +type_info +~~~~~~~~~ + +.. autofunction:: type_info + +ulp +~~~ + +.. autofunction:: ulp + diff --git a/_sources/reference/nibabel.cifti2.rst.txt b/_sources/reference/nibabel.cifti2.rst.txt new file mode 100644 index 0000000000..d900edf1b8 --- /dev/null +++ b/_sources/reference/nibabel.cifti2.rst.txt @@ -0,0 +1,402 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`cifti2` +============= +.. automodule:: nibabel.cifti2 + +.. currentmodule:: nibabel.cifti2 +.. autosummary:: + + +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +Module: :mod:`cifti2.cifti2` +---------------------------- +.. automodule:: nibabel.cifti2.cifti2 + +.. currentmodule:: nibabel.cifti2.cifti2 +.. autosummary:: + + Cifti2BrainModel + Cifti2Header + Cifti2HeaderError + Cifti2Image + Cifti2Label + Cifti2LabelTable + Cifti2Matrix + Cifti2MatrixIndicesMap + Cifti2MetaData + Cifti2NamedMap + Cifti2Parcel + Cifti2Surface + Cifti2TransformationMatrixVoxelIndicesIJKtoXYZ + Cifti2VertexIndices + Cifti2Vertices + Cifti2Volume + Cifti2VoxelIndicesIJK + LimitedNifti2Header + +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +Module: :mod:`cifti2.cifti2_axes` +--------------------------------- +.. automodule:: nibabel.cifti2.cifti2_axes + +.. currentmodule:: nibabel.cifti2.cifti2_axes +.. autosummary:: + + Axis + BrainModelAxis + LabelAxis + ParcelsAxis + ScalarAxis + SeriesAxis + from_index_mapping + to_header + +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +Module: :mod:`cifti2.parse_cifti2` +---------------------------------- +.. automodule:: nibabel.cifti2.parse_cifti2 + +.. currentmodule:: nibabel.cifti2.parse_cifti2 +.. autosummary:: + + Cifti2Extension + Cifti2Parser + + +.. currentmodule:: nibabel.cifti2 + + +.. currentmodule:: nibabel.cifti2.cifti2 + + +:class:`Cifti2BrainModel` +~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: Cifti2BrainModel + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`Cifti2Header` +~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: Cifti2Header + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`Cifti2HeaderError` +~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: Cifti2HeaderError + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`Cifti2Image` +~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: Cifti2Image + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`Cifti2Label` +~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: Cifti2Label + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`Cifti2LabelTable` +~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: Cifti2LabelTable + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`Cifti2Matrix` +~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: Cifti2Matrix + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`Cifti2MatrixIndicesMap` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: Cifti2MatrixIndicesMap + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`Cifti2MetaData` +~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: Cifti2MetaData + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`Cifti2NamedMap` +~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: Cifti2NamedMap + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`Cifti2Parcel` +~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: Cifti2Parcel + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`Cifti2Surface` +~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: Cifti2Surface + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`Cifti2TransformationMatrixVoxelIndicesIJKtoXYZ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: Cifti2TransformationMatrixVoxelIndicesIJKtoXYZ + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`Cifti2VertexIndices` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: Cifti2VertexIndices + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`Cifti2Vertices` +~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: Cifti2Vertices + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`Cifti2Volume` +~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: Cifti2Volume + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`Cifti2VoxelIndicesIJK` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: Cifti2VoxelIndicesIJK + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`LimitedNifti2Header` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: LimitedNifti2Header + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +.. currentmodule:: nibabel.cifti2.cifti2_axes + + +:class:`Axis` +~~~~~~~~~~~~~ + + +.. autoclass:: Axis + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`BrainModelAxis` +~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: BrainModelAxis + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`LabelAxis` +~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: LabelAxis + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`ParcelsAxis` +~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: ParcelsAxis + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`ScalarAxis` +~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: ScalarAxis + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`SeriesAxis` +~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: SeriesAxis + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + +from_index_mapping +~~~~~~~~~~~~~~~~~~ + +.. autofunction:: from_index_mapping + +to_header +~~~~~~~~~ + +.. autofunction:: to_header + + +.. currentmodule:: nibabel.cifti2.parse_cifti2 + + +:class:`Cifti2Extension` +~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: Cifti2Extension + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`Cifti2Parser` +~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: Cifti2Parser + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + diff --git a/_sources/reference/nibabel.cmdline.rst.txt b/_sources/reference/nibabel.cmdline.rst.txt new file mode 100644 index 0000000000..94d2526798 --- /dev/null +++ b/_sources/reference/nibabel.cmdline.rst.txt @@ -0,0 +1,425 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`cmdline` +============== +.. automodule:: nibabel.cmdline + +.. currentmodule:: nibabel.cmdline +.. autosummary:: + + +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +Module: :mod:`cmdline.conform` +------------------------------ +.. automodule:: nibabel.cmdline.conform + +.. currentmodule:: nibabel.cmdline.conform +.. autosummary:: + + main + +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +Module: :mod:`cmdline.convert` +------------------------------ +.. automodule:: nibabel.cmdline.convert + +.. currentmodule:: nibabel.cmdline.convert +.. autosummary:: + + main + +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +Module: :mod:`cmdline.dicomfs` +------------------------------ +.. automodule:: nibabel.cmdline.dicomfs + +.. currentmodule:: nibabel.cmdline.dicomfs +.. autosummary:: + + DICOMFS + FileHandle + dummy_fuse + fuse + get_opt_parser + main + +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +Module: :mod:`cmdline.diff` +--------------------------- +.. automodule:: nibabel.cmdline.diff + +.. currentmodule:: nibabel.cmdline.diff +.. autosummary:: + + are_values_different + diff + display_diff + get_data_diff + get_data_hash_diff + get_headers_diff + get_opt_parser + main + +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +Module: :mod:`cmdline.ls` +------------------------- +.. automodule:: nibabel.cmdline.ls + +.. currentmodule:: nibabel.cmdline.ls +.. autosummary:: + + get_opt_parser + main + proc_file + +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +Module: :mod:`cmdline.nifti_dx` +------------------------------- +.. automodule:: nibabel.cmdline.nifti_dx + +.. currentmodule:: nibabel.cmdline.nifti_dx +.. autosummary:: + + main + +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +Module: :mod:`cmdline.parrec2nii` +--------------------------------- +.. automodule:: nibabel.cmdline.parrec2nii + +.. currentmodule:: nibabel.cmdline.parrec2nii +.. autosummary:: + + error + get_opt_parser + main + proc_file + verbose + +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +Module: :mod:`cmdline.roi` +-------------------------- +.. automodule:: nibabel.cmdline.roi + +.. currentmodule:: nibabel.cmdline.roi +.. autosummary:: + + lossless_slice + main + parse_slice + sanitize + +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +Module: :mod:`cmdline.stats` +---------------------------- +.. automodule:: nibabel.cmdline.stats + +.. currentmodule:: nibabel.cmdline.stats +.. autosummary:: + + main + +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +Module: :mod:`cmdline.tck2trk` +------------------------------ +.. automodule:: nibabel.cmdline.tck2trk + +.. currentmodule:: nibabel.cmdline.tck2trk +.. autosummary:: + + main + parse_args + +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +Module: :mod:`cmdline.trk2tck` +------------------------------ +.. automodule:: nibabel.cmdline.trk2tck + +.. currentmodule:: nibabel.cmdline.trk2tck +.. autosummary:: + + main + parse_args + +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +Module: :mod:`cmdline.utils` +---------------------------- +.. automodule:: nibabel.cmdline.utils + +.. currentmodule:: nibabel.cmdline.utils +.. autosummary:: + + ap + safe_get + table2string + verbose + + +.. currentmodule:: nibabel.cmdline + + +.. currentmodule:: nibabel.cmdline.conform + +main +~~~~ + +.. autofunction:: main + + +.. currentmodule:: nibabel.cmdline.convert + +main +~~~~ + +.. autofunction:: main + + +.. currentmodule:: nibabel.cmdline.dicomfs + + +:class:`DICOMFS` +~~~~~~~~~~~~~~~~ + + +.. autoclass:: DICOMFS + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`FileHandle` +~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: FileHandle + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`dummy_fuse` +~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: dummy_fuse + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`fuse` +~~~~~~~~~~~~~ + + +.. autoclass:: fuse + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + +get_opt_parser +~~~~~~~~~~~~~~ + +.. autofunction:: get_opt_parser + +main +~~~~ + +.. autofunction:: main + + +.. currentmodule:: nibabel.cmdline.diff + +are_values_different +~~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: are_values_different + +diff +~~~~ + +.. autofunction:: diff + +display_diff +~~~~~~~~~~~~ + +.. autofunction:: display_diff + +get_data_diff +~~~~~~~~~~~~~ + +.. autofunction:: get_data_diff + +get_data_hash_diff +~~~~~~~~~~~~~~~~~~ + +.. autofunction:: get_data_hash_diff + +get_headers_diff +~~~~~~~~~~~~~~~~ + +.. autofunction:: get_headers_diff + +get_opt_parser +~~~~~~~~~~~~~~ + +.. autofunction:: get_opt_parser + +main +~~~~ + +.. autofunction:: main + + +.. currentmodule:: nibabel.cmdline.ls + +get_opt_parser +~~~~~~~~~~~~~~ + +.. autofunction:: get_opt_parser + +main +~~~~ + +.. autofunction:: main + +proc_file +~~~~~~~~~ + +.. autofunction:: proc_file + + +.. currentmodule:: nibabel.cmdline.nifti_dx + +main +~~~~ + +.. autofunction:: main + + +.. currentmodule:: nibabel.cmdline.parrec2nii + +error +~~~~~ + +.. autofunction:: error + +get_opt_parser +~~~~~~~~~~~~~~ + +.. autofunction:: get_opt_parser + +main +~~~~ + +.. autofunction:: main + +proc_file +~~~~~~~~~ + +.. autofunction:: proc_file + +verbose +~~~~~~~ + +.. autofunction:: verbose + + +.. currentmodule:: nibabel.cmdline.roi + +lossless_slice +~~~~~~~~~~~~~~ + +.. autofunction:: lossless_slice + +main +~~~~ + +.. autofunction:: main + +parse_slice +~~~~~~~~~~~ + +.. autofunction:: parse_slice + +sanitize +~~~~~~~~ + +.. autofunction:: sanitize + + +.. currentmodule:: nibabel.cmdline.stats + +main +~~~~ + +.. autofunction:: main + + +.. currentmodule:: nibabel.cmdline.tck2trk + +main +~~~~ + +.. autofunction:: main + +parse_args +~~~~~~~~~~ + +.. autofunction:: parse_args + + +.. currentmodule:: nibabel.cmdline.trk2tck + +main +~~~~ + +.. autofunction:: main + +parse_args +~~~~~~~~~~ + +.. autofunction:: parse_args + + +.. currentmodule:: nibabel.cmdline.utils + +ap +~~ + +.. autofunction:: ap + +safe_get +~~~~~~~~ + +.. autofunction:: safe_get + +table2string +~~~~~~~~~~~~ + +.. autofunction:: table2string + +verbose +~~~~~~~ + +.. autofunction:: verbose + diff --git a/_sources/reference/nibabel.data.rst.txt b/_sources/reference/nibabel.data.rst.txt new file mode 100644 index 0000000000..c8e689c639 --- /dev/null +++ b/_sources/reference/nibabel.data.rst.txt @@ -0,0 +1,102 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`data` +=========== +.. automodule:: nibabel.data + +.. currentmodule:: nibabel.data +.. autosummary:: + + Bomber + BomberError + DataError + Datasource + VersionedDatasource + datasource_or_bomber + find_data_dir + get_data_path + make_datasource + + +.. currentmodule:: nibabel.data + + +:class:`Bomber` +~~~~~~~~~~~~~~~ + + +.. autoclass:: Bomber + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`BomberError` +~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: BomberError + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`DataError` +~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: DataError + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`Datasource` +~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: Datasource + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`VersionedDatasource` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: VersionedDatasource + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + +datasource_or_bomber +~~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: datasource_or_bomber + +find_data_dir +~~~~~~~~~~~~~ + +.. autofunction:: find_data_dir + +get_data_path +~~~~~~~~~~~~~ + +.. autofunction:: get_data_path + +make_datasource +~~~~~~~~~~~~~~~ + +.. autofunction:: make_datasource + diff --git a/_sources/reference/nibabel.dataobj_images.rst.txt b/_sources/reference/nibabel.dataobj_images.rst.txt new file mode 100644 index 0000000000..bda68d7c53 --- /dev/null +++ b/_sources/reference/nibabel.dataobj_images.rst.txt @@ -0,0 +1,26 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`dataobj_images` +===================== +.. automodule:: nibabel.dataobj_images + +.. currentmodule:: nibabel.dataobj_images +.. autosummary:: + + DataobjImage + + +.. currentmodule:: nibabel.dataobj_images + + +:class:`DataobjImage` +~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: DataobjImage + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + diff --git a/_sources/reference/nibabel.deprecated.rst.txt b/_sources/reference/nibabel.deprecated.rst.txt new file mode 100644 index 0000000000..9b45620fcf --- /dev/null +++ b/_sources/reference/nibabel.deprecated.rst.txt @@ -0,0 +1,58 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`deprecated` +================= +.. automodule:: nibabel.deprecated + +.. currentmodule:: nibabel.deprecated +.. autosummary:: + + FutureWarningMixin + ModuleProxy + VisibleDeprecationWarning + alert_future_error + + +.. currentmodule:: nibabel.deprecated + + +:class:`FutureWarningMixin` +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: FutureWarningMixin + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`ModuleProxy` +~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: ModuleProxy + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`VisibleDeprecationWarning` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: VisibleDeprecationWarning + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + +alert_future_error +~~~~~~~~~~~~~~~~~~ + +.. autofunction:: alert_future_error + diff --git a/_sources/reference/nibabel.deprecator.rst.txt b/_sources/reference/nibabel.deprecator.rst.txt new file mode 100644 index 0000000000..3cb5d5c9e7 --- /dev/null +++ b/_sources/reference/nibabel.deprecator.rst.txt @@ -0,0 +1,39 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`deprecator` +================= +.. automodule:: nibabel.deprecator + +.. currentmodule:: nibabel.deprecator +.. autosummary:: + + Deprecator + ExpiredDeprecationError + + +.. currentmodule:: nibabel.deprecator + + +:class:`Deprecator` +~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: Deprecator + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`ExpiredDeprecationError` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: ExpiredDeprecationError + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + diff --git a/_sources/reference/nibabel.dft.rst.txt b/_sources/reference/nibabel.dft.rst.txt new file mode 100644 index 0000000000..9abc68218d --- /dev/null +++ b/_sources/reference/nibabel.dft.rst.txt @@ -0,0 +1,83 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`dft` +========== +.. automodule:: nibabel.dft + +.. currentmodule:: nibabel.dft +.. autosummary:: + + CachingError + DFTError + InstanceStackError + VolumeError + clear_cache + get_studies + update_cache + + +.. currentmodule:: nibabel.dft + + +:class:`CachingError` +~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: CachingError + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`DFTError` +~~~~~~~~~~~~~~~~~ + + +.. autoclass:: DFTError + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`InstanceStackError` +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: InstanceStackError + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`VolumeError` +~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: VolumeError + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + +clear_cache +~~~~~~~~~~~ + +.. autofunction:: clear_cache + +get_studies +~~~~~~~~~~~ + +.. autofunction:: get_studies + +update_cache +~~~~~~~~~~~~ + +.. autofunction:: update_cache + diff --git a/_sources/reference/nibabel.ecat.rst.txt b/_sources/reference/nibabel.ecat.rst.txt new file mode 100644 index 0000000000..cdb4ff5914 --- /dev/null +++ b/_sources/reference/nibabel.ecat.rst.txt @@ -0,0 +1,89 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`ecat` +=========== +.. automodule:: nibabel.ecat + +.. currentmodule:: nibabel.ecat +.. autosummary:: + + EcatHeader + EcatImage + EcatImageArrayProxy + EcatSubHeader + get_frame_order + get_series_framenumbers + read_mlist + read_subheaders + + +.. currentmodule:: nibabel.ecat + + +:class:`EcatHeader` +~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: EcatHeader + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`EcatImage` +~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: EcatImage + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`EcatImageArrayProxy` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: EcatImageArrayProxy + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`EcatSubHeader` +~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: EcatSubHeader + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + +get_frame_order +~~~~~~~~~~~~~~~ + +.. autofunction:: get_frame_order + +get_series_framenumbers +~~~~~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: get_series_framenumbers + +read_mlist +~~~~~~~~~~ + +.. autofunction:: read_mlist + +read_subheaders +~~~~~~~~~~~~~~~ + +.. autofunction:: read_subheaders + diff --git a/_sources/reference/nibabel.environment.rst.txt b/_sources/reference/nibabel.environment.rst.txt new file mode 100644 index 0000000000..84b297da14 --- /dev/null +++ b/_sources/reference/nibabel.environment.rst.txt @@ -0,0 +1,31 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`environment` +================== +.. automodule:: nibabel.environment + +.. currentmodule:: nibabel.environment +.. autosummary:: + + get_home_dir + get_nipy_system_dir + get_nipy_user_dir + + +.. currentmodule:: nibabel.environment + +get_home_dir +~~~~~~~~~~~~ + +.. autofunction:: get_home_dir + +get_nipy_system_dir +~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: get_nipy_system_dir + +get_nipy_user_dir +~~~~~~~~~~~~~~~~~ + +.. autofunction:: get_nipy_user_dir + diff --git a/_sources/reference/nibabel.eulerangles.rst.txt b/_sources/reference/nibabel.eulerangles.rst.txt new file mode 100644 index 0000000000..360ba20a9e --- /dev/null +++ b/_sources/reference/nibabel.eulerangles.rst.txt @@ -0,0 +1,49 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`eulerangles` +================== +.. automodule:: nibabel.eulerangles + +.. currentmodule:: nibabel.eulerangles +.. autosummary:: + + angle_axis2euler + euler2angle_axis + euler2mat + euler2quat + mat2euler + quat2euler + + +.. currentmodule:: nibabel.eulerangles + +angle_axis2euler +~~~~~~~~~~~~~~~~ + +.. autofunction:: angle_axis2euler + +euler2angle_axis +~~~~~~~~~~~~~~~~ + +.. autofunction:: euler2angle_axis + +euler2mat +~~~~~~~~~ + +.. autofunction:: euler2mat + +euler2quat +~~~~~~~~~~ + +.. autofunction:: euler2quat + +mat2euler +~~~~~~~~~ + +.. autofunction:: mat2euler + +quat2euler +~~~~~~~~~~ + +.. autofunction:: quat2euler + diff --git a/_sources/reference/nibabel.filebasedimages.rst.txt b/_sources/reference/nibabel.filebasedimages.rst.txt new file mode 100644 index 0000000000..6b93396805 --- /dev/null +++ b/_sources/reference/nibabel.filebasedimages.rst.txt @@ -0,0 +1,65 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`filebasedimages` +====================== +.. automodule:: nibabel.filebasedimages + +.. currentmodule:: nibabel.filebasedimages +.. autosummary:: + + FileBasedHeader + FileBasedImage + ImageFileError + SerializableImage + + +.. currentmodule:: nibabel.filebasedimages + + +:class:`FileBasedHeader` +~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: FileBasedHeader + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`FileBasedImage` +~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: FileBasedImage + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`ImageFileError` +~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: ImageFileError + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`SerializableImage` +~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: SerializableImage + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + diff --git a/_sources/reference/nibabel.fileholders.rst.txt b/_sources/reference/nibabel.fileholders.rst.txt new file mode 100644 index 0000000000..81f10271ee --- /dev/null +++ b/_sources/reference/nibabel.fileholders.rst.txt @@ -0,0 +1,45 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`fileholders` +================== +.. automodule:: nibabel.fileholders + +.. currentmodule:: nibabel.fileholders +.. autosummary:: + + FileHolder + FileHolderError + copy_file_map + + +.. currentmodule:: nibabel.fileholders + + +:class:`FileHolder` +~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: FileHolder + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`FileHolderError` +~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: FileHolderError + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + +copy_file_map +~~~~~~~~~~~~~ + +.. autofunction:: copy_file_map + diff --git a/_sources/reference/nibabel.filename_parser.rst.txt b/_sources/reference/nibabel.filename_parser.rst.txt new file mode 100644 index 0000000000..1bfb64ed7a --- /dev/null +++ b/_sources/reference/nibabel.filename_parser.rst.txt @@ -0,0 +1,44 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`filename_parser` +====================== +.. automodule:: nibabel.filename_parser + +.. currentmodule:: nibabel.filename_parser +.. autosummary:: + + TypesFilenamesError + parse_filename + splitext_addext + types_filenames + + +.. currentmodule:: nibabel.filename_parser + + +:class:`TypesFilenamesError` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: TypesFilenamesError + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + +parse_filename +~~~~~~~~~~~~~~ + +.. autofunction:: parse_filename + +splitext_addext +~~~~~~~~~~~~~~~ + +.. autofunction:: splitext_addext + +types_filenames +~~~~~~~~~~~~~~~ + +.. autofunction:: types_filenames + diff --git a/_sources/reference/nibabel.fileslice.rst.txt b/_sources/reference/nibabel.fileslice.rst.txt new file mode 100644 index 0000000000..459d863b6f --- /dev/null +++ b/_sources/reference/nibabel.fileslice.rst.txt @@ -0,0 +1,97 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`fileslice` +================ +.. automodule:: nibabel.fileslice + +.. currentmodule:: nibabel.fileslice +.. autosummary:: + + calc_slicedefs + canonical_slicers + fileslice + fill_slicer + is_fancy + optimize_read_slicers + optimize_slicer + predict_shape + read_segments + slice2len + slice2outax + slicers2segments + strided_scalar + threshold_heuristic + + +.. currentmodule:: nibabel.fileslice + +calc_slicedefs +~~~~~~~~~~~~~~ + +.. autofunction:: calc_slicedefs + +canonical_slicers +~~~~~~~~~~~~~~~~~ + +.. autofunction:: canonical_slicers + +fileslice +~~~~~~~~~ + +.. autofunction:: fileslice + +fill_slicer +~~~~~~~~~~~ + +.. autofunction:: fill_slicer + +is_fancy +~~~~~~~~ + +.. autofunction:: is_fancy + +optimize_read_slicers +~~~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: optimize_read_slicers + +optimize_slicer +~~~~~~~~~~~~~~~ + +.. autofunction:: optimize_slicer + +predict_shape +~~~~~~~~~~~~~ + +.. autofunction:: predict_shape + +read_segments +~~~~~~~~~~~~~ + +.. autofunction:: read_segments + +slice2len +~~~~~~~~~ + +.. autofunction:: slice2len + +slice2outax +~~~~~~~~~~~ + +.. autofunction:: slice2outax + +slicers2segments +~~~~~~~~~~~~~~~~ + +.. autofunction:: slicers2segments + +strided_scalar +~~~~~~~~~~~~~~ + +.. autofunction:: strided_scalar + +threshold_heuristic +~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: threshold_heuristic + diff --git a/_sources/reference/nibabel.fileutils.rst.txt b/_sources/reference/nibabel.fileutils.rst.txt new file mode 100644 index 0000000000..250403ed93 --- /dev/null +++ b/_sources/reference/nibabel.fileutils.rst.txt @@ -0,0 +1,19 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`fileutils` +================ +.. automodule:: nibabel.fileutils + +.. currentmodule:: nibabel.fileutils +.. autosummary:: + + read_zt_byte_strings + + +.. currentmodule:: nibabel.fileutils + +read_zt_byte_strings +~~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: read_zt_byte_strings + diff --git a/_sources/reference/nibabel.freesurfer.rst.txt b/_sources/reference/nibabel.freesurfer.rst.txt new file mode 100644 index 0000000000..43aab31775 --- /dev/null +++ b/_sources/reference/nibabel.freesurfer.rst.txt @@ -0,0 +1,120 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`freesurfer` +================= +.. automodule:: nibabel.freesurfer + +.. currentmodule:: nibabel.freesurfer +.. autosummary:: + + +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +Module: :mod:`freesurfer.io` +---------------------------- +.. automodule:: nibabel.freesurfer.io + +.. currentmodule:: nibabel.freesurfer.io +.. autosummary:: + + read_annot + read_geometry + read_label + read_morph_data + write_annot + write_geometry + write_morph_data + +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +Module: :mod:`freesurfer.mghformat` +----------------------------------- +.. automodule:: nibabel.freesurfer.mghformat + +.. currentmodule:: nibabel.freesurfer.mghformat +.. autosummary:: + + MGHError + MGHHeader + MGHImage + + +.. currentmodule:: nibabel.freesurfer + + +.. currentmodule:: nibabel.freesurfer.io + +read_annot +~~~~~~~~~~ + +.. autofunction:: read_annot + +read_geometry +~~~~~~~~~~~~~ + +.. autofunction:: read_geometry + +read_label +~~~~~~~~~~ + +.. autofunction:: read_label + +read_morph_data +~~~~~~~~~~~~~~~ + +.. autofunction:: read_morph_data + +write_annot +~~~~~~~~~~~ + +.. autofunction:: write_annot + +write_geometry +~~~~~~~~~~~~~~ + +.. autofunction:: write_geometry + +write_morph_data +~~~~~~~~~~~~~~~~ + +.. autofunction:: write_morph_data + + +.. currentmodule:: nibabel.freesurfer.mghformat + + +:class:`MGHError` +~~~~~~~~~~~~~~~~~ + + +.. autoclass:: MGHError + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`MGHHeader` +~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: MGHHeader + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`MGHImage` +~~~~~~~~~~~~~~~~~ + + +.. autoclass:: MGHImage + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + diff --git a/_sources/reference/nibabel.funcs.rst.txt b/_sources/reference/nibabel.funcs.rst.txt new file mode 100644 index 0000000000..9d8245fd6b --- /dev/null +++ b/_sources/reference/nibabel.funcs.rst.txt @@ -0,0 +1,37 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`funcs` +============ +.. automodule:: nibabel.funcs + +.. currentmodule:: nibabel.funcs +.. autosummary:: + + as_closest_canonical + concat_images + four_to_three + squeeze_image + + +.. currentmodule:: nibabel.funcs + +as_closest_canonical +~~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: as_closest_canonical + +concat_images +~~~~~~~~~~~~~ + +.. autofunction:: concat_images + +four_to_three +~~~~~~~~~~~~~ + +.. autofunction:: four_to_three + +squeeze_image +~~~~~~~~~~~~~ + +.. autofunction:: squeeze_image + diff --git a/_sources/reference/nibabel.gifti.rst.txt b/_sources/reference/nibabel.gifti.rst.txt new file mode 100644 index 0000000000..2b6c3cef33 --- /dev/null +++ b/_sources/reference/nibabel.gifti.rst.txt @@ -0,0 +1,175 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`gifti` +============ +.. automodule:: nibabel.gifti + +.. currentmodule:: nibabel.gifti +.. autosummary:: + + +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +Module: :mod:`gifti.gifti` +-------------------------- +.. automodule:: nibabel.gifti.gifti + +.. currentmodule:: nibabel.gifti.gifti +.. autosummary:: + + GiftiCoordSystem + GiftiDataArray + GiftiImage + GiftiLabel + GiftiLabelTable + GiftiMetaData + GiftiNVPairs + +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +Module: :mod:`gifti.parse_gifti_fast` +------------------------------------- +.. automodule:: nibabel.gifti.parse_gifti_fast + +.. currentmodule:: nibabel.gifti.parse_gifti_fast +.. autosummary:: + + GiftiImageParser + GiftiParseError + read_data_block + +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +Module: :mod:`gifti.util` +------------------------- +.. automodule:: nibabel.gifti.util + +.. currentmodule:: nibabel.gifti.util +.. autosummary:: + + + +.. currentmodule:: nibabel.gifti + + +.. currentmodule:: nibabel.gifti.gifti + + +:class:`GiftiCoordSystem` +~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: GiftiCoordSystem + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`GiftiDataArray` +~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: GiftiDataArray + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`GiftiImage` +~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: GiftiImage + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`GiftiLabel` +~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: GiftiLabel + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`GiftiLabelTable` +~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: GiftiLabelTable + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`GiftiMetaData` +~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: GiftiMetaData + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`GiftiNVPairs` +~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: GiftiNVPairs + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +.. currentmodule:: nibabel.gifti.parse_gifti_fast + + +:class:`GiftiImageParser` +~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: GiftiImageParser + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`GiftiParseError` +~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: GiftiParseError + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + +read_data_block +~~~~~~~~~~~~~~~ + +.. autofunction:: read_data_block + + +.. currentmodule:: nibabel.gifti.util + diff --git a/_sources/reference/nibabel.imageclasses.rst.txt b/_sources/reference/nibabel.imageclasses.rst.txt new file mode 100644 index 0000000000..bea117241b --- /dev/null +++ b/_sources/reference/nibabel.imageclasses.rst.txt @@ -0,0 +1,19 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`imageclasses` +=================== +.. automodule:: nibabel.imageclasses + +.. currentmodule:: nibabel.imageclasses +.. autosummary:: + + spatial_axes_first + + +.. currentmodule:: nibabel.imageclasses + +spatial_axes_first +~~~~~~~~~~~~~~~~~~ + +.. autofunction:: spatial_axes_first + diff --git a/_sources/reference/nibabel.imageglobals.rst.txt b/_sources/reference/nibabel.imageglobals.rst.txt new file mode 100644 index 0000000000..0c125bc5e1 --- /dev/null +++ b/_sources/reference/nibabel.imageglobals.rst.txt @@ -0,0 +1,39 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`imageglobals` +=================== +.. automodule:: nibabel.imageglobals + +.. currentmodule:: nibabel.imageglobals +.. autosummary:: + + ErrorLevel + LoggingOutputSuppressor + + +.. currentmodule:: nibabel.imageglobals + + +:class:`ErrorLevel` +~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: ErrorLevel + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`LoggingOutputSuppressor` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: LoggingOutputSuppressor + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + diff --git a/_sources/reference/nibabel.imagestats.rst.txt b/_sources/reference/nibabel.imagestats.rst.txt new file mode 100644 index 0000000000..36ab0c0446 --- /dev/null +++ b/_sources/reference/nibabel.imagestats.rst.txt @@ -0,0 +1,25 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`imagestats` +================= +.. automodule:: nibabel.imagestats + +.. currentmodule:: nibabel.imagestats +.. autosummary:: + + count_nonzero_voxels + mask_volume + + +.. currentmodule:: nibabel.imagestats + +count_nonzero_voxels +~~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: count_nonzero_voxels + +mask_volume +~~~~~~~~~~~ + +.. autofunction:: mask_volume + diff --git a/_sources/reference/nibabel.loadsave.rst.txt b/_sources/reference/nibabel.loadsave.rst.txt new file mode 100644 index 0000000000..7ed3ae5b70 --- /dev/null +++ b/_sources/reference/nibabel.loadsave.rst.txt @@ -0,0 +1,37 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`loadsave` +=============== +.. automodule:: nibabel.loadsave + +.. currentmodule:: nibabel.loadsave +.. autosummary:: + + guessed_image_type + load + read_img_data + save + + +.. currentmodule:: nibabel.loadsave + +guessed_image_type +~~~~~~~~~~~~~~~~~~ + +.. autofunction:: guessed_image_type + +load +~~~~ + +.. autofunction:: load + +read_img_data +~~~~~~~~~~~~~ + +.. autofunction:: read_img_data + +save +~~~~ + +.. autofunction:: save + diff --git a/_sources/reference/nibabel.minc1.rst.txt b/_sources/reference/nibabel.minc1.rst.txt new file mode 100644 index 0000000000..c9cba6f7ea --- /dev/null +++ b/_sources/reference/nibabel.minc1.rst.txt @@ -0,0 +1,91 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`minc1` +============ +.. automodule:: nibabel.minc1 + +.. currentmodule:: nibabel.minc1 +.. autosummary:: + + Minc1File + Minc1Header + Minc1Image + MincError + MincHeader + MincImageArrayProxy + + +.. currentmodule:: nibabel.minc1 + + +:class:`Minc1File` +~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: Minc1File + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`Minc1Header` +~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: Minc1Header + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`Minc1Image` +~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: Minc1Image + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`MincError` +~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: MincError + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`MincHeader` +~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: MincHeader + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`MincImageArrayProxy` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: MincImageArrayProxy + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + diff --git a/_sources/reference/nibabel.minc2.rst.txt b/_sources/reference/nibabel.minc2.rst.txt new file mode 100644 index 0000000000..03c2f0c94a --- /dev/null +++ b/_sources/reference/nibabel.minc2.rst.txt @@ -0,0 +1,65 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`minc2` +============ +.. automodule:: nibabel.minc2 + +.. currentmodule:: nibabel.minc2 +.. autosummary:: + + Hdf5Bunch + Minc2File + Minc2Header + Minc2Image + + +.. currentmodule:: nibabel.minc2 + + +:class:`Hdf5Bunch` +~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: Hdf5Bunch + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`Minc2File` +~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: Minc2File + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`Minc2Header` +~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: Minc2Header + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`Minc2Image` +~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: Minc2Image + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + diff --git a/_sources/reference/nibabel.mriutils.rst.txt b/_sources/reference/nibabel.mriutils.rst.txt new file mode 100644 index 0000000000..1b1de427db --- /dev/null +++ b/_sources/reference/nibabel.mriutils.rst.txt @@ -0,0 +1,32 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`mriutils` +=============== +.. automodule:: nibabel.mriutils + +.. currentmodule:: nibabel.mriutils +.. autosummary:: + + MRIError + calculate_dwell_time + + +.. currentmodule:: nibabel.mriutils + + +:class:`MRIError` +~~~~~~~~~~~~~~~~~ + + +.. autoclass:: MRIError + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + +calculate_dwell_time +~~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: calculate_dwell_time + diff --git a/_sources/reference/nibabel.nicom.rst.txt b/_sources/reference/nibabel.nicom.rst.txt new file mode 100644 index 0000000000..670180fdbb --- /dev/null +++ b/_sources/reference/nibabel.nicom.rst.txt @@ -0,0 +1,493 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`nicom` +============ +.. automodule:: nibabel.nicom + +.. currentmodule:: nibabel.nicom +.. autosummary:: + + +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +Module: :mod:`nicom.ascconv` +---------------------------- +.. automodule:: nibabel.nicom.ascconv + +.. currentmodule:: nibabel.nicom.ascconv +.. autosummary:: + + AscconvParseError + Atom + NoValue + assign2atoms + obj_from_atoms + parse_ascconv + +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +Module: :mod:`nicom.csareader` +------------------------------ +.. automodule:: nibabel.nicom.csareader + +.. currentmodule:: nibabel.nicom.csareader +.. autosummary:: + + CSAError + CSAReadError + get_acq_mat_txt + get_b_matrix + get_b_value + get_csa_header + get_g_vector + get_ice_dims + get_n_mosaic + get_scalar + get_slice_normal + get_vector + is_mosaic + nt_str + read + +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +Module: :mod:`nicom.dicomreaders` +--------------------------------- +.. automodule:: nibabel.nicom.dicomreaders + +.. currentmodule:: nibabel.nicom.dicomreaders +.. autosummary:: + + DicomReadError + mosaic_to_nii + read_mosaic_dir + read_mosaic_dwi_dir + slices_to_series + +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +Module: :mod:`nicom.dicomwrappers` +---------------------------------- +.. automodule:: nibabel.nicom.dicomwrappers + +.. currentmodule:: nibabel.nicom.dicomwrappers +.. autosummary:: + + FilterDwiIso + FilterMultiStack + FrameFilter + MosaicWrapper + MultiframeWrapper + SiemensWrapper + Wrapper + WrapperError + WrapperPrecisionError + none_or_close + wrapper_from_data + wrapper_from_file + +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +Module: :mod:`nicom.dwiparams` +------------------------------ +.. automodule:: nibabel.nicom.dwiparams + +.. currentmodule:: nibabel.nicom.dwiparams +.. autosummary:: + + B2q + nearest_pos_semi_def + q2bg + +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +Module: :mod:`nicom.structreader` +--------------------------------- +.. automodule:: nibabel.nicom.structreader + +.. currentmodule:: nibabel.nicom.structreader +.. autosummary:: + + Unpacker + +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +Module: :mod:`nicom.utils` +-------------------------- +.. automodule:: nibabel.nicom.utils + +.. currentmodule:: nibabel.nicom.utils +.. autosummary:: + + Vendor + find_private_section + vendor_from_private + + +.. currentmodule:: nibabel.nicom + + +.. currentmodule:: nibabel.nicom.ascconv + + +:class:`AscconvParseError` +~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: AscconvParseError + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`Atom` +~~~~~~~~~~~~~ + + +.. autoclass:: Atom + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`NoValue` +~~~~~~~~~~~~~~~~ + + +.. autoclass:: NoValue + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + +assign2atoms +~~~~~~~~~~~~ + +.. autofunction:: assign2atoms + +obj_from_atoms +~~~~~~~~~~~~~~ + +.. autofunction:: obj_from_atoms + +parse_ascconv +~~~~~~~~~~~~~ + +.. autofunction:: parse_ascconv + + +.. currentmodule:: nibabel.nicom.csareader + + +:class:`CSAError` +~~~~~~~~~~~~~~~~~ + + +.. autoclass:: CSAError + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`CSAReadError` +~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: CSAReadError + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + +get_acq_mat_txt +~~~~~~~~~~~~~~~ + +.. autofunction:: get_acq_mat_txt + +get_b_matrix +~~~~~~~~~~~~ + +.. autofunction:: get_b_matrix + +get_b_value +~~~~~~~~~~~ + +.. autofunction:: get_b_value + +get_csa_header +~~~~~~~~~~~~~~ + +.. autofunction:: get_csa_header + +get_g_vector +~~~~~~~~~~~~ + +.. autofunction:: get_g_vector + +get_ice_dims +~~~~~~~~~~~~ + +.. autofunction:: get_ice_dims + +get_n_mosaic +~~~~~~~~~~~~ + +.. autofunction:: get_n_mosaic + +get_scalar +~~~~~~~~~~ + +.. autofunction:: get_scalar + +get_slice_normal +~~~~~~~~~~~~~~~~ + +.. autofunction:: get_slice_normal + +get_vector +~~~~~~~~~~ + +.. autofunction:: get_vector + +is_mosaic +~~~~~~~~~ + +.. autofunction:: is_mosaic + +nt_str +~~~~~~ + +.. autofunction:: nt_str + +read +~~~~ + +.. autofunction:: read + + +.. currentmodule:: nibabel.nicom.dicomreaders + + +:class:`DicomReadError` +~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: DicomReadError + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + +mosaic_to_nii +~~~~~~~~~~~~~ + +.. autofunction:: mosaic_to_nii + +read_mosaic_dir +~~~~~~~~~~~~~~~ + +.. autofunction:: read_mosaic_dir + +read_mosaic_dwi_dir +~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: read_mosaic_dwi_dir + +slices_to_series +~~~~~~~~~~~~~~~~ + +.. autofunction:: slices_to_series + + +.. currentmodule:: nibabel.nicom.dicomwrappers + + +:class:`FilterDwiIso` +~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: FilterDwiIso + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`FilterMultiStack` +~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: FilterMultiStack + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`FrameFilter` +~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: FrameFilter + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`MosaicWrapper` +~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: MosaicWrapper + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`MultiframeWrapper` +~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: MultiframeWrapper + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`SiemensWrapper` +~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: SiemensWrapper + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`Wrapper` +~~~~~~~~~~~~~~~~ + + +.. autoclass:: Wrapper + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`WrapperError` +~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: WrapperError + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`WrapperPrecisionError` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: WrapperPrecisionError + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + +none_or_close +~~~~~~~~~~~~~ + +.. autofunction:: none_or_close + +wrapper_from_data +~~~~~~~~~~~~~~~~~ + +.. autofunction:: wrapper_from_data + +wrapper_from_file +~~~~~~~~~~~~~~~~~ + +.. autofunction:: wrapper_from_file + + +.. currentmodule:: nibabel.nicom.dwiparams + +B2q +~~~ + +.. autofunction:: B2q + +nearest_pos_semi_def +~~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: nearest_pos_semi_def + +q2bg +~~~~ + +.. autofunction:: q2bg + + +.. currentmodule:: nibabel.nicom.structreader + + +:class:`Unpacker` +~~~~~~~~~~~~~~~~~ + + +.. autoclass:: Unpacker + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +.. currentmodule:: nibabel.nicom.utils + + +:class:`Vendor` +~~~~~~~~~~~~~~~ + + +.. autoclass:: Vendor + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + +find_private_section +~~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: find_private_section + +vendor_from_private +~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: vendor_from_private + diff --git a/_sources/reference/nibabel.nifti1.rst.txt b/_sources/reference/nibabel.nifti1.rst.txt new file mode 100644 index 0000000000..0e42acbd18 --- /dev/null +++ b/_sources/reference/nibabel.nifti1.rst.txt @@ -0,0 +1,129 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`nifti1` +============= +.. automodule:: nibabel.nifti1 + +.. currentmodule:: nibabel.nifti1 +.. autosummary:: + + Nifti1DicomExtension + Nifti1Extension + Nifti1Extensions + Nifti1Header + Nifti1Image + Nifti1Pair + Nifti1PairHeader + NiftiExtension + load + save + + +.. currentmodule:: nibabel.nifti1 + + +:class:`Nifti1DicomExtension` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: Nifti1DicomExtension + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`Nifti1Extension` +~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: Nifti1Extension + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`Nifti1Extensions` +~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: Nifti1Extensions + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`Nifti1Header` +~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: Nifti1Header + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`Nifti1Image` +~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: Nifti1Image + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`Nifti1Pair` +~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: Nifti1Pair + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`Nifti1PairHeader` +~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: Nifti1PairHeader + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`NiftiExtension` +~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: NiftiExtension + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + +load +~~~~ + +.. autofunction:: load + +save +~~~~ + +.. autofunction:: save + diff --git a/_sources/reference/nibabel.nifti2.rst.txt b/_sources/reference/nibabel.nifti2.rst.txt new file mode 100644 index 0000000000..072fb2afe3 --- /dev/null +++ b/_sources/reference/nibabel.nifti2.rst.txt @@ -0,0 +1,77 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`nifti2` +============= +.. automodule:: nibabel.nifti2 + +.. currentmodule:: nibabel.nifti2 +.. autosummary:: + + Nifti2Header + Nifti2Image + Nifti2Pair + Nifti2PairHeader + load + save + + +.. currentmodule:: nibabel.nifti2 + + +:class:`Nifti2Header` +~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: Nifti2Header + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`Nifti2Image` +~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: Nifti2Image + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`Nifti2Pair` +~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: Nifti2Pair + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`Nifti2PairHeader` +~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: Nifti2PairHeader + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + +load +~~~~ + +.. autofunction:: load + +save +~~~~ + +.. autofunction:: save + diff --git a/_sources/reference/nibabel.onetime.rst.txt b/_sources/reference/nibabel.onetime.rst.txt new file mode 100644 index 0000000000..19290491e6 --- /dev/null +++ b/_sources/reference/nibabel.onetime.rst.txt @@ -0,0 +1,26 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`onetime` +============== +.. automodule:: nibabel.onetime + +.. currentmodule:: nibabel.onetime +.. autosummary:: + + ResetMixin + + +.. currentmodule:: nibabel.onetime + + +:class:`ResetMixin` +~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: ResetMixin + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + diff --git a/_sources/reference/nibabel.openers.rst.txt b/_sources/reference/nibabel.openers.rst.txt new file mode 100644 index 0000000000..f01be129db --- /dev/null +++ b/_sources/reference/nibabel.openers.rst.txt @@ -0,0 +1,65 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`openers` +============== +.. automodule:: nibabel.openers + +.. currentmodule:: nibabel.openers +.. autosummary:: + + DeterministicGzipFile + Fileish + ImageOpener + Opener + + +.. currentmodule:: nibabel.openers + + +:class:`DeterministicGzipFile` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: DeterministicGzipFile + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`Fileish` +~~~~~~~~~~~~~~~~ + + +.. autoclass:: Fileish + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`ImageOpener` +~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: ImageOpener + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`Opener` +~~~~~~~~~~~~~~~ + + +.. autoclass:: Opener + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + diff --git a/_sources/reference/nibabel.optpkg.rst.txt b/_sources/reference/nibabel.optpkg.rst.txt new file mode 100644 index 0000000000..3d8b7d4c8f --- /dev/null +++ b/_sources/reference/nibabel.optpkg.rst.txt @@ -0,0 +1,19 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`optpkg` +============= +.. automodule:: nibabel.optpkg + +.. currentmodule:: nibabel.optpkg +.. autosummary:: + + optional_package + + +.. currentmodule:: nibabel.optpkg + +optional_package +~~~~~~~~~~~~~~~~ + +.. autofunction:: optional_package + diff --git a/_sources/reference/nibabel.orientations.rst.txt b/_sources/reference/nibabel.orientations.rst.txt new file mode 100644 index 0000000000..3eed93437d --- /dev/null +++ b/_sources/reference/nibabel.orientations.rst.txt @@ -0,0 +1,74 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`orientations` +=================== +.. automodule:: nibabel.orientations + +.. currentmodule:: nibabel.orientations +.. autosummary:: + + OrientationError + aff2axcodes + apply_orientation + axcodes2ornt + flip_axis + inv_ornt_aff + io_orientation + ornt2axcodes + ornt_transform + + +.. currentmodule:: nibabel.orientations + + +:class:`OrientationError` +~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: OrientationError + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + +aff2axcodes +~~~~~~~~~~~ + +.. autofunction:: aff2axcodes + +apply_orientation +~~~~~~~~~~~~~~~~~ + +.. autofunction:: apply_orientation + +axcodes2ornt +~~~~~~~~~~~~ + +.. autofunction:: axcodes2ornt + +flip_axis +~~~~~~~~~ + +.. autofunction:: flip_axis + +inv_ornt_aff +~~~~~~~~~~~~ + +.. autofunction:: inv_ornt_aff + +io_orientation +~~~~~~~~~~~~~~ + +.. autofunction:: io_orientation + +ornt2axcodes +~~~~~~~~~~~~ + +.. autofunction:: ornt2axcodes + +ornt_transform +~~~~~~~~~~~~~~ + +.. autofunction:: ornt_transform + diff --git a/_sources/reference/nibabel.parrec.rst.txt b/_sources/reference/nibabel.parrec.rst.txt new file mode 100644 index 0000000000..e115a415b3 --- /dev/null +++ b/_sources/reference/nibabel.parrec.rst.txt @@ -0,0 +1,95 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`parrec` +============= +.. automodule:: nibabel.parrec + +.. currentmodule:: nibabel.parrec +.. autosummary:: + + PARRECArrayProxy + PARRECError + PARRECHeader + PARRECImage + exts2pars + one_line + parse_PAR_header + vol_is_full + vol_numbers + + +.. currentmodule:: nibabel.parrec + + +:class:`PARRECArrayProxy` +~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: PARRECArrayProxy + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`PARRECError` +~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: PARRECError + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`PARRECHeader` +~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: PARRECHeader + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`PARRECImage` +~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: PARRECImage + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + +exts2pars +~~~~~~~~~ + +.. autofunction:: exts2pars + +one_line +~~~~~~~~ + +.. autofunction:: one_line + +parse_PAR_header +~~~~~~~~~~~~~~~~ + +.. autofunction:: parse_PAR_header + +vol_is_full +~~~~~~~~~~~ + +.. autofunction:: vol_is_full + +vol_numbers +~~~~~~~~~~~ + +.. autofunction:: vol_numbers + diff --git a/_sources/reference/nibabel.pointset.rst.txt b/_sources/reference/nibabel.pointset.rst.txt new file mode 100644 index 0000000000..6f9eac8f99 --- /dev/null +++ b/_sources/reference/nibabel.pointset.rst.txt @@ -0,0 +1,65 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`pointset` +=============== +.. automodule:: nibabel.pointset + +.. currentmodule:: nibabel.pointset +.. autosummary:: + + CoordinateArray + Grid + GridIndices + Pointset + + +.. currentmodule:: nibabel.pointset + + +:class:`CoordinateArray` +~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: CoordinateArray + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`Grid` +~~~~~~~~~~~~~ + + +.. autoclass:: Grid + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`GridIndices` +~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: GridIndices + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`Pointset` +~~~~~~~~~~~~~~~~~ + + +.. autoclass:: Pointset + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + diff --git a/_sources/reference/nibabel.processing.rst.txt b/_sources/reference/nibabel.processing.rst.txt new file mode 100644 index 0000000000..f58cdcb135 --- /dev/null +++ b/_sources/reference/nibabel.processing.rst.txt @@ -0,0 +1,55 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`processing` +================= +.. automodule:: nibabel.processing + +.. currentmodule:: nibabel.processing +.. autosummary:: + + adapt_affine + conform + fwhm2sigma + resample_from_to + resample_to_output + sigma2fwhm + smooth_image + + +.. currentmodule:: nibabel.processing + +adapt_affine +~~~~~~~~~~~~ + +.. autofunction:: adapt_affine + +conform +~~~~~~~ + +.. autofunction:: conform + +fwhm2sigma +~~~~~~~~~~ + +.. autofunction:: fwhm2sigma + +resample_from_to +~~~~~~~~~~~~~~~~ + +.. autofunction:: resample_from_to + +resample_to_output +~~~~~~~~~~~~~~~~~~ + +.. autofunction:: resample_to_output + +sigma2fwhm +~~~~~~~~~~ + +.. autofunction:: sigma2fwhm + +smooth_image +~~~~~~~~~~~~ + +.. autofunction:: smooth_image + diff --git a/_sources/reference/nibabel.pydicom_compat.rst.txt b/_sources/reference/nibabel.pydicom_compat.rst.txt new file mode 100644 index 0000000000..2860303b4a --- /dev/null +++ b/_sources/reference/nibabel.pydicom_compat.rst.txt @@ -0,0 +1,19 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`pydicom_compat` +===================== +.. automodule:: nibabel.pydicom_compat + +.. currentmodule:: nibabel.pydicom_compat +.. autosummary:: + + dicom_test + + +.. currentmodule:: nibabel.pydicom_compat + +dicom_test +~~~~~~~~~~ + +.. autofunction:: dicom_test + diff --git a/_sources/reference/nibabel.quaternions.rst.txt b/_sources/reference/nibabel.quaternions.rst.txt new file mode 100644 index 0000000000..2354787f7e --- /dev/null +++ b/_sources/reference/nibabel.quaternions.rst.txt @@ -0,0 +1,97 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`quaternions` +================== +.. automodule:: nibabel.quaternions + +.. currentmodule:: nibabel.quaternions +.. autosummary:: + + angle_axis2mat + angle_axis2quat + conjugate + eye + fillpositive + inverse + isunit + mat2quat + mult + nearly_equivalent + norm + quat2angle_axis + quat2mat + rotate_vector + + +.. currentmodule:: nibabel.quaternions + +angle_axis2mat +~~~~~~~~~~~~~~ + +.. autofunction:: angle_axis2mat + +angle_axis2quat +~~~~~~~~~~~~~~~ + +.. autofunction:: angle_axis2quat + +conjugate +~~~~~~~~~ + +.. autofunction:: conjugate + +eye +~~~ + +.. autofunction:: eye + +fillpositive +~~~~~~~~~~~~ + +.. autofunction:: fillpositive + +inverse +~~~~~~~ + +.. autofunction:: inverse + +isunit +~~~~~~ + +.. autofunction:: isunit + +mat2quat +~~~~~~~~ + +.. autofunction:: mat2quat + +mult +~~~~ + +.. autofunction:: mult + +nearly_equivalent +~~~~~~~~~~~~~~~~~ + +.. autofunction:: nearly_equivalent + +norm +~~~~ + +.. autofunction:: norm + +quat2angle_axis +~~~~~~~~~~~~~~~ + +.. autofunction:: quat2angle_axis + +quat2mat +~~~~~~~~ + +.. autofunction:: quat2mat + +rotate_vector +~~~~~~~~~~~~~ + +.. autofunction:: rotate_vector + diff --git a/_sources/reference/nibabel.rst.txt b/_sources/reference/nibabel.rst.txt new file mode 100644 index 0000000000..5508dc2830 --- /dev/null +++ b/_sources/reference/nibabel.rst.txt @@ -0,0 +1,31 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`nibabel` +============== +.. automodule:: nibabel + +.. currentmodule:: nibabel +.. autosummary:: + + bench + get_info + test + + +.. currentmodule:: nibabel + +bench +~~~~~ + +.. autofunction:: bench + +get_info +~~~~~~~~ + +.. autofunction:: get_info + +test +~~~~ + +.. autofunction:: test + diff --git a/_sources/reference/nibabel.rstutils.rst.txt b/_sources/reference/nibabel.rstutils.rst.txt new file mode 100644 index 0000000000..0068d5e0d5 --- /dev/null +++ b/_sources/reference/nibabel.rstutils.rst.txt @@ -0,0 +1,19 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`rstutils` +=============== +.. automodule:: nibabel.rstutils + +.. currentmodule:: nibabel.rstutils +.. autosummary:: + + rst_table + + +.. currentmodule:: nibabel.rstutils + +rst_table +~~~~~~~~~ + +.. autofunction:: rst_table + diff --git a/_sources/reference/nibabel.spaces.rst.txt b/_sources/reference/nibabel.spaces.rst.txt new file mode 100644 index 0000000000..ec46a383bd --- /dev/null +++ b/_sources/reference/nibabel.spaces.rst.txt @@ -0,0 +1,25 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`spaces` +============= +.. automodule:: nibabel.spaces + +.. currentmodule:: nibabel.spaces +.. autosummary:: + + slice2volume + vox2out_vox + + +.. currentmodule:: nibabel.spaces + +slice2volume +~~~~~~~~~~~~ + +.. autofunction:: slice2volume + +vox2out_vox +~~~~~~~~~~~ + +.. autofunction:: vox2out_vox + diff --git a/_sources/reference/nibabel.spatialimages.rst.txt b/_sources/reference/nibabel.spatialimages.rst.txt new file mode 100644 index 0000000000..ccf82f93b2 --- /dev/null +++ b/_sources/reference/nibabel.spatialimages.rst.txt @@ -0,0 +1,123 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`spatialimages` +==================== +.. automodule:: nibabel.spatialimages + +.. currentmodule:: nibabel.spatialimages +.. autosummary:: + + HasDtype + HeaderDataError + HeaderTypeError + ImageDataError + SpatialFirstSlicer + SpatialHeader + SpatialImage + SpatialProtocol + supported_np_types + + +.. currentmodule:: nibabel.spatialimages + + +:class:`HasDtype` +~~~~~~~~~~~~~~~~~ + + +.. autoclass:: HasDtype + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`HeaderDataError` +~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: HeaderDataError + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`HeaderTypeError` +~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: HeaderTypeError + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`ImageDataError` +~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: ImageDataError + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`SpatialFirstSlicer` +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: SpatialFirstSlicer + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`SpatialHeader` +~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: SpatialHeader + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`SpatialImage` +~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: SpatialImage + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`SpatialProtocol` +~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: SpatialProtocol + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + +supported_np_types +~~~~~~~~~~~~~~~~~~ + +.. autofunction:: supported_np_types + diff --git a/_sources/reference/nibabel.spm2analyze.rst.txt b/_sources/reference/nibabel.spm2analyze.rst.txt new file mode 100644 index 0000000000..ac8d29e3d2 --- /dev/null +++ b/_sources/reference/nibabel.spm2analyze.rst.txt @@ -0,0 +1,39 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`spm2analyze` +================== +.. automodule:: nibabel.spm2analyze + +.. currentmodule:: nibabel.spm2analyze +.. autosummary:: + + Spm2AnalyzeHeader + Spm2AnalyzeImage + + +.. currentmodule:: nibabel.spm2analyze + + +:class:`Spm2AnalyzeHeader` +~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: Spm2AnalyzeHeader + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`Spm2AnalyzeImage` +~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: Spm2AnalyzeImage + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + diff --git a/_sources/reference/nibabel.spm99analyze.rst.txt b/_sources/reference/nibabel.spm99analyze.rst.txt new file mode 100644 index 0000000000..8c02fdefdb --- /dev/null +++ b/_sources/reference/nibabel.spm99analyze.rst.txt @@ -0,0 +1,52 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`spm99analyze` +=================== +.. automodule:: nibabel.spm99analyze + +.. currentmodule:: nibabel.spm99analyze +.. autosummary:: + + Spm99AnalyzeHeader + Spm99AnalyzeImage + SpmAnalyzeHeader + + +.. currentmodule:: nibabel.spm99analyze + + +:class:`Spm99AnalyzeHeader` +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: Spm99AnalyzeHeader + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`Spm99AnalyzeImage` +~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: Spm99AnalyzeImage + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`SpmAnalyzeHeader` +~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: SpmAnalyzeHeader + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + diff --git a/_sources/reference/nibabel.streamlines.rst.txt b/_sources/reference/nibabel.streamlines.rst.txt new file mode 100644 index 0000000000..1b5e3ea022 --- /dev/null +++ b/_sources/reference/nibabel.streamlines.rst.txt @@ -0,0 +1,434 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`streamlines` +================== +.. automodule:: nibabel.streamlines + +.. currentmodule:: nibabel.streamlines +.. autosummary:: + + detect_format + is_supported + load + save + +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +Module: :mod:`streamlines.array_sequence` +----------------------------------------- +.. automodule:: nibabel.streamlines.array_sequence + +.. currentmodule:: nibabel.streamlines.array_sequence +.. autosummary:: + + ArraySequence + concatenate + create_arraysequences_from_generator + is_array_sequence + is_ndarray_of_int_or_bool + +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +Module: :mod:`streamlines.header` +--------------------------------- +.. automodule:: nibabel.streamlines.header + +.. currentmodule:: nibabel.streamlines.header +.. autosummary:: + + Field + +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +Module: :mod:`streamlines.tck` +------------------------------ +.. automodule:: nibabel.streamlines.tck + +.. currentmodule:: nibabel.streamlines.tck +.. autosummary:: + + TckFile + +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +Module: :mod:`streamlines.tractogram` +------------------------------------- +.. automodule:: nibabel.streamlines.tractogram + +.. currentmodule:: nibabel.streamlines.tractogram +.. autosummary:: + + LazyDict + LazyTractogram + PerArrayDict + PerArraySequenceDict + SliceableDataDict + Tractogram + TractogramItem + is_data_dict + is_lazy_dict + +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +Module: :mod:`streamlines.tractogram_file` +------------------------------------------ +.. automodule:: nibabel.streamlines.tractogram_file + +.. currentmodule:: nibabel.streamlines.tractogram_file +.. autosummary:: + + DataError + DataWarning + ExtensionWarning + HeaderError + HeaderWarning + TractogramFile + abstractclassmethod + +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +Module: :mod:`streamlines.trk` +------------------------------ +.. automodule:: nibabel.streamlines.trk + +.. currentmodule:: nibabel.streamlines.trk +.. autosummary:: + + TrkFile + decode_value_from_name + encode_value_in_name + get_affine_rasmm_to_trackvis + get_affine_trackvis_to_rasmm + +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +Module: :mod:`streamlines.utils` +-------------------------------- +.. automodule:: nibabel.streamlines.utils + +.. currentmodule:: nibabel.streamlines.utils +.. autosummary:: + + get_affine_from_reference + peek_next + + +.. currentmodule:: nibabel.streamlines + +detect_format +~~~~~~~~~~~~~ + +.. autofunction:: detect_format + +is_supported +~~~~~~~~~~~~ + +.. autofunction:: is_supported + +load +~~~~ + +.. autofunction:: load + +save +~~~~ + +.. autofunction:: save + + +.. currentmodule:: nibabel.streamlines.array_sequence + + +:class:`ArraySequence` +~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: ArraySequence + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + +concatenate +~~~~~~~~~~~ + +.. autofunction:: concatenate + +create_arraysequences_from_generator +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: create_arraysequences_from_generator + +is_array_sequence +~~~~~~~~~~~~~~~~~ + +.. autofunction:: is_array_sequence + +is_ndarray_of_int_or_bool +~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: is_ndarray_of_int_or_bool + + +.. currentmodule:: nibabel.streamlines.header + + +:class:`Field` +~~~~~~~~~~~~~~ + + +.. autoclass:: Field + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +.. currentmodule:: nibabel.streamlines.tck + + +:class:`TckFile` +~~~~~~~~~~~~~~~~ + + +.. autoclass:: TckFile + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +.. currentmodule:: nibabel.streamlines.tractogram + + +:class:`LazyDict` +~~~~~~~~~~~~~~~~~ + + +.. autoclass:: LazyDict + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`LazyTractogram` +~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: LazyTractogram + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`PerArrayDict` +~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: PerArrayDict + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`PerArraySequenceDict` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: PerArraySequenceDict + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`SliceableDataDict` +~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: SliceableDataDict + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`Tractogram` +~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: Tractogram + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`TractogramItem` +~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: TractogramItem + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + +is_data_dict +~~~~~~~~~~~~ + +.. autofunction:: is_data_dict + +is_lazy_dict +~~~~~~~~~~~~ + +.. autofunction:: is_lazy_dict + + +.. currentmodule:: nibabel.streamlines.tractogram_file + + +:class:`DataError` +~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: DataError + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`DataWarning` +~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: DataWarning + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`ExtensionWarning` +~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: ExtensionWarning + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`HeaderError` +~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: HeaderError + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`HeaderWarning` +~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: HeaderWarning + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`TractogramFile` +~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: TractogramFile + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`abstractclassmethod` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: abstractclassmethod + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +.. currentmodule:: nibabel.streamlines.trk + + +:class:`TrkFile` +~~~~~~~~~~~~~~~~ + + +.. autoclass:: TrkFile + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + +decode_value_from_name +~~~~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: decode_value_from_name + +encode_value_in_name +~~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: encode_value_in_name + +get_affine_rasmm_to_trackvis +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: get_affine_rasmm_to_trackvis + +get_affine_trackvis_to_rasmm +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: get_affine_trackvis_to_rasmm + + +.. currentmodule:: nibabel.streamlines.utils + +get_affine_from_reference +~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: get_affine_from_reference + +peek_next +~~~~~~~~~ + +.. autofunction:: peek_next + diff --git a/_sources/reference/nibabel.tmpdirs.rst.txt b/_sources/reference/nibabel.tmpdirs.rst.txt new file mode 100644 index 0000000000..633f86d94f --- /dev/null +++ b/_sources/reference/nibabel.tmpdirs.rst.txt @@ -0,0 +1,38 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`tmpdirs` +============== +.. automodule:: nibabel.tmpdirs + +.. currentmodule:: nibabel.tmpdirs +.. autosummary:: + + TemporaryDirectory + InGivenDirectory + InTemporaryDirectory + + +.. currentmodule:: nibabel.tmpdirs + + +:class:`TemporaryDirectory` +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: TemporaryDirectory + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + +InGivenDirectory +~~~~~~~~~~~~~~~~ + +.. autofunction:: InGivenDirectory + +InTemporaryDirectory +~~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: InTemporaryDirectory + diff --git a/_sources/reference/nibabel.tripwire.rst.txt b/_sources/reference/nibabel.tripwire.rst.txt new file mode 100644 index 0000000000..1829eb239c --- /dev/null +++ b/_sources/reference/nibabel.tripwire.rst.txt @@ -0,0 +1,45 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`tripwire` +=============== +.. automodule:: nibabel.tripwire + +.. currentmodule:: nibabel.tripwire +.. autosummary:: + + TripWire + TripWireError + is_tripwire + + +.. currentmodule:: nibabel.tripwire + + +:class:`TripWire` +~~~~~~~~~~~~~~~~~ + + +.. autoclass:: TripWire + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`TripWireError` +~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: TripWireError + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + +is_tripwire +~~~~~~~~~~~ + +.. autofunction:: is_tripwire + diff --git a/_sources/reference/nibabel.viewers.rst.txt b/_sources/reference/nibabel.viewers.rst.txt new file mode 100644 index 0000000000..652e29d602 --- /dev/null +++ b/_sources/reference/nibabel.viewers.rst.txt @@ -0,0 +1,26 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`viewers` +============== +.. automodule:: nibabel.viewers + +.. currentmodule:: nibabel.viewers +.. autosummary:: + + OrthoSlicer3D + + +.. currentmodule:: nibabel.viewers + + +:class:`OrthoSlicer3D` +~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: OrthoSlicer3D + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + diff --git a/_sources/reference/nibabel.volumeutils.rst.txt b/_sources/reference/nibabel.volumeutils.rst.txt new file mode 100644 index 0000000000..617027df94 --- /dev/null +++ b/_sources/reference/nibabel.volumeutils.rst.txt @@ -0,0 +1,129 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`volumeutils` +================== +.. automodule:: nibabel.volumeutils + +.. currentmodule:: nibabel.volumeutils +.. autosummary:: + + DtypeMapper + Recoder + apply_read_scaling + array_from_file + array_to_file + best_write_scale_ftype + better_float_of + finite_range + fname_ext_ul_case + int_scinter_ftype + make_dt_codes + pretty_mapping + rec2dict + seek_tell + shape_zoom_affine + working_type + write_zeros + + +.. currentmodule:: nibabel.volumeutils + + +:class:`DtypeMapper` +~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: DtypeMapper + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`Recoder` +~~~~~~~~~~~~~~~~ + + +.. autoclass:: Recoder + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + +apply_read_scaling +~~~~~~~~~~~~~~~~~~ + +.. autofunction:: apply_read_scaling + +array_from_file +~~~~~~~~~~~~~~~ + +.. autofunction:: array_from_file + +array_to_file +~~~~~~~~~~~~~ + +.. autofunction:: array_to_file + +best_write_scale_ftype +~~~~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: best_write_scale_ftype + +better_float_of +~~~~~~~~~~~~~~~ + +.. autofunction:: better_float_of + +finite_range +~~~~~~~~~~~~ + +.. autofunction:: finite_range + +fname_ext_ul_case +~~~~~~~~~~~~~~~~~ + +.. autofunction:: fname_ext_ul_case + +int_scinter_ftype +~~~~~~~~~~~~~~~~~ + +.. autofunction:: int_scinter_ftype + +make_dt_codes +~~~~~~~~~~~~~ + +.. autofunction:: make_dt_codes + +pretty_mapping +~~~~~~~~~~~~~~ + +.. autofunction:: pretty_mapping + +rec2dict +~~~~~~~~ + +.. autofunction:: rec2dict + +seek_tell +~~~~~~~~~ + +.. autofunction:: seek_tell + +shape_zoom_affine +~~~~~~~~~~~~~~~~~ + +.. autofunction:: shape_zoom_affine + +working_type +~~~~~~~~~~~~ + +.. autofunction:: working_type + +write_zeros +~~~~~~~~~~~ + +.. autofunction:: write_zeros + diff --git a/_sources/reference/nibabel.wrapstruct.rst.txt b/_sources/reference/nibabel.wrapstruct.rst.txt new file mode 100644 index 0000000000..63a27ccad4 --- /dev/null +++ b/_sources/reference/nibabel.wrapstruct.rst.txt @@ -0,0 +1,52 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`wrapstruct` +================= +.. automodule:: nibabel.wrapstruct + +.. currentmodule:: nibabel.wrapstruct +.. autosummary:: + + LabeledWrapStruct + WrapStruct + WrapStructError + + +.. currentmodule:: nibabel.wrapstruct + + +:class:`LabeledWrapStruct` +~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: LabeledWrapStruct + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`WrapStruct` +~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: WrapStruct + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`WrapStructError` +~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: WrapStructError + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + diff --git a/_sources/reference/nibabel.xmlutils.rst.txt b/_sources/reference/nibabel.xmlutils.rst.txt new file mode 100644 index 0000000000..1a8863272f --- /dev/null +++ b/_sources/reference/nibabel.xmlutils.rst.txt @@ -0,0 +1,52 @@ +.. AUTO-GENERATED FILE -- DO NOT EDIT! + +:mod:`xmlutils` +=============== +.. automodule:: nibabel.xmlutils + +.. currentmodule:: nibabel.xmlutils +.. autosummary:: + + XmlBasedHeader + XmlParser + XmlSerializable + + +.. currentmodule:: nibabel.xmlutils + + +:class:`XmlBasedHeader` +~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: XmlBasedHeader + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`XmlParser` +~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: XmlParser + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + + +:class:`XmlSerializable` +~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. autoclass:: XmlSerializable + :members: + :undoc-members: + :show-inheritance: + + .. automethod:: __init__ + diff --git a/doc/source/tutorials.rst b/_sources/tutorials.rst.txt similarity index 100% rename from doc/source/tutorials.rst rename to _sources/tutorials.rst.txt diff --git a/_static/basic.css b/_static/basic.css new file mode 100644 index 0000000000..f316efcb47 --- /dev/null +++ b/_static/basic.css @@ -0,0 +1,925 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +div.section::after { + display: block; + content: ''; + clear: left; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(http://github.com/file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li p.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: 360px; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +a:visited { + color: #551A8B; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, figure.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, figure.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, figure.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, figure.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar, +aside.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px; + background-color: #ffe; + width: 40%; + float: right; + clear: right; + overflow-x: auto; +} + +p.sidebar-title { + font-weight: bold; +} + +nav.contents, +aside.topic, +div.admonition, div.topic, blockquote { + clear: left; +} + +/* -- topics ---------------------------------------------------------------- */ + +nav.contents, +aside.topic, +div.topic { + border: 1px solid #ccc; + padding: 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +aside.sidebar > :last-child, +nav.contents > :last-child, +aside.topic > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + +div.sidebar::after, +aside.sidebar::after, +nav.contents::after, +aside.topic::after, +div.topic::after, +div.admonition::after, +blockquote::after { + display: block; + content: ''; + clear: both; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + margin-top: 10px; + margin-bottom: 10px; + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > :first-child, +td > :first-child { + margin-top: 0px; +} + +th > :last-child, +td > :last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure, figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption, figcaption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number, +figcaption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text, +figcaption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist { + margin: 1em 0; +} + +table.hlist td { + vertical-align: top; +} + +/* -- object description styles --------------------------------------------- */ + +.sig { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; +} + +.sig-name, code.descname { + background-color: transparent; + font-weight: bold; +} + +.sig-name { + font-size: 1.1em; +} + +code.descname { + font-size: 1.2em; +} + +.sig-prename, code.descclassname { + background-color: transparent; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.sig-param.n { + font-style: italic; +} + +/* C++ specific styling */ + +.sig-inline.c-texpr, +.sig-inline.cpp-texpr { + font-family: unset; +} + +.sig.c .k, .sig.c .kt, +.sig.cpp .k, .sig.cpp .kt { + color: #0033B3; +} + +.sig.c .m, +.sig.cpp .m { + color: #1750EB; +} + +.sig.c .s, .sig.c .sc, +.sig.cpp .s, .sig.cpp .sc { + color: #067D17; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +:not(li) > ol > li:first-child > :first-child, +:not(li) > ul > li:first-child > :first-child { + margin-top: 0px; +} + +:not(li) > ol > li:last-child > :last-child, +:not(li) > ul > li:last-child > :last-child { + margin-bottom: 0px; +} + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} + +aside.footnote > span, +div.citation > span { + float: left; +} +aside.footnote > span:last-of-type, +div.citation > span:last-of-type { + padding-right: 0.5em; +} +aside.footnote > p { + margin-left: 2em; +} +div.citation > p { + margin-left: 4em; +} +aside.footnote > p:last-of-type, +div.citation > p:last-of-type { + margin-bottom: 0em; +} +aside.footnote > p:last-of-type:after, +div.citation > p:last-of-type:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > :first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +.sig dd { + margin-top: 0px; + margin-bottom: 0px; +} + +.sig dl { + margin-top: 0px; + margin-bottom: 0px; +} + +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0 0.5em; + content: ":"; + display: inline-block; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +.translated { + background-color: rgba(207, 255, 207, 0.2) +} + +.untranslated { + background-color: rgba(255, 207, 207, 0.2) +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +pre, div[class*="highlight-"] { + clear: both; +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; + white-space: nowrap; +} + +div[class*="highlight-"] { + margin: 1em 0; +} + +td.linenos pre { + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + display: block; +} + +table.highlighttable tbody { + display: block; +} + +table.highlighttable tr { + display: flex; +} + +table.highlighttable td { + margin: 0; + padding: 0; +} + +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +table.highlighttable td.code { + flex: 1; + overflow: hidden; +} + +.highlight .hll { + display: block; +} + +div.highlight pre, +table.highlighttable pre { + margin: 0; +} + +div.code-block-caption + div { + margin-top: 0; +} + +div.code-block-caption { + margin-top: 1em; + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +table.highlighttable td.linenos, +span.linenos, +div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; + -webkit-user-select: text; /* Safari fallback only */ + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+ */ +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + margin: 1em 0; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: absolute; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/_static/contents.png b/_static/contents.png new file mode 100644 index 0000000000..6c59aa1f9c Binary files /dev/null and b/_static/contents.png differ diff --git a/_static/doctools.js b/_static/doctools.js new file mode 100644 index 0000000000..4d67807d17 --- /dev/null +++ b/_static/doctools.js @@ -0,0 +1,156 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Base JavaScript utilities for all Sphinx HTML documentation. + * + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ + "TEXTAREA", + "INPUT", + "SELECT", + "BUTTON", +]); + +const _ready = (callback) => { + if (document.readyState !== "loading") { + callback(); + } else { + document.addEventListener("DOMContentLoaded", callback); + } +}; + +/** + * Small JavaScript module for the documentation. + */ +const Documentation = { + init: () => { + Documentation.initDomainIndexTable(); + Documentation.initOnKeyListeners(); + }, + + /** + * i18n support + */ + TRANSLATIONS: {}, + PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), + LOCALE: "unknown", + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext: (string) => { + const translated = Documentation.TRANSLATIONS[string]; + switch (typeof translated) { + case "undefined": + return string; // no translation + case "string": + return translated; // translation exists + default: + return translated[0]; // (singular, plural) translation tuple exists + } + }, + + ngettext: (singular, plural, n) => { + const translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated !== "undefined") + return translated[Documentation.PLURAL_EXPR(n)]; + return n === 1 ? singular : plural; + }, + + addTranslations: (catalog) => { + Object.assign(Documentation.TRANSLATIONS, catalog.messages); + Documentation.PLURAL_EXPR = new Function( + "n", + `return (${catalog.plural_expr})` + ); + Documentation.LOCALE = catalog.locale; + }, + + /** + * helper function to focus on search bar + */ + focusSearchBar: () => { + document.querySelectorAll("input[name=q]")[0]?.focus(); + }, + + /** + * Initialise the domain index toggle buttons + */ + initDomainIndexTable: () => { + const toggler = (el) => { + const idNumber = el.id.substr(7); + const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); + if (el.src.substr(-9) === "minus.png") { + el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; + toggledRows.forEach((el) => (el.style.display = "none")); + } else { + el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; + toggledRows.forEach((el) => (el.style.display = "")); + } + }; + + const togglerElements = document.querySelectorAll("img.toggler"); + togglerElements.forEach((el) => + el.addEventListener("click", (event) => toggler(event.currentTarget)) + ); + togglerElements.forEach((el) => (el.style.display = "")); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); + }, + + initOnKeyListeners: () => { + // only install a listener if it is really needed + if ( + !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && + !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS + ) + return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.altKey || event.ctrlKey || event.metaKey) return; + + if (!event.shiftKey) { + switch (event.key) { + case "ArrowLeft": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const prevLink = document.querySelector('link[rel="prev"]'); + if (prevLink && prevLink.href) { + window.location.href = prevLink.href; + event.preventDefault(); + } + break; + case "ArrowRight": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const nextLink = document.querySelector('link[rel="next"]'); + if (nextLink && nextLink.href) { + window.location.href = nextLink.href; + event.preventDefault(); + } + break; + } + } + + // some keyboard layouts may need Shift to get / + switch (event.key) { + case "/": + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; + Documentation.focusSearchBar(); + event.preventDefault(); + } + }); + }, +}; + +// quick alias for translations +const _ = Documentation.gettext; + +_ready(Documentation.init); diff --git a/_static/documentation_options.js b/_static/documentation_options.js new file mode 100644 index 0000000000..9440763000 --- /dev/null +++ b/_static/documentation_options.js @@ -0,0 +1,13 @@ +const DOCUMENTATION_OPTIONS = { + VERSION: '5.4.0.dev1+g3b1c7b37', + LANGUAGE: 'en', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false, + SHOW_SEARCH_SUMMARY: true, + ENABLE_SEARCH_SHORTCUTS: true, +}; \ No newline at end of file diff --git a/_static/file.png b/_static/file.png new file mode 100644 index 0000000000..a858a410e4 Binary files /dev/null and b/_static/file.png differ diff --git a/_static/graphviz.css b/_static/graphviz.css new file mode 100644 index 0000000000..027576e34d --- /dev/null +++ b/_static/graphviz.css @@ -0,0 +1,19 @@ +/* + * graphviz.css + * ~~~~~~~~~~~~ + * + * Sphinx stylesheet -- graphviz extension. + * + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +img.graphviz { + border: 0; + max-width: 100%; +} + +object.graphviz { + max-width: 100%; +} diff --git a/doc/source/_static/item.png b/_static/item.png similarity index 100% rename from doc/source/_static/item.png rename to _static/item.png diff --git a/_static/language_data.js b/_static/language_data.js new file mode 100644 index 0000000000..367b8ed81b --- /dev/null +++ b/_static/language_data.js @@ -0,0 +1,199 @@ +/* + * language_data.js + * ~~~~~~~~~~~~~~~~ + * + * This script contains the language-specific data used by searchtools.js, + * namely the list of stopwords, stemmer, scorer and splitter. + * + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; + + +/* Non-minified version is copied as a separate JS file, if available */ + +/** + * Porter Stemmer + */ +var Stemmer = function() { + + var step2list = { + ational: 'ate', + tional: 'tion', + enci: 'ence', + anci: 'ance', + izer: 'ize', + bli: 'ble', + alli: 'al', + entli: 'ent', + eli: 'e', + ousli: 'ous', + ization: 'ize', + ation: 'ate', + ator: 'ate', + alism: 'al', + iveness: 'ive', + fulness: 'ful', + ousness: 'ous', + aliti: 'al', + iviti: 'ive', + biliti: 'ble', + logi: 'log' + }; + + var step3list = { + icate: 'ic', + ative: '', + alize: 'al', + iciti: 'ic', + ical: 'ic', + ful: '', + ness: '' + }; + + var c = "[^aeiou]"; // consonant + var v = "[aeiouy]"; // vowel + var C = c + "[^aeiouy]*"; // consonant sequence + var V = v + "[aeiou]*"; // vowel sequence + + var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 + var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 + var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 + var s_v = "^(" + C + ")?" + v; // vowel in stem + + this.stemWord = function (w) { + var stem; + var suffix; + var firstch; + var origword = w; + + if (w.length < 3) + return w; + + var re; + var re2; + var re3; + var re4; + + firstch = w.substr(0,1); + if (firstch == "y") + w = firstch.toUpperCase() + w.substr(1); + + // Step 1a + re = /^(.+?)(ss|i)es$/; + re2 = /^(.+?)([^s])s$/; + + if (re.test(w)) + w = w.replace(re,"$1$2"); + else if (re2.test(w)) + w = w.replace(re2,"$1$2"); + + // Step 1b + re = /^(.+?)eed$/; + re2 = /^(.+?)(ed|ing)$/; + if (re.test(w)) { + var fp = re.exec(w); + re = new RegExp(mgr0); + if (re.test(fp[1])) { + re = /.$/; + w = w.replace(re,""); + } + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = new RegExp(s_v); + if (re2.test(stem)) { + w = stem; + re2 = /(at|bl|iz)$/; + re3 = new RegExp("([^aeiouylsz])\\1$"); + re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re2.test(w)) + w = w + "e"; + else if (re3.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + else if (re4.test(w)) + w = w + "e"; + } + } + + // Step 1c + re = /^(.+?)y$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(s_v); + if (re.test(stem)) + w = stem + "i"; + } + + // Step 2 + re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step2list[suffix]; + } + + // Step 3 + re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step3list[suffix]; + } + + // Step 4 + re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; + re2 = /^(.+?)(s|t)(ion)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + if (re.test(stem)) + w = stem; + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = new RegExp(mgr1); + if (re2.test(stem)) + w = stem; + } + + // Step 5 + re = /^(.+?)e$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + re2 = new RegExp(meq1); + re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) + w = stem; + } + re = /ll$/; + re2 = new RegExp(mgr1); + if (re.test(w) && re2.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + + // and turn initial Y back to y + if (firstch == "y") + w = firstch.toLowerCase() + w.substr(1); + return w; + } +} + diff --git a/_static/minus.png b/_static/minus.png new file mode 100644 index 0000000000..d96755fdaf Binary files /dev/null and b/_static/minus.png differ diff --git a/_static/navigation.png b/_static/navigation.png new file mode 100644 index 0000000000..fda6cd29ed Binary files /dev/null and b/_static/navigation.png differ diff --git a/doc/source/_static/nibabel-logo.svg b/_static/nibabel-logo.svg similarity index 100% rename from doc/source/_static/nibabel-logo.svg rename to _static/nibabel-logo.svg diff --git a/doc/source/_static/nibabel.css b/_static/nibabel.css similarity index 100% rename from doc/source/_static/nibabel.css rename to _static/nibabel.css diff --git a/doc/source/_static/nipy-logo-bg-138x120.png b/_static/nipy-logo-bg-138x120.png similarity index 100% rename from doc/source/_static/nipy-logo-bg-138x120.png rename to _static/nipy-logo-bg-138x120.png diff --git a/doc/source/_static/nipy.css b/_static/nipy.css similarity index 100% rename from doc/source/_static/nipy.css rename to _static/nipy.css diff --git a/_static/plot_directive.css b/_static/plot_directive.css new file mode 100644 index 0000000000..d45593c93c --- /dev/null +++ b/_static/plot_directive.css @@ -0,0 +1,16 @@ +/* + * plot_directive.css + * ~~~~~~~~~~~~ + * + * Stylesheet controlling images created using the `plot` directive within + * Sphinx. + * + * :copyright: Copyright 2020-* by the Matplotlib development team. + * :license: Matplotlib, see LICENSE for details. + * + */ + +img.plot-directive { + border: 0; + max-width: 100%; +} diff --git a/_static/plus.png b/_static/plus.png new file mode 100644 index 0000000000..7107cec93a Binary files /dev/null and b/_static/plus.png differ diff --git a/_static/pygments.css b/_static/pygments.css new file mode 100644 index 0000000000..0d49244eda --- /dev/null +++ b/_static/pygments.css @@ -0,0 +1,75 @@ +pre { line-height: 125%; } +td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.highlight .hll { background-color: #ffffcc } +.highlight { background: #eeffcc; } +.highlight .c { color: #408090; font-style: italic } /* Comment */ +.highlight .err { border: 1px solid #FF0000 } /* Error */ +.highlight .k { color: #007020; font-weight: bold } /* Keyword */ +.highlight .o { color: #666666 } /* Operator */ +.highlight .ch { color: #408090; font-style: italic } /* Comment.Hashbang */ +.highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #007020 } /* Comment.Preproc */ +.highlight .cpf { color: #408090; font-style: italic } /* Comment.PreprocFile */ +.highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */ +.highlight .gd { color: #A00000 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ +.highlight .gr { color: #FF0000 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #00A000 } /* Generic.Inserted */ +.highlight .go { color: #333333 } /* Generic.Output */ +.highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #0044DD } /* Generic.Traceback */ +.highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #007020 } /* Keyword.Pseudo */ +.highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #902000 } /* Keyword.Type */ +.highlight .m { color: #208050 } /* Literal.Number */ +.highlight .s { color: #4070a0 } /* Literal.String */ +.highlight .na { color: #4070a0 } /* Name.Attribute */ +.highlight .nb { color: #007020 } /* Name.Builtin */ +.highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ +.highlight .no { color: #60add5 } /* Name.Constant */ +.highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ +.highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ +.highlight .ne { color: #007020 } /* Name.Exception */ +.highlight .nf { color: #06287e } /* Name.Function */ +.highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ +.highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ +.highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #bb60d5 } /* Name.Variable */ +.highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mb { color: #208050 } /* Literal.Number.Bin */ +.highlight .mf { color: #208050 } /* Literal.Number.Float */ +.highlight .mh { color: #208050 } /* Literal.Number.Hex */ +.highlight .mi { color: #208050 } /* Literal.Number.Integer */ +.highlight .mo { color: #208050 } /* Literal.Number.Oct */ +.highlight .sa { color: #4070a0 } /* Literal.String.Affix */ +.highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ +.highlight .sc { color: #4070a0 } /* Literal.String.Char */ +.highlight .dl { color: #4070a0 } /* Literal.String.Delimiter */ +.highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #4070a0 } /* Literal.String.Double */ +.highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ +.highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ +.highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ +.highlight .sx { color: #c65d09 } /* Literal.String.Other */ +.highlight .sr { color: #235388 } /* Literal.String.Regex */ +.highlight .s1 { color: #4070a0 } /* Literal.String.Single */ +.highlight .ss { color: #517918 } /* Literal.String.Symbol */ +.highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #06287e } /* Name.Function.Magic */ +.highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ +.highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ +.highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ +.highlight .vm { color: #bb60d5 } /* Name.Variable.Magic */ +.highlight .il { color: #208050 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/doc/source/_static/reggie.png b/_static/reggie.png similarity index 100% rename from doc/source/_static/reggie.png rename to _static/reggie.png diff --git a/_static/searchtools.js b/_static/searchtools.js new file mode 100644 index 0000000000..b08d58c9b9 --- /dev/null +++ b/_static/searchtools.js @@ -0,0 +1,620 @@ +/* + * searchtools.js + * ~~~~~~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for the full-text search. + * + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +/** + * Simple result scoring code. + */ +if (typeof Scorer === "undefined") { + var Scorer = { + // Implement the following function to further tweak the score for each result + // The function takes a result array [docname, title, anchor, descr, score, filename] + // and returns the new score. + /* + score: result => { + const [docname, title, anchor, descr, score, filename] = result + return score + }, + */ + + // query matches the full name of an object + objNameMatch: 11, + // or matches in the last dotted part of the object name + objPartialMatch: 6, + // Additive scores depending on the priority of the object + objPrio: { + 0: 15, // used to be importantResults + 1: 5, // used to be objectResults + 2: -5, // used to be unimportantResults + }, + // Used when the priority is not in the mapping. + objPrioDefault: 0, + + // query found in title + title: 15, + partialTitle: 7, + // query found in terms + term: 5, + partialTerm: 2, + }; +} + +const _removeChildren = (element) => { + while (element && element.lastChild) element.removeChild(element.lastChild); +}; + +/** + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping + */ +const _escapeRegExp = (string) => + string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string + +const _displayItem = (item, searchTerms, highlightTerms) => { + const docBuilder = DOCUMENTATION_OPTIONS.BUILDER; + const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX; + const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX; + const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; + const contentRoot = document.documentElement.dataset.content_root; + + const [docName, title, anchor, descr, score, _filename] = item; + + let listItem = document.createElement("li"); + let requestUrl; + let linkUrl; + if (docBuilder === "dirhtml") { + // dirhtml builder + let dirname = docName + "/"; + if (dirname.match(/\/index\/$/)) + dirname = dirname.substring(0, dirname.length - 6); + else if (dirname === "index/") dirname = ""; + requestUrl = contentRoot + dirname; + linkUrl = requestUrl; + } else { + // normal html builders + requestUrl = contentRoot + docName + docFileSuffix; + linkUrl = docName + docLinkSuffix; + } + let linkEl = listItem.appendChild(document.createElement("a")); + linkEl.href = linkUrl + anchor; + linkEl.dataset.score = score; + linkEl.innerHTML = title; + if (descr) { + listItem.appendChild(document.createElement("span")).innerHTML = + " (" + descr + ")"; + // highlight search terms in the description + if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js + highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); + } + else if (showSearchSummary) + fetch(requestUrl) + .then((responseData) => responseData.text()) + .then((data) => { + if (data) + listItem.appendChild( + Search.makeSearchSummary(data, searchTerms, anchor) + ); + // highlight search terms in the summary + if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js + highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); + }); + Search.output.appendChild(listItem); +}; +const _finishSearch = (resultCount) => { + Search.stopPulse(); + Search.title.innerText = _("Search Results"); + if (!resultCount) + Search.status.innerText = Documentation.gettext( + "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." + ); + else + Search.status.innerText = _( + "Search finished, found ${resultCount} page(s) matching the search query." + ).replace('${resultCount}', resultCount); +}; +const _displayNextItem = ( + results, + resultCount, + searchTerms, + highlightTerms, +) => { + // results left, load the summary and display it + // this is intended to be dynamic (don't sub resultsCount) + if (results.length) { + _displayItem(results.pop(), searchTerms, highlightTerms); + setTimeout( + () => _displayNextItem(results, resultCount, searchTerms, highlightTerms), + 5 + ); + } + // search finished, update title and status message + else _finishSearch(resultCount); +}; +// Helper function used by query() to order search results. +// Each input is an array of [docname, title, anchor, descr, score, filename]. +// Order the results by score (in opposite order of appearance, since the +// `_displayNextItem` function uses pop() to retrieve items) and then alphabetically. +const _orderResultsByScoreThenName = (a, b) => { + const leftScore = a[4]; + const rightScore = b[4]; + if (leftScore === rightScore) { + // same score: sort alphabetically + const leftTitle = a[1].toLowerCase(); + const rightTitle = b[1].toLowerCase(); + if (leftTitle === rightTitle) return 0; + return leftTitle > rightTitle ? -1 : 1; // inverted is intentional + } + return leftScore > rightScore ? 1 : -1; +}; + +/** + * Default splitQuery function. Can be overridden in ``sphinx.search`` with a + * custom function per language. + * + * The regular expression works by splitting the string on consecutive characters + * that are not Unicode letters, numbers, underscores, or emoji characters. + * This is the same as ``\W+`` in Python, preserving the surrogate pair area. + */ +if (typeof splitQuery === "undefined") { + var splitQuery = (query) => query + .split(/[^\p{Letter}\p{Number}_\p{Emoji_Presentation}]+/gu) + .filter(term => term) // remove remaining empty strings +} + +/** + * Search Module + */ +const Search = { + _index: null, + _queued_query: null, + _pulse_status: -1, + + htmlToText: (htmlString, anchor) => { + const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html'); + for (const removalQuery of [".headerlink", "script", "style"]) { + htmlElement.querySelectorAll(removalQuery).forEach((el) => { el.remove() }); + } + if (anchor) { + const anchorContent = htmlElement.querySelector(`[role="main"] ${anchor}`); + if (anchorContent) return anchorContent.textContent; + + console.warn( + `Anchored content block not found. Sphinx search tries to obtain it via DOM query '[role=main] ${anchor}'. Check your theme or template.` + ); + } + + // if anchor not specified or not found, fall back to main content + const docContent = htmlElement.querySelector('[role="main"]'); + if (docContent) return docContent.textContent; + + console.warn( + "Content block not found. Sphinx search tries to obtain it via DOM query '[role=main]'. Check your theme or template." + ); + return ""; + }, + + init: () => { + const query = new URLSearchParams(window.location.search).get("q"); + document + .querySelectorAll('input[name="q"]') + .forEach((el) => (el.value = query)); + if (query) Search.performSearch(query); + }, + + loadIndex: (url) => + (document.body.appendChild(document.createElement("script")).src = url), + + setIndex: (index) => { + Search._index = index; + if (Search._queued_query !== null) { + const query = Search._queued_query; + Search._queued_query = null; + Search.query(query); + } + }, + + hasIndex: () => Search._index !== null, + + deferQuery: (query) => (Search._queued_query = query), + + stopPulse: () => (Search._pulse_status = -1), + + startPulse: () => { + if (Search._pulse_status >= 0) return; + + const pulse = () => { + Search._pulse_status = (Search._pulse_status + 1) % 4; + Search.dots.innerText = ".".repeat(Search._pulse_status); + if (Search._pulse_status >= 0) window.setTimeout(pulse, 500); + }; + pulse(); + }, + + /** + * perform a search for something (or wait until index is loaded) + */ + performSearch: (query) => { + // create the required interface elements + const searchText = document.createElement("h2"); + searchText.textContent = _("Searching"); + const searchSummary = document.createElement("p"); + searchSummary.classList.add("search-summary"); + searchSummary.innerText = ""; + const searchList = document.createElement("ul"); + searchList.classList.add("search"); + + const out = document.getElementById("search-results"); + Search.title = out.appendChild(searchText); + Search.dots = Search.title.appendChild(document.createElement("span")); + Search.status = out.appendChild(searchSummary); + Search.output = out.appendChild(searchList); + + const searchProgress = document.getElementById("search-progress"); + // Some themes don't use the search progress node + if (searchProgress) { + searchProgress.innerText = _("Preparing search..."); + } + Search.startPulse(); + + // index already loaded, the browser was quick! + if (Search.hasIndex()) Search.query(query); + else Search.deferQuery(query); + }, + + _parseQuery: (query) => { + // stem the search terms and add them to the correct list + const stemmer = new Stemmer(); + const searchTerms = new Set(); + const excludedTerms = new Set(); + const highlightTerms = new Set(); + const objectTerms = new Set(splitQuery(query.toLowerCase().trim())); + splitQuery(query.trim()).forEach((queryTerm) => { + const queryTermLower = queryTerm.toLowerCase(); + + // maybe skip this "word" + // stopwords array is from language_data.js + if ( + stopwords.indexOf(queryTermLower) !== -1 || + queryTerm.match(/^\d+$/) + ) + return; + + // stem the word + let word = stemmer.stemWord(queryTermLower); + // select the correct list + if (word[0] === "-") excludedTerms.add(word.substr(1)); + else { + searchTerms.add(word); + highlightTerms.add(queryTermLower); + } + }); + + if (SPHINX_HIGHLIGHT_ENABLED) { // set in sphinx_highlight.js + localStorage.setItem("sphinx_highlight_terms", [...highlightTerms].join(" ")) + } + + // console.debug("SEARCH: searching for:"); + // console.info("required: ", [...searchTerms]); + // console.info("excluded: ", [...excludedTerms]); + + return [query, searchTerms, excludedTerms, highlightTerms, objectTerms]; + }, + + /** + * execute search (requires search index to be loaded) + */ + _performSearch: (query, searchTerms, excludedTerms, highlightTerms, objectTerms) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + const allTitles = Search._index.alltitles; + const indexEntries = Search._index.indexentries; + + // Collect multiple result groups to be sorted separately and then ordered. + // Each is an array of [docname, title, anchor, descr, score, filename]. + const normalResults = []; + const nonMainIndexResults = []; + + _removeChildren(document.getElementById("search-progress")); + + const queryLower = query.toLowerCase().trim(); + for (const [title, foundTitles] of Object.entries(allTitles)) { + if (title.toLowerCase().trim().includes(queryLower) && (queryLower.length >= title.length/2)) { + for (const [file, id] of foundTitles) { + const score = Math.round(Scorer.title * queryLower.length / title.length); + const boost = titles[file] === title ? 1 : 0; // add a boost for document titles + normalResults.push([ + docNames[file], + titles[file] !== title ? `${titles[file]} > ${title}` : title, + id !== null ? "#" + id : "", + null, + score + boost, + filenames[file], + ]); + } + } + } + + // search for explicit entries in index directives + for (const [entry, foundEntries] of Object.entries(indexEntries)) { + if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) { + for (const [file, id, isMain] of foundEntries) { + const score = Math.round(100 * queryLower.length / entry.length); + const result = [ + docNames[file], + titles[file], + id ? "#" + id : "", + null, + score, + filenames[file], + ]; + if (isMain) { + normalResults.push(result); + } else { + nonMainIndexResults.push(result); + } + } + } + } + + // lookup as object + objectTerms.forEach((term) => + normalResults.push(...Search.performObjectSearch(term, objectTerms)) + ); + + // lookup as search terms in fulltext + normalResults.push(...Search.performTermsSearch(searchTerms, excludedTerms)); + + // let the scorer override scores with a custom scoring function + if (Scorer.score) { + normalResults.forEach((item) => (item[4] = Scorer.score(item))); + nonMainIndexResults.forEach((item) => (item[4] = Scorer.score(item))); + } + + // Sort each group of results by score and then alphabetically by name. + normalResults.sort(_orderResultsByScoreThenName); + nonMainIndexResults.sort(_orderResultsByScoreThenName); + + // Combine the result groups in (reverse) order. + // Non-main index entries are typically arbitrary cross-references, + // so display them after other results. + let results = [...nonMainIndexResults, ...normalResults]; + + // remove duplicate search results + // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept + let seen = new Set(); + results = results.reverse().reduce((acc, result) => { + let resultStr = result.slice(0, 4).concat([result[5]]).map(v => String(v)).join(','); + if (!seen.has(resultStr)) { + acc.push(result); + seen.add(resultStr); + } + return acc; + }, []); + + return results.reverse(); + }, + + query: (query) => { + const [searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms] = Search._parseQuery(query); + const results = Search._performSearch(searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms); + + // for debugging + //Search.lastresults = results.slice(); // a copy + // console.info("search results:", Search.lastresults); + + // print the results + _displayNextItem(results, results.length, searchTerms, highlightTerms); + }, + + /** + * search for object names + */ + performObjectSearch: (object, objectTerms) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const objects = Search._index.objects; + const objNames = Search._index.objnames; + const titles = Search._index.titles; + + const results = []; + + const objectSearchCallback = (prefix, match) => { + const name = match[4] + const fullname = (prefix ? prefix + "." : "") + name; + const fullnameLower = fullname.toLowerCase(); + if (fullnameLower.indexOf(object) < 0) return; + + let score = 0; + const parts = fullnameLower.split("."); + + // check for different match types: exact matches of full name or + // "last name" (i.e. last dotted part) + if (fullnameLower === object || parts.slice(-1)[0] === object) + score += Scorer.objNameMatch; + else if (parts.slice(-1)[0].indexOf(object) > -1) + score += Scorer.objPartialMatch; // matches in last name + + const objName = objNames[match[1]][2]; + const title = titles[match[0]]; + + // If more than one term searched for, we require other words to be + // found in the name/title/description + const otherTerms = new Set(objectTerms); + otherTerms.delete(object); + if (otherTerms.size > 0) { + const haystack = `${prefix} ${name} ${objName} ${title}`.toLowerCase(); + if ( + [...otherTerms].some((otherTerm) => haystack.indexOf(otherTerm) < 0) + ) + return; + } + + let anchor = match[3]; + if (anchor === "") anchor = fullname; + else if (anchor === "-") anchor = objNames[match[1]][1] + "-" + fullname; + + const descr = objName + _(", in ") + title; + + // add custom score for some objects according to scorer + if (Scorer.objPrio.hasOwnProperty(match[2])) + score += Scorer.objPrio[match[2]]; + else score += Scorer.objPrioDefault; + + results.push([ + docNames[match[0]], + fullname, + "#" + anchor, + descr, + score, + filenames[match[0]], + ]); + }; + Object.keys(objects).forEach((prefix) => + objects[prefix].forEach((array) => + objectSearchCallback(prefix, array) + ) + ); + return results; + }, + + /** + * search for full-text terms in the index + */ + performTermsSearch: (searchTerms, excludedTerms) => { + // prepare search + const terms = Search._index.terms; + const titleTerms = Search._index.titleterms; + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + + const scoreMap = new Map(); + const fileMap = new Map(); + + // perform the search on the required terms + searchTerms.forEach((word) => { + const files = []; + const arr = [ + { files: terms[word], score: Scorer.term }, + { files: titleTerms[word], score: Scorer.title }, + ]; + // add support for partial matches + if (word.length > 2) { + const escapedWord = _escapeRegExp(word); + if (!terms.hasOwnProperty(word)) { + Object.keys(terms).forEach((term) => { + if (term.match(escapedWord)) + arr.push({ files: terms[term], score: Scorer.partialTerm }); + }); + } + if (!titleTerms.hasOwnProperty(word)) { + Object.keys(titleTerms).forEach((term) => { + if (term.match(escapedWord)) + arr.push({ files: titleTerms[term], score: Scorer.partialTitle }); + }); + } + } + + // no match but word was a required one + if (arr.every((record) => record.files === undefined)) return; + + // found search word in contents + arr.forEach((record) => { + if (record.files === undefined) return; + + let recordFiles = record.files; + if (recordFiles.length === undefined) recordFiles = [recordFiles]; + files.push(...recordFiles); + + // set score for the word in each file + recordFiles.forEach((file) => { + if (!scoreMap.has(file)) scoreMap.set(file, {}); + scoreMap.get(file)[word] = record.score; + }); + }); + + // create the mapping + files.forEach((file) => { + if (!fileMap.has(file)) fileMap.set(file, [word]); + else if (fileMap.get(file).indexOf(word) === -1) fileMap.get(file).push(word); + }); + }); + + // now check if the files don't contain excluded terms + const results = []; + for (const [file, wordList] of fileMap) { + // check if all requirements are matched + + // as search terms with length < 3 are discarded + const filteredTermCount = [...searchTerms].filter( + (term) => term.length > 2 + ).length; + if ( + wordList.length !== searchTerms.size && + wordList.length !== filteredTermCount + ) + continue; + + // ensure that none of the excluded terms is in the search result + if ( + [...excludedTerms].some( + (term) => + terms[term] === file || + titleTerms[term] === file || + (terms[term] || []).includes(file) || + (titleTerms[term] || []).includes(file) + ) + ) + break; + + // select one (max) score for the file. + const score = Math.max(...wordList.map((w) => scoreMap.get(file)[w])); + // add result to the result list + results.push([ + docNames[file], + titles[file], + "", + null, + score, + filenames[file], + ]); + } + return results; + }, + + /** + * helper function to return a node containing the + * search summary for a given text. keywords is a list + * of stemmed words. + */ + makeSearchSummary: (htmlText, keywords, anchor) => { + const text = Search.htmlToText(htmlText, anchor); + if (text === "") return null; + + const textLower = text.toLowerCase(); + const actualStartPosition = [...keywords] + .map((k) => textLower.indexOf(k.toLowerCase())) + .filter((i) => i > -1) + .slice(-1)[0]; + const startWithContext = Math.max(actualStartPosition - 120, 0); + + const top = startWithContext === 0 ? "" : "..."; + const tail = startWithContext + 240 < text.length ? "..." : ""; + + let summary = document.createElement("p"); + summary.classList.add("context"); + summary.textContent = top + text.substr(startWithContext, 240).trim() + tail; + + return summary; + }, +}; + +_ready(Search.init); diff --git a/_static/sphinx_highlight.js b/_static/sphinx_highlight.js new file mode 100644 index 0000000000..8a96c69a19 --- /dev/null +++ b/_static/sphinx_highlight.js @@ -0,0 +1,154 @@ +/* Highlighting utilities for Sphinx HTML documentation. */ +"use strict"; + +const SPHINX_HIGHLIGHT_ENABLED = true + +/** + * highlight a given string on a node by wrapping it in + * span elements with the given class name. + */ +const _highlight = (node, addItems, text, className) => { + if (node.nodeType === Node.TEXT_NODE) { + const val = node.nodeValue; + const parent = node.parentNode; + const pos = val.toLowerCase().indexOf(text); + if ( + pos >= 0 && + !parent.classList.contains(className) && + !parent.classList.contains("nohighlight") + ) { + let span; + + const closestNode = parent.closest("body, svg, foreignObject"); + const isInSVG = closestNode && closestNode.matches("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.classList.add(className); + } + + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + const rest = document.createTextNode(val.substr(pos + text.length)); + parent.insertBefore( + span, + parent.insertBefore( + rest, + node.nextSibling + ) + ); + node.nodeValue = val.substr(0, pos); + /* There may be more occurrences of search term in this node. So call this + * function recursively on the remaining fragment. + */ + _highlight(rest, addItems, text, className); + + if (isInSVG) { + const rect = document.createElementNS( + "http://www.w3.org/2000/svg", + "rect" + ); + const bbox = parent.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute("class", className); + addItems.push({ parent: parent, target: rect }); + } + } + } else if (node.matches && !node.matches("button, select, textarea")) { + node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); + } +}; +const _highlightText = (thisNode, text, className) => { + let addItems = []; + _highlight(thisNode, addItems, text, className); + addItems.forEach((obj) => + obj.parent.insertAdjacentElement("beforebegin", obj.target) + ); +}; + +/** + * Small JavaScript module for the documentation. + */ +const SphinxHighlight = { + + /** + * highlight the search words provided in localstorage in the text + */ + highlightSearchWords: () => { + if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight + + // get and clear terms from localstorage + const url = new URL(window.location); + const highlight = + localStorage.getItem("sphinx_highlight_terms") + || url.searchParams.get("highlight") + || ""; + localStorage.removeItem("sphinx_highlight_terms") + url.searchParams.delete("highlight"); + window.history.replaceState({}, "", url); + + // get individual terms from highlight string + const terms = highlight.toLowerCase().split(/\s+/).filter(x => x); + if (terms.length === 0) return; // nothing to do + + // There should never be more than one element matching "div.body" + const divBody = document.querySelectorAll("div.body"); + const body = divBody.length ? divBody[0] : document.querySelector("body"); + window.setTimeout(() => { + terms.forEach((term) => _highlightText(body, term, "highlighted")); + }, 10); + + const searchBox = document.getElementById("searchbox"); + if (searchBox === null) return; + searchBox.appendChild( + document + .createRange() + .createContextualFragment( + '" + ) + ); + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords: () => { + document + .querySelectorAll("#searchbox .highlight-link") + .forEach((el) => el.remove()); + document + .querySelectorAll("span.highlighted") + .forEach((el) => el.classList.remove("highlighted")); + localStorage.removeItem("sphinx_highlight_terms") + }, + + initEscapeListener: () => { + // only install a listener if it is really needed + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; + if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { + SphinxHighlight.hideSearchWords(); + event.preventDefault(); + } + }); + }, +}; + +_ready(() => { + /* Do not call highlightSearchWords() when we are on the search page. + * It will highlight words from the *previous* search query. + */ + if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords(); + SphinxHighlight.initEscapeListener(); +}); diff --git a/_static/sphinxdoc.css b/_static/sphinxdoc.css new file mode 100644 index 0000000000..b03830b411 --- /dev/null +++ b/_static/sphinxdoc.css @@ -0,0 +1,354 @@ +/* + * sphinxdoc.css_t + * ~~~~~~~~~~~~~~~ + * + * Sphinx stylesheet -- sphinxdoc theme. Originally created by + * Armin Ronacher for Werkzeug. + * + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +@import url("http://github.com/basic.css"); + +/* -- page layout ----------------------------------------------------------- */ + +body { + font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', + 'Verdana', sans-serif; + font-size: 14px; + letter-spacing: -0.01em; + line-height: 150%; + text-align: center; + background-color: #BFD1D4; + color: black; + padding: 0; + border: 1px solid #aaa; + + margin: 0px 80px 0px 80px; + min-width: 740px; +} + +div.document { + background-color: white; + text-align: left; + background-image: url(http://github.com/contents.png); + background-repeat: repeat-x; +} + +div.documentwrapper { + float: left; + width: 100%; +} + +div.bodywrapper { + margin: 0 calc(230px + 10px) 0 0; + border-right: 1px solid #ccc; +} + +div.body { + margin: 0; + padding: 0.5em 20px 20px 20px; +} + +div.related { + font-size: 1em; +} + +div.related ul { + background-image: url(http://github.com/navigation.png); + height: 2em; + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; +} + +div.related ul li { + margin: 0; + padding: 0; + height: 2em; + float: left; +} + +div.related ul li.right { + float: right; + margin-right: 5px; +} + +div.related ul li a { + margin: 0; + padding: 0 5px 0 5px; + line-height: 1.75em; + color: #EE9816; +} + +div.related ul li a:hover { + color: #3CA8E7; +} + +div.sphinxsidebarwrapper { + padding: 0; +} + +div.sphinxsidebar { + padding: 0.5em 15px 15px 0; + width: calc(230px - 20px); + float: right; + font-size: 1em; + text-align: left; +} + +div.sphinxsidebar h3, div.sphinxsidebar h4 { + margin: 1em 0 0.5em 0; + font-size: 1em; + padding: 0.1em 0 0.1em 0.5em; + color: white; + border: 1px solid #86989B; + background-color: #AFC1C4; +} + +div.sphinxsidebar h3 a { + color: white; +} + +div.sphinxsidebar ul { + padding-left: 1.5em; + margin-top: 7px; + padding: 0; + line-height: 130%; +} + +div.sphinxsidebar ul ul { + margin-left: 20px; +} + +div.footer { + background-color: #E3EFF1; + color: #86989B; + padding: 3px 8px 3px 0; + clear: both; + font-size: 0.8em; + text-align: right; +} + +div.footer a { + color: #86989B; + text-decoration: underline; +} + +/* -- body styles ----------------------------------------------------------- */ + +p { + margin: 0.8em 0 0.5em 0; +} + +a { + color: #CA7900; + text-decoration: none; +} + +a:hover { + color: #2491CF; +} + +a:visited { + color: #551A8B; +} + +div.body a { + text-decoration: underline; +} + +h1 { + margin: 0; + padding: 0.7em 0 0.3em 0; + font-size: 1.5em; + color: #11557C; +} + +h2 { + margin: 1.3em 0 0.2em 0; + font-size: 1.35em; + padding: 0; +} + +h3 { + margin: 1em 0 -0.3em 0; + font-size: 1.2em; +} + +div.body h1 a, div.body h2 a, div.body h3 a, div.body h4 a, div.body h5 a, div.body h6 a { + color: black!important; +} + +h1 a.anchor, h2 a.anchor, h3 a.anchor, h4 a.anchor, h5 a.anchor, h6 a.anchor { + display: none; + margin: 0 0 0 0.3em; + padding: 0 0.2em 0 0.2em; + color: #aaa!important; +} + +h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor, +h5:hover a.anchor, h6:hover a.anchor { + display: inline; +} + +h1 a.anchor:hover, h2 a.anchor:hover, h3 a.anchor:hover, h4 a.anchor:hover, +h5 a.anchor:hover, h6 a.anchor:hover { + color: #777; + background-color: #eee; +} + +a.headerlink { + color: #c60f0f!important; + font-size: 1em; + margin-left: 6px; + padding: 0 4px 0 4px; + text-decoration: none!important; +} + +a.headerlink:hover { + background-color: #ccc; + color: white!important; +} + +cite, code, code { + font-family: 'Consolas', 'Deja Vu Sans Mono', + 'Bitstream Vera Sans Mono', monospace; + font-size: 0.95em; + letter-spacing: 0.01em; +} + +code { + background-color: #f2f2f2; + border-bottom: 1px solid #ddd; + color: #333; +} + +code.descname, code.descclassname, code.xref { + border: 0; +} + +hr { + border: 1px solid #abc; + margin: 2em; +} + +a code { + border: 0; + color: #CA7900; +} + +a code:hover { + color: #2491CF; +} + +pre { + font-family: 'Consolas', 'Deja Vu Sans Mono', + 'Bitstream Vera Sans Mono', monospace; + font-size: 0.95em; + letter-spacing: 0.015em; + line-height: 120%; + padding: 0.5em; + border: 1px solid #ccc; +} + +pre a { + color: inherit; + text-decoration: underline; +} + +td.linenos pre { + padding: 0.5em 0; +} + +div.quotebar { + background-color: #f8f8f8; + max-width: 250px; + float: right; + padding: 2px 7px; + border: 1px solid #ccc; +} + +nav.contents, +aside.topic, +div.topic { + background-color: #f8f8f8; +} + +table { + border-collapse: collapse; + margin: 0 -0.5em 0 -0.5em; +} + +table td, table th { + padding: 0.2em 0.5em 0.2em 0.5em; +} + +div.admonition, div.warning { + font-size: 0.9em; + margin: 1em 0 1em 0; + border: 1px solid #86989B; + background-color: #f7f7f7; + padding: 0; +} + +div.admonition p, div.warning p { + margin: 0.5em 1em 0.5em 1em; + padding: 0; +} + +div.admonition pre, div.warning pre { + margin: 0.4em 1em 0.4em 1em; +} + +div.admonition p.admonition-title, +div.warning p.admonition-title { + margin: 0; + padding: 0.1em 0 0.1em 0.5em; + color: white; + border-bottom: 1px solid #86989B; + font-weight: bold; + background-color: #AFC1C4; +} + +div.warning { + border: 1px solid #940000; +} + +div.warning p.admonition-title { + background-color: #CF0000; + border-bottom-color: #940000; +} + +div.admonition ul, div.admonition ol, +div.warning ul, div.warning ol { + margin: 0.1em 0.5em 0.5em 3em; + padding: 0; +} + +div.versioninfo { + margin: 1em 0 0 0; + border: 1px solid #ccc; + background-color: #DDEAF0; + padding: 8px; + line-height: 1.3em; + font-size: 0.9em; +} + +.viewcode-back { + font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', + 'Verdana', sans-serif; +} + +div.viewcode-block:target { + background-color: #f4debf; + border-top: 1px solid #ac9; + border-bottom: 1px solid #ac9; +} + +div.code-block-caption { + background-color: #ddd; + color: #222; + border: 1px solid #ccc; +} \ No newline at end of file diff --git a/api.html b/api.html new file mode 100644 index 0000000000..78e4a1905a --- /dev/null +++ b/api.html @@ -0,0 +1,363 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + + +
+
+ +
+
+

NiBabel

+

Access a cacophony of neuro-imaging file formats

+
+
+ + + + +
+
+
+
+ +
+

API Documentation

+ + + + + + +

nibabel

Read and write access to common neuroimaging file formats, including: ANALYZE_ (plain, SPM99, SPM2 and later), GIFTI_, NIfTI1_, NIfTI2_, `CIFTI-2`_, MINC1_, MINC2_, `AFNI BRIK/HEAD`_, ECAT_ and Philips PAR/REC.

+
+

File Formats

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

analyze

Read / write access to the basic Mayo Analyze format

spm2analyze

Read / write access to SPM2 version of analyze image format

spm99analyze

Read / write access to SPM99 version of analyze image format

cifti2

CIFTI-2 format IO

gifti

GIfTI format IO

freesurfer

Reading functions for freesurfer files

minc1

Read MINC1 format images

minc2

Preliminary MINC2 support

nicom

DICOM reader

nifti1

Read / write access to NIfTI1 image format

nifti2

Read / write access to NIfTI2 image format

ecat

Read ECAT format images

parrec

Read images in PAR/REC format

streamlines

Multiformat-capable streamline format read / write interface

+
+
+

Image Utilities

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

eulerangles

Module implementing Euler angle rotations and their conversions

funcs

Processor functions for images

imageclasses

Define supported image classes and names

imageglobals

Defaults for images and headers

loadsave

Utilities to load and save image objects

orientations

Utilities for calculating and applying affine orientations

quaternions

Functions to operate on, or return, quaternions

spatialimages

A simple spatial image class

volumeutils

Utility functions for analyze-like formats

+
+
+

Float / integer conversion

+ + + + + + + + + +

arraywriters

Array writer objects

casting

Utilities for casting numpy values in various ways

+
+
+

System utilities

+ + + + + + + + + +

data

Utilities to find files from NIPY data packages

environment

Settings from the system environment relevant to NIPY

+
+
+

Miscellaneous Helpers

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

arrayproxy

Array proxy base class

affines

Utility routines for working with points and affine transforms

batteryrunners

Battery runner classes and Report classes

data

Utilities to find files from NIPY data packages

dft

DICOM filesystem tools

fileholders

Fileholder class

filename_parser

Create filename pairs, triplets etc, with expected extensions

fileslice

Utilities for getting array slices out of file-like objects

onetime

Descriptor support for NIPY

openers

Context manager openers for various fileobject types

optpkg

Routines to support optional packages

rstutils

ReStructured Text utilities

tmpdirs

Contexts for with statement providing temporary directories

tripwire

Class to raise error for missing modules or other misfortunes

wrapstruct

Class to wrap numpy structured array

+
+
+

Alphabetical API reference

+ +
+
+ + +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/bin/nib-dicomfs b/bin/nib-dicomfs deleted file mode 100755 index 51c7414752..0000000000 --- a/bin/nib-dicomfs +++ /dev/null @@ -1,15 +0,0 @@ -#!python -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# Copyright (C) 2011 Christian Haselgrove - -from nibabel.cmdline.dicomfs import main - -if __name__ == '__main__': - main() diff --git a/bin/nib-diff b/bin/nib-diff deleted file mode 100755 index 2ae66dda9d..0000000000 --- a/bin/nib-diff +++ /dev/null @@ -1,17 +0,0 @@ -#!python -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -""" -Quick diff summary for a set of neuroimaging files -""" - -from nibabel.cmdline.diff import main - -if __name__ == '__main__': - main() diff --git a/bin/nib-ls b/bin/nib-ls deleted file mode 100755 index 067efb0533..0000000000 --- a/bin/nib-ls +++ /dev/null @@ -1,17 +0,0 @@ -#!python -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -""" -Output a summary table for neuroimaging files (resolution, dimensionality, etc.) -""" - -from nibabel.cmdline.ls import main - -if __name__ == '__main__': - main() diff --git a/bin/nib-nifti-dx b/bin/nib-nifti-dx deleted file mode 100755 index 2562e0f0d8..0000000000 --- a/bin/nib-nifti-dx +++ /dev/null @@ -1,15 +0,0 @@ -#!python -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Print nifti diagnostics for header files""" - -from nibabel.cmdline.nifti_dx import main - -if __name__ == '__main__': - main() diff --git a/bin/nib-tck2trk b/bin/nib-tck2trk deleted file mode 100644 index 896e67a5d1..0000000000 --- a/bin/nib-tck2trk +++ /dev/null @@ -1,18 +0,0 @@ -#!python -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -""" -Convert tractograms (TCK -> TRK). -""" - -from nibabel.cmdline.tck2trk import main - - -if __name__ == '__main__': - main() diff --git a/bin/nib-trk2tck b/bin/nib-trk2tck deleted file mode 100644 index 85509e7447..0000000000 --- a/bin/nib-trk2tck +++ /dev/null @@ -1,18 +0,0 @@ -#!python -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -""" -Convert tractograms (TRK -> TCK). -""" - -from nibabel.cmdline.trk2tck import main - - -if __name__ == '__main__': - main() diff --git a/bin/parrec2nii b/bin/parrec2nii deleted file mode 100755 index e5ec8bfe38..0000000000 --- a/bin/parrec2nii +++ /dev/null @@ -1,7 +0,0 @@ -#!python -"""PAR/REC to NIfTI converter""" - -from nibabel.cmdline.parrec2nii import main - -if __name__ == '__main__': - main() diff --git a/changelog.html b/changelog.html new file mode 100644 index 0000000000..1a54405b54 --- /dev/null +++ b/changelog.html @@ -0,0 +1,2179 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
+
+ +
+
+

NiBabel

+

Access a cacophony of neuro-imaging file formats

+
+
+ + + + +
+
+
+
+ +
+

NiBabel Development Changelog

+

NiBabel is the successor to the much-loved PyNifti package. Here we list the +releases for both packages.

+

The full VCS changelog is available here:

+
+
+
+

Nibabel releases

+

Most work on NiBabel so far has been by Matthew Brett (MB), Chris Markiewicz +(CM), Michael Hanke (MH), Marc-Alexandre Côté (MC), Ben Cipollini (BC), Paul +McCarthy (PM), Chris Cheng (CC), Yaroslav Halchenko (YOH), Satra Ghosh (SG), +Eric Larson (EL), Demian Wassermann, Stephan Gerhard and Ross Markello (RM).

+

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

+
+

5.3.0 (Tuesday 8 October 2024)

+

This release primarily adds support for Python 3.13 and Numpy 2.0.

+

NiBabel 6.0 will drop support for Numpy 1.x.

+
+

New features

+
    +
  • Update NIfTI extension protocol to include .content : bytes, .text : str and .json : dict +properties for accessing extension contents. Exceptions will be raised on .text and .json if +conversion fails. (pr/1336) (CM)

  • +
+
+
+

Enhancements

+
    +
  • Ability to read data from many multiframe DICOM files that previously generated errors (pr/1340) +(Brendan Moloney, reviewed by CM)

  • +
  • nib-nifti-dx now supports NIfTI-2 files with a --nifti2 flag (pr/1323) (CM)

  • +
  • Update nibabel.streamlines.tractogram to support ragged arrays. (pr/1291) +(Serge Koudoro, reviewed by CM)

  • +
  • Filter numpy UserWarning on np.finfo(np.longdouble). This can occur on +Windows systems, but it’s done in the context of checking for the problem that +is being warned against, so there’s no need to be noisy. (pr/1310) +(Joshua Newton, reviewed by CM)

  • +
  • Improve error message for for dicomwrapper errors in shape calculation (pr/1302) +(YOH, reviewed by CM)

  • +
  • Support “flat” ASCII-encoded GIFTI DataArrays (pr/1298) (PM, reviewed by CM)

  • +
+
+
+

Bug fixes

+
    +
  • Fix location initialization/update in OrthoSlicer3D for permuted axes (pr/1319, pr/1350) +(Guillaume Becq, reviewed by CM)

  • +
  • Fix DICOM scaling, making frame filtering explicit (pr/1342) (Brendan Moloney, reviewed by CM)

  • +
  • Fixed multiframe DICOM issue where data could be flipped along slice dimension relative to the +affine (pr/1340) (Brendan Moloney, reviewed by CM)

  • +
  • Fixed multiframe DICOM issue where image_position and the translation component in the +affine could be incorrect (pr/1340) (Brendan Moloney, reviewed by CM)

  • +
+
+
+

Maintenance

+
    +
  • Numpy 2.0 compatibility and addressing deprecations in numpy API +(pr/1304, pr/1330, pr/1331, pr/1334, pr/1337) (Jon Haitz Legarreta Gorroño, CM)

  • +
  • Python 3.13 compatibility (pr/1315) (Sandro from the Fedora Project, reviewed by CM)

  • +
  • Testing on Python 3.13 with free-threading (pr/1339) (CM)

  • +
  • Testing on ARM64 Mac OS runners (pr/1320) (CM)

  • +
  • Proactively address deprecations in coming Python versions (pr/1329, pr/1332, pr/1333) +(Jon Haitz Legarreta Gorroño, reviewed by CM)

  • +
  • Replace nose-era setup() and teardown() functions with pytest equivalents +(pr/1325) (Sandro from the Fedora Project, reviewed by Étienne Mollier and CM)

  • +
  • Transitioned from blue/isort/flake8 to ruff. (pr/1289) +(Dimitri Papadopoulos, reviewed by CM)

  • +
  • Vetted and added various rules to the ruff configuration for auto-formatting and style +guide enforcement. (pr/1321, pr/1351, pr/1352, pr/1353, pr/1354, pr/1355, pr/1357, pr/1358, +pr/1359, pr/1360, pr/1361, pr/1362, pr/1363, pr/1364, pr/1368, pr/1369) +(Dimitri Papadopoulos, reviewed by CM)

  • +
  • Fixing typos when found. (pr/1313, pr/1370) (MB, Dimitri Papadopoulos)

  • +
  • Applied Repo-Review suggestions (Dimitri Papadopoulos, reviewed by CM)

  • +
+
+
+

API changes and deprecations

+
    +
  • Raise HeaderDataError from +set_qform() if the affine fails to decompose. +This would previously result in numpy.linalg.LinAlgError. (pr/1227) (CM)

  • +
  • The nibabel.onetime.auto_attr() module can be replaced by functools.cached_property() +in all supported versions of Python. This alias may be removed in future versions. (pr/1341) (CM)

  • +
  • Removed the deprecated nisext (setuptools extensions) package. (pr/1290) (CM, reviewed by MB)

  • +
+
+
+
+

5.2.1 (Monday 26 February 2024)

+

Bug-fix release in the 5.2.x series.

+
+

Enhancements

+
    +
  • Support “flat” ASCII-encoded GIFTI DataArrays (pr/1298) (PM, reviewed by CM)

  • +
+
+
+

Bug fixes

+
    +
  • Tolerate missing git when reporting version info (pr/1286) (CM, reviewed by +Yuri Victorovich)

  • +
  • Handle Siemens XA30 derived DWI DICOMs (pr/1296) (CM, reviewed by YOH and +Mathias Goncalves)

  • +
+
+
+

Maintenance

+
    +
  • Add tool for generating GitHub-friendly release notes (pr/1284) (CM)

  • +
  • Accommodate pytest 8 changes (pr/1297) (CM)

  • +
+
+
+
+

5.2.0 (Monday 11 December 2023)

+

New feature release in the 5.2.x series.

+

This release requires a minimum Python of 3.8 and NumPy 1.20, and has been +tested up to Python 3.12 and NumPy 1.26.

+
+

New features

+
    +
  • Add generic Pointset and regularly spaced +Grid data structures in preparation for coordinate +transformation and resampling (pr/1251) (CM, reviewed by Oscar Esteban)

  • +
+
+
+

Enhancements

+
    +
  • Add copy() method to +ArrayProxy (pr/1255) (CM, reviewed by Paul McCarthy)

  • +
  • Permit to_xml() methods to pass keyword +arguments to xml.etree.ElementTree.tostring() (pr/1258) +(CM)

  • +
  • Allow user expansion (e.g., ~/...) in strings passed to functions that +accept paths (pr/1260) (Reinder Vos de Wael, reviewed by CM)

  • +
  • Expand CIFTI-2 brain structures to permit synonyms (pr/1256) (CM, reviewed +by Mathias Goncalves)

  • +
  • Annotate SpatialImage as accepting +affine=None argument (pr/1253) (Blake Dewey, reviewed by CM)

  • +
  • Warn on invalid MINC2 spacing declarations, treat as missing (pr/1237) +(Peter Suter, reviewed by CM)

  • +
  • Refactor find_private_section() for improved +readability and maintainability (pr/1228) (MB, reviewed by CM)

  • +
+
+
+

Bug fixes

+
    +
  • Resolve test failure related to randomly generated invalid case (pr/1221) (CM)

  • +
+
+
+

Documentation

+
    +
  • Remove references to NiPy data packages from documentation (pr/1275) +(Dimitri Papadopoulos, reviewed by CM, MB)

  • +
+
+
+

Maintenance

+
    +
  • Quality of life improvements for CI, including color output and OIDC publishing +(pr/1282) (CM)

  • +
  • Patch for NumPy 2.0 pre-release compatibility (pr/1250) (Mathieu +Scheltienne and EL, reviewed by CM)

  • +
  • Add spellchecking to tox, CI and pre-commit (pr/1266) (CM)

  • +
  • Add py312-dev-x64 environment to Tox to test NumPy 2.0 pre-release +compatibility (pr/1267) (CM, reviewed by EL)

  • +
  • Resurrect tox configuration to cover development workflows and CI checks +(pr/1262) (CM)

  • +
  • Updates for Python 3.12 support (pr/1247, pr/1261, pr/1273) (CM)

  • +
  • Remove uses of deprecated numpy.compat.py3k module (pr/1243) (Eric +Larson, reviewed by CM)

  • +
  • Various fixes for typos and style issues detected by Codespell, pyupgrade and +refurb (pr/1263, pr/1269, pr/1270, pr/1271, pr/1276) (Dimitri Papadopoulos, +reviewed by CM)

  • +
  • Use stable argsorts in PARREC tests to ensure consistent behavior on systems +with AVX512 SIMD instructions and numpy 1.25 (pr/1234) (CM)

  • +
  • Resolve CodeCov submission failures (pr/1224) (CM)

  • +
  • Link to logo with full URL to avoid broken links in PyPI (pr/1218) (CM, +reviewed by Zvi Baratz)

  • +
+
+
+

API changes and deprecations

+
    +
  • The nibabel.pydicom_compat module is deprecated and will be removed +in NiBabel 7.0. (pr/1280)

  • +
  • The int_to_float() and as_int() +functions are no longer needed to work around NumPy deficiencies and have been +deprecated (pr/1272) (CM, reviewed by EL)

  • +
+
+
+
+

5.1.0 (Monday 3 April 2023)

+

New feature release in the 5.1.x series.

+
+

Enhancements

+ +
+
+

Bug fixes

+
    +
  • Require explicit overrides to write GIFTI files that contain data arrays +with data types not permitted by the GIFTI standard (pr/1199) (CM, reviewed +by Alexis Thual)

  • +
+
+
+

Maintenance

+
    +
  • Move compression detection logic into a private nibabel._compression +module, resolving unexpected errors from pyzstd. (pr/1212) (CM)

  • +
  • Improved consistency of docstring formatting (pr/1200) (Zvi Baratz, reviewed +by CM)

  • +
  • Modernized README text (pr/1195) (Zvi Baratz, reviewed by CM)

  • +
  • Updated README badges to include package distributions (pr/1192) (Horea +Christian, reviewed by CM)

  • +
  • Removed all dependencies on distutils and setuptools (pr/1190) (CM, +reviewed by Zvi Baratz)

  • +
  • Add a _version.pyi stub to allow mypy to run without building nibabel +(pr/1210) (CM)

  • +
+
+
+
+

5.0.1 (Sunday 12 February 2023)

+

Bug-fix release in the 5.0.x series.

+
+

Bug fixes

+
    +
  • Support ragged voxel arrays in +ParcelsAxis (pr/1194) (Michiel Cottaar, +reviewed by CM)

  • +
  • Return to cwd on exception in InTemporaryDirectory +(pr/1184) (CM)

  • +
+
+
+

Maintenance

+
    +
  • Add py.typed to module root to enable use of types in downstream +projects (CM, reviewed by Fernando Pérez-Garcia)

  • +
  • Cache git-archive separately from Python packages in GitHub Actions +(pr/1186) (CM, reviewed by Zvi Baratz)

  • +
+
+
+
+

5.0.0 (Monday 9 January 2023)

+

New feature release in the 5.0.x series.

+
+

New features

+ +
+
+

Enhancements

+
    +
  • Support multiline header fields in TCKFile +(pr/1175) (CM, reviewed by Matt Cieslak)

  • +
  • Make layout order an initialization parameter of +ArrayProxy (pr/1131) (CM, reviewed by MB)

  • +
  • Initial support for type annotations. (pr/1115, pr/1178) (CM, reviewed by +Zvi Baratz)

  • +
+
+
+

Bug fixes

+
    +
  • Handle extension/file-format mismatches implemented incompletely in pr/1013 +(pr/1138) (CM, reviewed by Thomas Phil)

  • +
  • Improve handling of invalid TCK files, which could sometimes cause an +infinite loop (pr/1140) (Anibal Solon, reviewed by CM)

  • +
  • Clean up ECAT test case that left filehandle open and failed to use class +variables (pr/1155) (Dimitri Papadopoulos, reviewed by CM)

  • +
+
+
+

Maintenance

+
    +
  • Simplify TCK reading code by assuming files are open in binary mode +(pr/1142) (Anibal Solon, reviewed by MC, CM)

  • +
  • Code support for tests covering deprecated functionality (pr/1159) (CM)

  • +
  • Miscellaneous code cleanups (pr/1148, pr/1149, pr/1153, pr/1154, pr/1156) +(Dimitri Papadopoulos, reviewed by CM)

  • +
  • Update CI to build, test and deploy PyPI artifacts (pr/1134) (CM, reviewed +by MB)

  • +
  • Transition from setup.cfg to pyproject.toml package configuration +(pr/1133) (CM, reviewed by MB)

  • +
  • Addressed race conditions preventing running tests with pytest-xdist. +(pr/1157, pr/1158) (CM, reviewed by Christian Haselgrove)

  • +
  • Apply blue and isort auto-formatters and provide pre-commit configuration +to reduce human burden of style guidelines. (pr/1124, pr/1165, pr/1169) +(CM and Zvi Baratz)

  • +
  • Manage versioning with setuptools_scm (pr/1171) (CM, reviewed by Zvi Baratz)

  • +
  • Reduce installed package size by excluding very large test file (pr/1176) +(CM, reviewed by Zvi Baratz)

  • +
+
+
+

API changes and deprecations

+ +
+
+
+

4.0.2 (Wednesday 31 August 2022)

+

Bug-fix release in the 4.0.x series.

+
+

Bug fixes

+
    +
  • Make GiftiMetaData.data a list proxy, deprecate (pr/1127) (CM, reviewed +by Hao-Ting Wang)

  • +
+
+
+

Maintenance

+
    +
  • Finalize deprecation of ArrayWriter.to_fileobj(nan2zero=...) argument +(pr/1126) (CM)

  • +
+
+
+
+

4.0.1 (Saturday 18 June 2022)

+

Bug-fix release in the 4.0.x series.

+
+

Bug fixes

+
    +
  • Finalize 4.0 deprecations, converting tests expecting DeprecationWarning to +expected ExpiredDeprecationError (pr/1117) (CM)

  • +
+
+
+

Maintenance

+
    +
  • Suppress new numpy warning on nan-to-int cast (pr/1118) (CM, reviewed by MB)

  • +
+
+
+
+

4.0.0 (Saturday 18 June 2022)

+

New feature release in the 4.0.x series.

+
+

New features

+
    +
  • nib-convert CLI tool to make image type and data dtype conversion accessible +via the command line. (pr/1113) (CM, reviewed by Ariel Rokem)

  • +
  • Add 'mask', 'compat' and 'smallest' dtype aliases to NIfTI images +to allow for dtype specifications that can depend on the contents of the data. +'mask' is a synonym for uint8. 'compat' will find the nearest +Analyze-compatible (therefore widely supported) dtype that will not truncate +the data. 'smallest' attempts to find the smallest integer dtype that will +contain the data. (pr/1096) (CM, reviewed by Chris Rorden and Josh Teves)

  • +
  • Add dtype arguments to Cifti2Image (pr/1111) (CM)

  • +
  • Allow dtypes to be passed to Analyze-like images at __init__() and +to_filename() to provide better control over output images. (pr/1082) +(CM, following discussions with Chris Rorden, Josh Teves, Jerome Dockes, and MB)

  • +
  • Allow compressed GIFTI images (MB, reviewed by CM)

  • +
  • Add zstd compression support (pr/1005) (Andrew Van, reviewed by CM)

  • +
  • Support ExternalFileBinary GIFTI data arrays (PM, reviewed by CM)

  • +
+
+
+

Enhancements

+
    +
  • Document InTemporaryDirectory as non-thread-safe (pr/1103) (Jacob Roberts, +reviewed by MB)

  • +
  • Unify Caret-XML-style metadata structure (GiftiMetaData, Cifti2MetaData) +as dict-like (pr/1091) (CM, reviewed by Josh Teves and Hao-Ting Wang)

  • +
  • Add __repr__ methods to GIFTI objects (pr/1092) (CM, +reviewed by Josh Teves and Hao-Ting Wang)

  • +
  • Create gzip header deterministically by default (pr/1024) (CM, reviewed by YOH)

  • +
  • Provide clear error message when files with zip extensions don’t match +file contents (pr/1013) (Jérôme Dockès, reviewed by CM)

  • +
+
+
+

Bug fixes

+
    +
  • Re-import externals/netcdf.py from scipy to resolve numpy API change (pr/1110) +(CM)

  • +
  • Resize ArraySequence.data without helper function to avoid reference increment +(pr/1093) (MC, reviewed by CM)

  • +
+
+
+

Maintenance

+
    +
  • Update submodule URLs to use https over git protocol (pr/1097) (CM)

  • +
  • Published BIAP 9: CoordinateImage API (pr/1084) (CM)

  • +
  • Drop uses of deprecated distutils (pr/1073) (CM, reviewed by MB)

  • +
  • Suppress LGTM false alarm “Clear-text logging of sensitive information” +(pr/1052) (Dimitri Papadopoulos, reviewed by CM)

  • +
  • Test on Python 3.10 (pr/1047) (CM)

  • +
  • Fix typos found by codespell (pr/1040, pr/1044) +(Dimitri Papadopoulos, reviewed by CM)

  • +
  • Run stable tests weekly, pre-release tests nightly (pr/1025) (CM)

  • +
  • Documentation updates to establish/clarify governance and decision +making (pr/1019, pr/1020, pr/1022, pr/1018, pr/1017, pr/1016) (MB and CM)

  • +
+
+
+

API changes and deprecations

+
    +
  • Writing NIfTIs with 64-bit integer dtypes is getting harder. +Passing (u)int64 arrays to Nifti1Image and subclasses will warn unless +a header or dtype option is passed; in the future this will become an +error. +Additionally, passing int or 'int' to set_data_dtype() now raises +an error, requiring an explicit numpy dtype to make 64-bit integer images. +(pr/1082) (CM, following discussions with Chris Rorden, Josh Teves, Jerome Dockes, +and MB)

  • +
  • Drop support for Python 3.6, Numpy < 1.17 (pr/1079) (CM)

  • +
  • Fully removed the following APIs, which have raised errors on use +since 3.0 (pr/980) (CM, reviewed by Jonathan Daniel)

    +
    +
      +
    • nibabel.trackvis

    • +
    • nibabel.volumeutils.calculate_scale

    • +
    • nibabel.volumeutils.can_cast

    • +
    • nibabel.volumeutils.scale_min_max

    • +
    • nibabel.dataobj_images.DataobjImage.get_shape

    • +
    • nibabel.minc1.MincImage (use Minc1Image)

    • +
    • nibabel.minc1.MincFile (use Minc1File)

    • +
    • nibabel.filebasedimages.FileBasedImage.from_files

    • +
    • nibabel.filebasedimages.FileBasedImage.filespec_to_files

    • +
    • nibabel.filebasedimages.FileBasedImage.to_filespec

    • +
    • nibabel.filebasedimages.FileBasedImage.to_files

    • +
    • nibabel.arrayproxy.ArrayProxy.header

    • +
    • keep_file_open=="auto" parameter to load method (now must be boolean)

    • +
    +
    +
  • +
+
+
+
+

3.2.2 (Monday 7 February 2022)

+

Bug fix release in the 3.2.x series.

+
+

Bug fixes

+
    +
  • Reshape CIFTI-2 affines to 4x4 when encoded as row-major sequence (pr/1059) +(Andrew Van, reviewed by CM)

  • +
  • Suggest nibabel.save() on calls to deprecated giftiio.write() (pr/1055) +(Anibal Solon, reviewed by CM)

  • +
  • Various bugs and style issues detected by LGTM (pr/1043, pr/1048) +(Dimitri Papadopoulos, reviewed by CM)

  • +
  • Resolve unclosed file warning in GiftiImage (pr/1038) (Lea Waller, reviewed by CM)

  • +
  • Fix typos preventing deprecation warnings from being raised (pr/991) +(Jonathan Daniel, reviewed by MB)

  • +
  • Work around numpy SystemError to maintain expected error types (pr/1051) (CM)

  • +
  • Use more constrained mock when testing optpkg (pr/983) (CM, reviewed by YOH)

  • +
+
+
+

Maintenance

+
    +
  • Add setuptools requirement to match usage (pr/1009) +(Tomáš Hrnčiar, reviewed by CM)

  • +
  • Fix grammar of headings in CoC (pr/996) (MB, reviewed by CM, Ariel Rokem)

  • +
  • Set minimum pydicom to 1.0.0 (pr/1050) (CM)

  • +
  • Submit coverage to codecov via pinned PyPI package (pr/1008) (CM)

  • +
  • Upgrade versioneer to 0.19 (pr/967) (CM)

  • +
  • Migrate to GitHub actions (pr/972) (CM, reviewed by Serge Koudoro)

  • +
+
+
+
+

3.2.1 (Saturday 28 November 2020)

+

Bug fix release in the 3.2.x series.

+
+

Maintenance

+
    +
  • Drop references to builtin types in Numpy namespace like np.float +(pr/964) (EL, reviewed by CM)

  • +
  • Ensure compatibility with Python 3.9 (pr/963) (CM)

  • +
+
+
+
+

3.2.0 (Tuesday 20 October 2020)

+

New feature release in the 3.2.x series.

+
+

New features

+
    +
  • nib-stats CLI tool to expose new nibabel.imagestats API. Initial +implementation of volume calculations, a la fslstats -V. (Julian Klug, +reviewed by CM and GitHub user 0rC0)

  • +
  • nib-roi CLI tool to crop images and/or flip axes (pr/947) (CM, reviewed +by Chris Cheng and Mathias Goncalves)

  • +
  • Parser for Siemens “ASCCONV” text format (pr/896) (Brendan Moloney and MB, +reviewed by CM)

  • +
+
+
+

Enhancements

+
    +
  • Drop confusing mention of img.to_filename() in getting started guide +(pr/946) (Fernando Pérez-Garcia, reviewed by MB, CM)

  • +
  • Implement to_bytes()/from_bytes() methods for Cifti2Image +(pr/938) (CM, reviewed by Mathias Goncalves)

  • +
  • Clean up of DICOM documentation (pr/910) (Jonathan Daniel, reviewed by MB)

  • +
+
+
+

Bug fixes

+
    +
  • Use canvas manager API to set title in OrthoSlicer3D (pr/958) (EL, +reviewed by CM)

  • +
  • Record units as seconds parrec2nii; previously set TR to seconds but +retained msec units (pr/931) (CM, reviewed by MB)

  • +
  • Reflect on-disk dimensions in NIfTI-2 view of CIFTI-2 images (pr/930) +(Mathias Goncalves and CM)

  • +
  • Fix outdated Python 2 and Sympy code in DICOM derivations (pr/911) (MB, +reviewed by CM)

  • +
  • Change string with invalid escape to raw string (pr/909) (EL, reviewed +by MB)

  • +
+
+
+

Maintenance

+
    +
  • Fix typo in docs (pr/955) (Carl Gauthier, reviewed by CM)

  • +
  • Purge nose from nisext tests (pr/934) (Markéta Calábková, reviewed by CM)

  • +
  • Suppress expected warnings in tests (pr/949) (CM, reviewed by Dorota +Jarecka)

  • +
  • Various cleanups and modernizations (pr/916, pr/917, pr/918, pr/919) +(Jonathan Daniel, reviewed by CM)

  • +
  • SVG logo for improved appearance in with zooming (pr/914) (Jonathan Daniel, +reviewed by CM)

  • +
+
+
+

API changes and deprecations

+
    +
  • Drop support for Numpy < 1.13 (pr/922) (CM)

  • +
  • Warn on use of onetime.setattr_on_read, which has been a deprecated +alias of auto_attr (pr/948) (CM, reviewed by Ariel Rokem)

  • +
+
+
+
+

3.1.1 (Friday 26 June 2020)

+

Bug-fix release in the 3.1.x series.

+

These are small compatibility fixes that support ARM64 architecture and +indexed_gzip>=1.3.0.

+
+

Bug fixes

+
    +
  • Detect IndexedGzipFile as compressed file type (pr/925) (PM, reviewed by +CM)

  • +
  • Correctly cast nan when testing array_to_file, fixing ARM64 builds +(pr/862) (CM, reviewed by MB)

  • +
+
+
+
+

3.1.0 (Monday 20 April 2020)

+

New feature release in the 3.1.x series.

+
+

New features

+
    +
  • Conformation function (processing.conform) and CLI tool +(nib-conform) to apply shape, orientation and zooms (pr/853) (Jakub +Kaczmarzyk, reviewed by CM, YOH)

  • +
  • Affine rescaling function (affines.rescale_affine) to update +dimensions and voxel sizes (pr/853) (CM, reviewed by Jakub Kaczmarzyk)

  • +
+
+
+

Bug fixes

+
    +
  • Delay import of h5py until needed (pr/889) (YOH, reviewed by CM)

  • +
+
+
+

Maintenance

+
    +
  • Fix typo in documentation (pr/893) (Zvi Baratz, reviewed by CM)

  • +
  • Tests converted from nose to pytest (pr/865 + many sub-PRs) +(Dorota Jarecka, Krzyzstof Gorgolewski, Roberto Guidotti, Anibal Solon, +and Or Duek)

  • +
+
+
+

API changes and deprecations

+
    +
  • kw_only_meth/kw_only_func decorators are deprecated (pr/848) +(RM, reviewed by CM)

  • +
+
+
+
+

2.5.2 (Wednesday 8 April 2020)

+

Bug-fix release in the 2.5.x series. This is an extended-support series, +providing bug fixes for Python 2.7 and 3.4.

+

This and all future releases in the 2.5.x series will be incompatible with +Python 3.9. The last compatible series of numpy and scipy are 1.16.x and +1.2.x, respectively.

+

If you are able to upgrade to Python 3, it is recommended to upgrade to +NiBabel 3.

+
+

Bug fixes

+
    +
  • Change strings with invalid escapes to raw strings (pr/827) (EL, reviewed +by CM)

  • +
  • Re-import externals/netcdf.py from scipy to resolve numpy deprecation +(pr/821) (CM)

  • +
+
+
+

Maintenance

+
    +
  • Set maximum numpy to 1.16.x, maximum scipy to 1.2.x (pr/901) (CM)

  • +
+
+
+
+

3.0.2 (Monday 9 March 2020)

+
+

Bug fixes

+
    +
  • Attempt to find versioneer version when building docs (pr/894) (CM)

  • +
  • Delay import of h5py until needed (backport of pr/889) (YOH, reviewed by CM)

  • +
+
+
+

Maintenance

+
    +
  • Fix typo in documentation (backport of pr/893) (Zvi Baratz, reviewed by CM)

  • +
  • Set minimum matplotlib to 1.5.3 to ensure wheels are available on all +supported Python versions. (backport of pr/887) (CM)

  • +
  • Remove pyproject.toml for now. (issue/859) (CM)

  • +
+
+
+
+

3.0.1 (Monday 27 January 2020)

+
+

Bug fixes

+
    +
  • Test failed by using array method on tuple. (pr/860) (Ben Darwin, reviewed by +CM)

  • +
  • Validate ExpiredDeprecationErrors, promoted by 3.0 release from +DeprecationWarnings. (pr/857) (CM)

  • +
+
+
+

Maintenance

+
    +
  • Remove logic accommodating numpy without float16 types. (pr/866) (CM)

  • +
  • Accommodate new numpy dtype strings. (pr/858) (CM)

  • +
+
+
+
+

3.0.0 (Wednesday 18 December 2019)

+
+

New features

+
    +
  • ArrayProxy __array__() now accepts a dtype parameter, allowing +numpy.array(dataobj, dtype=...) calls, as well as casting directly +with a dtype (for example, numpy.float32(dataobj)) to control the +output type. Scale factors (slope, intercept) are applied, but may be +cast to narrower types, to control memory usage. This is now the basis +of img.get_fdata(), which will scale data in single precision if +the output type is float32. (pr/844) (CM, reviewed by Alejandro +de la Vega, Ross Markello)

  • +
  • GiftiImage method agg_data() to return usable data arrays (pr/793) +(Hao-Ting Wang, reviewed by CM)

  • +
  • Accept os.PathLike objects in place of filenames (pr/610) (Cameron +Riddell, reviewed by MB, CM)

  • +
  • Function to calculate obliquity of affines (pr/815) (Oscar Esteban, +reviewed by MB)

  • +
+
+
+

Enhancements

+
    +
  • Improve testing of data scaling in ArrayProxy API (pr/847) (CM, reviewed +by Alejandro de la Vega)

  • +
  • Document SpatialImage.slicer interface (pr/846) (CM)

  • +
  • get_fdata(dtype=np.float32) will attempt to avoid casting data to +np.float64 when scaling parameters would otherwise promote the data +type unnecessarily. (pr/833) (CM, reviewed by Ross Markello)

  • +
  • ArraySequence now supports a large set of Python operators to combine +or update in-place. (pr/811) (MC, reviewed by Serge Koudoro, Philippe Poulin, +CM, MB)

  • +
  • Warn, rather than fail, on DICOMs with unreadable Siemens CSA tags (pr/818) +(Henry Braun, reviewed by CM)

  • +
  • Improve clarity of coordinate system tutorial (pr/823) (Egor Panfilov, +reviewed by MB)

  • +
+
+
+

Bug fixes

+
    +
  • Sliced Tractograms no longer apply_affine to the original +Tractogram’s streamlines. (pr/811) (MC, reviewed by Serge Koudoro, +Philippe Poulin, CM, MB)

  • +
  • Change strings with invalid escapes to raw strings (pr/827) (EL, reviewed +by CM)

  • +
  • Re-import externals/netcdf.py from scipy to resolve numpy deprecation +(pr/821) (CM)

  • +
+
+
+

Maintenance

+
    +
  • Remove replicated metadata for packaged data from MANIFEST.in (pr/845) (CM)

  • +
  • Support Python >=3.5.1, including Python 3.8.0 (pr/787) (CM)

  • +
  • Manage versioning with slightly customized Versioneer (pr/786) (CM)

  • +
  • Reference Nipy Community Code and Nibabel Developer Guidelines in +GitHub community documents (pr/778) (CM, reviewed by MB)

  • +
+
+
+

API changes and deprecations

+
    +
  • Fully remove deprecated checkwarns and minc modules. (pr/852) (CM)

  • +
  • The keep_file_open argument to file load operations and ArrayProxys +no longer accepts the value "auto", raising a ValueError. (pr/852) +(CM)

  • +
  • Deprecate ArraySequence.data in favor of ArraySequence.get_data(), +which will return a copy. ArraySequence.data now returns a read-only +view. (pr/811) (MC, reviewed by Serge Koudoro, Philippe Poulin, CM, MB)

  • +
  • Deprecate DataobjImage.get_data() API, to be removed in nibabel 5.0 +(pr/794, pr/809) (CM, reviewed by MB)

  • +
+
+
+
+

2.5.1 (Monday 23 September 2019)

+
+

Enhancements

+
    +
  • Ignore endianness in nib-diff if values match (pr/799) (YOH, reviewed +by CM)

  • +
+
+
+

Bug fixes

+
    +
  • Correctly handle Philips DICOMs w/ derived volume (pr/795) (Mathias +Goncalves, reviewed by CM)

  • +
  • Raise CSA tag limit to 1000, parametrize for future relaxing (pr/798, +backported to 2.5.x in pr/800) (Henry Braun, reviewed by CM, MB)

  • +
  • Coerce data types to match NIfTI intent codes when writing GIFTI data +arrays (pr/806) (CM, reported by Tom Holroyd)

  • +
+
+
+

Maintenance

+
    +
  • Require h5py 2.10 for Windows + Python < 3.6 to resolve unexpected dtypes +in Minc2 data (pr/804) (CM, reviewed by YOH)

  • +
+
+
+

API changes and deprecations

+
    +
  • Deprecate nicom.dicomwrappers.Wrapper.get_affine() in favor of affine +property; final removal in nibabel 4.0 (pr/796) (YOH, reviewed by CM)

  • +
+
+
+
+

2.5.0 (Sunday 4 August 2019)

+

The 2.5.x series is the last with support for either Python 2 or Python 3.4. +Extended support for this series 2.5 will last through December 2020.

+

Thanks for the test ECAT file and fix provided by Andrew Crabb.

+
+

Enhancements

+
    +
  • Add SerializableImage class with to/from_bytes methods (pr/644) (CM, +reviewed by MB)

  • +
  • Check CIFTI-2 data shape matches shape described by header (pr/774) +(Michiel Cottaar, reviewed by CM)

  • +
+
+
+

Bug fixes

+
    +
  • Handle stricter numpy casting rules in tests (pr/768) (CM) +reviewed by PM)

  • +
  • TRK header fields flipped in files written on big-endian systems +(pr/782) (CM, reviewed by YOH, MB)

  • +
  • Load multiframe ECAT images with Python 3 (CM and Andrew Crabb)

  • +
+
+
+

Maintenance

+
    +
  • Fix CodeCov paths on Appveyor for more accurate coverage (pr/769) (CM)

  • +
  • Move to setuptools and reduce use nisext functions (pr/764) (CM, +reviewed by YOH)

  • +
  • Better handle test setup/teardown (pr/785) (CM, reviewed by YOH)

  • +
+
+
+

API changes and deprecations

+
    +
  • Effect threatened warnings and set some deprecation timelines (pr/755) (CM) +* Trackvis methods now default to v2 formats +* nibabel.trackvis scheduled for removal in nibabel 4.0 +* nibabel.minc and nibabel.MincImage will be removed in nibabel 3.0

  • +
+
+
+
+

2.4.1 (Monday 27 May 2019)

+

Contributions from Egor Pafilov, Jath Palasubramaniam, Richard Nemec, and +Dave Allured.

+
+

Enhancements

+
    +
  • Enable mmap, keep_file_open options when loading any +DataobjImage (pr/759) (CM, reviewed by PM)

  • +
+
+
+

Bug fixes

+
    +
  • Ensure loaded GIFTI files expose writable data arrays (pr/750) (CM, +reviewed by PM)

  • +
  • Safer warning registry manipulation when checking for overflows (pr/753) +(CM, reviewed by MB)

  • +
  • Correctly write .annot files with duplicate labels (pr/763) (Richard Nemec +with CM)

  • +
+
+
+

Maintenance

+
    +
  • Fix typo in coordinate systems doc (pr/751) (Egor Panfilov, reviewed by +CM)

  • +
  • Replace invalid MINC1 test file with fixed file (pr/754) (Dave Allured +with CM)

  • +
  • Update Sphinx config to support recent Sphinx/numpydoc (pr/749) (CM, +reviewed by PM)

  • +
  • Pacify FutureWarning and DeprecationWarning from h5py, numpy +(pr/760) (CM)

  • +
  • Accommodate Python 3.8 deprecation of collections.MutableMapping +(pr/762) (Jath Palasubramaniam, reviewed by CM)

  • +
+
+
+

API changes and deprecations

+
    +
  • Deprecate keep_file_open == 'auto' (pr/761) (CM, reviewed by PM)

  • +
+
+
+
+

2.4.0 (Monday 1 April 2019)

+
+

New features

+
    +
  • Alternative Axis-based interface for manipulating CIFTI-2 headers +(pr/641) (Michiel Cottaar, reviewed by Demian Wassermann, CM, SG)

  • +
+
+
+

Enhancements

+
    +
  • Accept TCK files produced by tools with other delimiter/EOF defaults +(pr/720) (Soichi Hayashi, reviewed by CM, MB, MC)

  • +
  • Allow BrainModels or Parcels to contain a single vertex in CIFTI +(pr/739) (Michiel Cottaar, reviewed by CM)

  • +
  • Support for NIFTI_XFORM_TEMPLATE_OTHER xform code (pr/743) (CM)

  • +
+
+
+

Bug fixes

+
    +
  • Skip refcheck in ArraySequence construction/extension (pr/719) (Ariel +Rokem, reviewed by CM, MC)

  • +
  • Use safe resizing for ArraySequence extension (pr/724) (CM, reviewed +by MC)

  • +
  • Fix typo in error message (pr/726) (Jon Haitz Legarreta Gorroño, +reviewed by CM)

  • +
  • Support DICOM slice sorting in Python 3 (pr/728) (Samir Reddigari, +reviewed by CM)

  • +
  • Correctly reorient dim_info when reorienting NIfTI images +(Konstantinos Raktivan, CM, reviewed by CM)

  • +
+
+
+

Maintenance

+
    +
  • Import updates to reduce upstream deprecation warnings (pr/711, +pr/705, pr/738) (EL, YOH, reviewed by CM)

  • +
  • Delay import of nibabel.testing, nose and mock to speed up +import (pr/699) (CM)

  • +
  • Increase coverage testing, drop coveralls (pr/722, pr/732) (CM)

  • +
  • Add Zenodo metadata, sorted by commits (pr/732) (CM + others)

  • +
  • Update author listing and copyrights (pr/742) (MB, reviewed by CM)

  • +
+
+
+
+

2.3.3 (Wednesday 16 January 2019)

+
+

Maintenance

+
    +
  • Restore six dependency (pr/714) (CM, reviewed by Gael Varoquaux, MB)

  • +
+
+
+
+

2.3.2 (Wednesday 2 January 2019)

+
+

Enhancements

+
    +
  • Enable toggling crosshair with Ctrl-x in OrthoSlicer3D viewer (pr/701) +(Miguel Estevan Moreno, reviewed by CM)

  • +
+
+
+

Bug fixes

+
    +
  • Read .PAR files corresponding to ADC maps (pr/685) (Gregory R. Lee, reviewed +by CM)

  • +
  • Increase maximum number of items read from Siemens CSA format (Igor Solovey, +reviewed by CM, MB)

  • +
  • Check boolean dtypes with numpy.issubdtype(..., np.bool_) (pr/707) +(Jon Haitz Legarreta Gorroño, reviewed by CM)

  • +
+
+
+

Maintenance

+
    +
  • Fix small typos in parrec2nii help text (pr/682) (Thomas Roos, reviewed by +MB)

  • +
  • Remove deprecated calls to numpy.asscalar (pr/686) (CM, reviewed by +Gregory R. Lee)

  • +
  • Update QA directives to accommodate Flake8 3.6 (pr/695) (CM)

  • +
  • Update DOI links to use https://doi.org (pr/703) (Katrin Leinweber, +reviewed by CM)

  • +
  • Remove deprecated calls to numpy.fromstring (pr/700) (Ariel Rokem, +reviewed by CM, MB)

  • +
  • Drop distutils support, require bz2file for Python 2.7 (pr/700) +(CM, reviewed by MB)

  • +
  • Replace mutable bytes hack, disabled in numpy pre-release, with +bytearray/readinto strategy (pr/700) (Ariel Rokem, CM, reviewed by +CM, MB)

  • +
+
+
+

API changes and deprecations

+
    +
  • Add Opener.readinto method to read file contents into pre-allocated buffers +(pr/700) (Ariel Rokem, reviewed by CM, MB)

  • +
+
+
+
+

2.3.1 (Tuesday 16 October 2018)

+
+

New features

+
    +
  • nib-diff command line tool for comparing image files (pr/617, pr/672, +pr/678) (CC, reviewed by YOH, Pradeep Raamana and CM)

  • +
+
+
+

Enhancements

+
    +
  • Speed up reading of numeric arrays in CIFTI2 (pr/655) (Michiel Cottaar, +reviewed by CM)

  • +
  • Add ndim property to ArrayProxy and DataobjImage (pr/674) (CM, +reviewed by MB)

  • +
+
+
+

Bug fixes

+
    +
  • Deterministic deduction of slice ordering in degenerate cases (pr/647) +(YOH, reviewed by CM)

  • +
  • Allow 0ms TR in MGH files (pr/653) (EL, reviewed by CM)

  • +
  • Allow for PPC64 little-endian long doubles (pr/658) (MB, reviewed by CM)

  • +
  • Correct construction of FreeSurfer annotation labels (pr/666) (CM, reviewed +by EL, Paul D. McCarthy)

  • +
  • Fix logic for persisting filehandles with indexed-gzip (pr/679) (Paul D. +McCarthy, reviewed by CM)

  • +
+
+
+

Maintenance

+
    +
  • Fix semantic error in coordinate systems documentation (pr/646) (Ariel +Rokem, reviewed by CM, MB)

  • +
  • Test on Python 3.7, minor associated fixes (pr/651) (CM, reviewed by Gregory +R. Lee, MB)

  • +
+
+
+
+

2.3 (Tuesday 12 June 2018)

+
+

New features

+
    +
  • TRK <=> TCK streamlines conversion CLI tools (pr/606) (MC, reviewed by CM)

  • +
  • Image slicing for SpatialImages (pr/550) (CM)

  • +
+
+
+

Enhancements

+
    +
  • Simplfiy MGHImage and add footer fields (pr/569) (CM, reviewed by MB)

  • +
  • Force sform/qform codes to be ints, rather than numpy types (pr/575) (Paul +McCarthy, reviewed by MB, CM)

  • +
  • Auto-fill color table in FreeSurfer annotation file (pr/592) (PM, +reviewed by CM, MB)

  • +
  • Set default intent code for CIFTI2 images (pr/604) (Mathias Goncalves, +reviewed by CM, SG, MB, Tim Coalson)

  • +
  • Raise informative error on empty files (pr/611) (Pradeep Raamana, reviewed +by CM, MB)

  • +
  • Accept degenerate filenames such as .nii (pr/621) (Dimitri +Papadopoulos-Orfanos, reviewed by Yaroslav Halchenko)

  • +
  • Take advantage of IndexedGzipFile drop_handles flag to release +filehandles by default (pr/614) (PM, reviewed by CM, MB)

  • +
+
+
+

Bug fixes

+
    +
  • Preserve first point of LazyTractogram +(pr/588) (MC, reviewed by Nil Goyette, CM, MB)

  • +
  • Stop adding extraneous metadata padding (pr/593) (Jon Stutters, reviewed by +CM, MB)

  • +
  • Accept lower-case orientation codes in TRK files (pr/600) (Kesshi Jordan, +MB, reviewed by MB, MC, CM)

  • +
  • Annotation file reading (pr/592) (PM, reviewed by CM, MB)

  • +
  • Fix buffer size calculation in ArraySequence (pr/597) (Serge Koudoro, +reviewed by MC, MB, Eleftherios Garyfallidis, CM)

  • +
  • Resolve UnboundLocalError in Python 3 (pr/607) (Jakub Kaczmarzyk, +reviewed by MB, CM)

  • +
  • Do not crash on non-ImportError failures in optional imports (pr/618) +(Yaroslav Halchenko, reviewed by CM)

  • +
  • Return original array from get_fdata for array image, if no cast +required (pr/638, MB, reviewed by CM)

  • +
+
+
+

Maintenance

+
    +
  • Use SSH address to use key-based auth (pr/587) (CM, reviewed by MB)

  • +
  • Fix doctests for numpy 1.14 array printing (pr/591) (MB, reviewed by CM)

  • +
  • Refactor for pydicom 1.0 API changes (pr/599) (MB, reviewed by CM)

  • +
  • Increase test coverage, remove unreachable code (pr/602) (CM, reviewed by +Yaroslav Halchenko, MB)

  • +
  • Move nib-ls and other programs to a new cmdline module (pr/601, pr/615) +(Chris Cheng, reviewed by MB, Yaroslav Halchenko)

  • +
  • Remove deprecated numpy indexing (EL, reviewed by CM)

  • +
  • Update documentation to encourage get_fdata over get_data (pr/637, +MB, reviewed by CM)

  • +
+
+
+

API changes and deprecations

+
    +
  • Support for keep_file_open = 'auto' as a parameter to Opener() will +be deprecated in 2.4, for removal in 3.0. Accordingly, support for +openers.KEEP_FILE_OPEN_DEFAULT = 'auto' will be dropped on the same +schedule.

  • +
  • Drop-in support for indexed_gzip < 0.7 has been removed.

  • +
+
+
+
+

2.2.1 (Wednesday 22 November 2017)

+
+

Bug fixes

+
    +
  • Set L/R labels in orthoview correctly (pr/564) (CM)

  • +
  • Defer use of ufunc / memmap test - allows “freezing” (pr/572) (MB, reviewed +by SG)

  • +
  • Fix doctest failures with pre-release numpy (pr/582) (MB, reviewed by CM)

  • +
+
+
+

Maintenance

+
    +
  • Update documentation around NIfTI qform/sform codes (pr/576) (PM, +reviewed by MB, CM) + (pr/580) (Bennet Fauber, reviewed by PM)

  • +
  • Skip precision test on macOS, newer numpy (pr/583) (MB, reviewed by CM)

  • +
  • Simplify AppVeyor script, removing conda (pr/584) (MB, reviewed by CM)

  • +
+
+
+
+

2.2 (Friday 13 October 2017)

+
+

New features

+
    +
  • CIFTI support (pr/249) (SG, Michiel Cottaar, BC, CM, Demian Wassermann, MB)

  • +
  • Support for MRtrix TCK streamlines file format (pr/486) (MC, reviewed by +MB, Arnaud Bore, J-Donald Tournier, Jean-Christophe Houde)

  • +
  • Added get_fdata() as default method to retrieve scaled floating point +data from DataobjImages (pr/551) (MB, reviewed by CM, SG)

  • +
+
+
+

Enhancements

+
    +
  • Support for alternative header field name variants in .PAR files +(pr/507) (Gregory R. Lee)

  • +
  • Various enhancements to streamlines API by MC: support for reading TRK +version 1 (pr/512); concatenation of tractograms using +/+= operators +(pr/495); function to concatenate multiple ArraySequence objects (pr/494)

  • +
  • Support for numpy 1.12 (pr/500, pr/502) (MC, MB)

  • +
  • Allow dtype specifiers as fileslice input (pr/485) (MB)

  • +
  • Support “headerless” ArrayProxy specification, enabling memory-efficient +ArrayProxy reshaping (pr/521) (CM)

  • +
  • Allow unknown NIfTI intent codes, add FSL codes (pr/528) (PM)

  • +
  • Improve error handling for img.__getitem__ (pr/533) (Ariel Rokem)

  • +
  • Delegate reorientation to SpatialImage classes (pr/544) (Mark Hymers, CM, +reviewed by MB)

  • +
  • Enable using indexed_gzip to reduce memory usage when reading from +gzipped NIfTI and MGH files (pr/552) (PM, reviewed by MB, CM)

  • +
+
+
+

Bug fixes

+
    +
  • Miscellaneous MINC reader fixes (pr/493) (Robert D. Vincent, reviewed by CM, +MB)

  • +
  • Fix corner case in wrapstruct.get (pr/516) (PM, reviewed by +CM, MB)

  • +
+
+
+

Maintenance

+
    +
  • Fix documentation errors (pr/517, pr/536) (Fernando Perez, Venky Reddy)

  • +
  • Documentation update (pr/514) (Ivan Gonzalez)

  • +
  • Update testing to use pre-release builds of dependencies (pr/509) (MB)

  • +
  • Better warnings when nibabel not on path (pr/503) (MB)

  • +
+
+
+

API changes and deprecations

+
    +
  • header argument to ArrayProxy.__init__ is renamed to spec

  • +
  • Deprecation of header property of ArrayProxy object, for removal in +3.0

  • +
  • wrapstruct.get now returns entries evaluating False, instead of None

  • +
  • DataobjImage.get_data to be deprecated April 2018, scheduled for removal +April 2020

  • +
+
+
+
+

2.1 (Monday 22 August 2016)

+
+

New features

+
    +
  • New API for managing streamlines and their different file formats. This +adds a new module nibabel.streamlines that will eventually deprecate +the current trackvis reader found in nibabel.trackvis (pr/391) (MC, +reviewed by Jean-Christophe Houde, Bago Amirbekian, Eleftherios +Garyfallidis, Samuel St-Jean, MB);

  • +
  • A prototype image viewer using matplotlib (pr/404) (EL, based on a +proto-prototype by Paul Ivanov) (Reviewed by Gregory R. Lee, MB);

  • +
  • Functions for image resampling and smoothing using scipy ndimage (pr/255) +(MB, reviewed by EL, BC);

  • +
  • Add ability to write FreeSurfer morphology data (pr/414) (CM, BC, reviewed +by BC);

  • +
  • Read and write support for DICOM tags in NIfTI Extended Header using +pydicom (pr/296) (Eric Kastman).

  • +
+
+
+

Enhancements

+
    +
  • Extensions to FreeSurfer module to fix reading and writing of FreeSurfer +geometry data (pr/460) (Alexandre Gramfort, Jaakko Leppäkangas, reviewed +by EL, CM, MB);

  • +
  • Various improvements to PAR / REC handling by Gregory R. Lee: supporting +multiple TR values (pr/429); output of volume labels (pr/427); fix for +some diffusion files (pr/426); option for more sophisticated sorting of +volumes (pr/409);

  • +
  • Original trackvis reader will now allow final streamline to have fewer +points than the number declared in the header, with strict=False +argument to read function;

  • +
  • Helper function to return voxel sizes from an affine matrix (pr/413);

  • +
  • Fixes to DICOM multiframe reading to avoid assumptions on the position of +the multiframe index (pr/439) (Eric M. Baker);

  • +
  • More robust handling of “CSA” private information in DICOM files (pr/393) +(Brendan Moloney);

  • +
  • More explicit error when trying to read image from non-existent file +(pr/455) (Ariel Rokem);

  • +
  • Extension to nib-ls command to show image statistics (pr/437) and other +header files (pr/348) (Yarik Halchenko).

  • +
+
+
+

Bug fixes

+
    +
  • Fixes to rotation order to generate affine matrices of PAR / REC files (MB, +Gregory R Lee).

  • +
+
+
+

Maintenance

+
    +
  • Dropped support for Pythons 2.6 and 3.2;

  • +
  • Comprehensive refactor and generalization of surface / GIFTI file support +with improved API and extended tests (pr/352-355, pr/360, pr/365, pr/403) +(BC, reviewed by CM, MB);

  • +
  • Refactor of image classes (pr/328, pr/329) (BC, reviewed by CM);

  • +
  • Better Appveyor testing on new Python versions (pr/446) (Ariel Rokem);

  • +
  • Fix shebang lines in scripts for correct install into virtualenvs via pip +(pr/434);

  • +
  • Various fixes for numpy, matplotlib, and PIL / Pillow compatibility (CM, +Ariel Rokem, MB);

  • +
  • Improved test framework for warnings (pr/345) (BC, reviewed by CM, MB);

  • +
  • New decorator to specify start and end versions for deprecation warnings +(MB, reviewed by CM);

  • +
  • Write qform affine matrix to NIfTI images output by parrec2nii (pr/478) +(Jasper J.F. van den Bosch, reviewed by Gregory R. Lee, MB).

  • +
+
+
+

API changes and deprecations

+
    +
  • Minor API breakage in original (rather than new) trackvis reader. We are now +raising a DataError if there are too few streamlines in the file, +instead of a HeaderError. We are raising a DataError if the track +is truncated when strict=True (the default), rather than a TypeError +when trying to create the points array.

  • +
  • Change sform code that parrec2nii script writes to NIfTI images; change +from 2 (“aligned”) to 1 (“scanner”);

  • +
  • Deprecation of get_header, get_affine method of image objects for +removal in version 4.0;

  • +
  • Removed broken from_filespec method from image objects, and deprecated +from_filespec method of ECAT image objects for removal in 4.0;

  • +
  • Deprecation of class_map instance in imageclasses module in favor of +new image class attributes, for removal in 4.0;

  • +
  • Deprecation of ext_map instance in imageclasses module in favor of +new image loading API, for removal in 4.0;

  • +
  • Deprecation of Header class in favor of SpatialHeader, for removal +in 4.0;

  • +
  • Deprecation of BinOpener class in favor of more generic Opener +class, for removal in 4.0;

  • +
  • Deprecation of GiftiMetadata methods get_metadata and get_rgba; +GiftiDataArray methods get_metadata, get_labeltable, +set_labeltable; GiftiImage methods get_meta, set_meta. All +these deprecated in favor of corresponding properties, for removal in 4.0;

  • +
  • Deprecation of giftiio read and write functions in favor of +nibabel load and save functions, for removal in 4.0;

  • +
  • Deprecation of gifti.data_tag function, for removal in 4.0;

  • +
  • Deprecation of write-access to GiftiDataArray.num_dim, and new error +when trying to set invalid values for num_dim. We will remove +write-access in 4.0;

  • +
  • Deprecation of GiftiDataArray.from_array in favor of GiftiDataArray +constructor, for removal in 4.0;

  • +
  • Deprecation of GiftiDataArray to_xml_open, to_xml_close methods in +favor of to_xml method, for removal in 4.0;

  • +
  • Deprecation of parse_gifti_fast.Outputter class in favor of +GiftiImageParser, for removal in 4.0;

  • +
  • Deprecation of parse_gifti_fast.parse_gifti_file function in favor of +GiftiImageParser.parse method, for removal in 4.0;

  • +
  • Deprecation of loadsave functions guessed_image_type and +which_analyze_type, in favor of new API where each image class tests the +file for compatibility during load, for removal in 4.0.

  • +
+
+
+
+

2.0.2 (Monday 23 November 2015)

+
    +
  • Fix for integer overflow on large images (pr/325) (MB);

  • +
  • Fix for Freesurfer nifti files with unusual dimensions (pr/332) (Chris +Markiewicz);

  • +
  • Fix typos on benchmarks and tests (pr/336, pr/340, pr/347) (Chris +Markiewicz);

  • +
  • Fix Windows install script (pr/339) (MB);

  • +
  • Support for Python 3.5 (pr/363) (MB) and numpy 1.10 (pr/358) (Chris +Markiewicz);

  • +
  • Update pydicom imports to permit version 1.0 (pr/379) (Chris Markiewicz);

  • +
  • Workaround for Python 3.5.0 gzip regression (pr/383) (Ben Cipollini).

  • +
  • tripwire.TripWire object now raises subclass of AttributeError when trying +to get an attribute, rather than a direct subclass of Exception. This +prevents Python 3.5 triggering the tripwire when doing inspection prior to +running doctests.

  • +
  • Minor API change for tripwire.TripWire object; code that checked for +AttributeError will now also catch TripWireError.

  • +
+
+
+

2.0.1 (Saturday 27 June 2015)

+

Contributions from Ben Cipollini, Chris Markiewicz, Alexandre Gramfort, +Clemens Bauer, github user freec84.

+
    +
  • Bugfix release with minor new features;

  • +
  • Added axis parameter to concat_images (pr/298) (Ben Cipollini);

  • +
  • Fix for unsigned integer data types in ECAT images (pr/302) (MB, test data +and issue report from Github user freec84);

  • +
  • Added new ECAT and Freesurfer data files to automated testing;

  • +
  • Fix for Freesurfer labels error on early numpies (pr/307) (Alexandre +Gramfort);

  • +
  • Fixes for PAR / REC header parsing (pr/312) (MB, issue reporting and test +data by Clemens C. C. Bauer);

  • +
  • Workaround for reading Freesurfer ico7 surface files (pr/315) (Chris +Markiewicz);

  • +
  • Changed to github pages for doc hosting;

  • +
  • Changed docs to point to neuroimaging@python.org mailing list.

  • +
+
+
+

2.0.0 (Tuesday 9 December 2014)

+

This release had large contributions from Eric Larson, Brendan Moloney, +Nolan Nichols, Basile Pinsard, Chris Johnson and Nikolaas N. Oosterhof.

+
    +
  • New feature, bugfix release with minor API breakage;

  • +
  • Minor API breakage: default write of NIfTI / Analyze image data offset +value. The data offset is the number of bytes from the beginning of file +to skip before reading the image data. Nibabel behavior changed from +keeping the value as read from file, to setting the offset to zero on +read, and setting the offset when writing the header. The value of the +offset will now be the minimum value necessary to make room for the header +and any extensions when writing the file. You can override the default +offset by setting value explicitly to some value other than zero. To read +the original data offset as read from the header, use the offset +property of the image dataobj attribute;

  • +
  • Minor API breakage: data scaling in NIfTI / Analyze now set to NaN when +reading images. Data scaling refers to the data intercept and slope +values in the NIfTI / Analyze header. To read the original data scaling +you need to look at the slope and inter properties of the image +dataobj attribute. You can set scaling explicitly by setting the +slope and intercept values in the header to values other than NaN;

  • +
  • New API for managing image caching; images have an in_memory property +that is true if the image data has been loaded into cache, or is already +an array in memory; get_data has new keyword argument caching to +specify whether the cache should be filled by get_data;

  • +
  • Images now have properties dataobj, affine, header. We will +slowly phase out the get_affine and get_header image methods;

  • +
  • The image dataobj can be sliced using an efficient algorithm to avoid +reading unnecessary data from disk. This makes it possible to do very +efficient reads of single volumes from a time series;

  • +
  • NIfTI2 read / write support;

  • +
  • Read support for MINC2;

  • +
  • Much extended read support for PAR / REC, largely due to work from Eric +Larson and Gregory R. Lee on new code, advice and code review. Thanks also +to Jeff Stevenson and Bennett Landman for helpful discussion;

  • +
  • parrec2nii script outputs images in LAS voxel orientation, which +appears to be necessary for compatibility with FSL dtifit / +fslview diffusion analysis pipeline;

  • +
  • Preliminary support for Philips multiframe DICOM images (thanks to Nolan +Nichols, Ly Nguyen and Brendan Moloney);

  • +
  • New function to save Freesurfer annotation files (by Github user ohinds);

  • +
  • Method to return MGH format vox2ras_tkr affine (Eric Larson);

  • +
  • A new API for reading unscaled data from NIfTI and other images, using +img.dataobj.get_unscaled(). Deprecate previous way of doing this, +which was to read data with the read_img_data function;

  • +
  • Fix for bug when replacing NaN values with zero when writing floating +point data as integers. If the input floating point data range did not +include zero, then NaN would not get written to a value corresponding to +zero in the output;

  • +
  • Improvements and bug fixes to image orientation calculation and DICOM +wrappers by Brendan Moloney;

  • +
  • Bug fixes writing GIfTI files. We were using a base64 encoding that didn’t +match the spec, and the wrong field name for the endian code. Thanks to +Basile Pinsard and Russ Poldrack for diagnosis and fixes;

  • +
  • Bug fix in freesurfer.read_annot with orig_ids=False when annot +contains vertices with no label (Alexandre Gramfort);

  • +
  • More tutorials in the documentation, including introductory tutorial on +DICOM, and on coordinate systems;

  • +
  • Lots of code refactoring, including moving to common code-base for Python +2 and Python 3;

  • +
  • New mechanism to add images for tests via git submodules.

  • +
+
+
+

1.3.0 (Tuesday 11 September 2012)

+

Special thanks to Chris Johnson, Brendan Moloney and JB Poline.

+
    +
  • New feature and bugfix release

  • +
  • Add ability to write Freesurfer triangle files (Chris Johnson)

  • +
  • Relax threshold for detecting rank deficient affines in orientation +detection (JB Poline)

  • +
  • Fix for DICOM slice normal numerical error (issue #137) (Brendan Moloney)

  • +
  • Fix for Python 3 error when writing zero bytes for offset padding

  • +
+
+
+

1.2.2 (Wednesday 27 June 2012)

+
    +
  • Bugfix release

  • +
  • Fix longdouble tests for Debian PPC (thanks to Yaroslav Halchecko for +finding and diagnosing these errors)

  • +
  • Generalize longdouble tests in the hope of making them more robust

  • +
  • Disable saving of float128 nifti type unless platform has real IEEE +binary128 longdouble type.

  • +
+
+
+

1.2.1 (Wednesday 13 June 2012)

+

Particular thanks to Yaroslav Halchecko for fixes and cleanups in this +release.

+
    +
  • Bugfix release

  • +
  • Make compatible with pydicom 0.9.7

  • +
  • Refactor, rename nifti diagnostic script to nib-nifti-dx

  • +
  • Fix a bug causing an error when analyzing affines for orientation, when the +affine contained all 0 columns

  • +
  • Add missing dicomfs script to installation list and rename to +nib-dicomfs

  • +
+
+
+

1.2.0 (Sunday 6 May 2012)

+

This release had large contributions from Krish Subramaniam, Alexandre +Gramfort, Cindee Madison, Félix C. Morency and Christian Haselgrove.

+
    +
  • New feature and bugfix release

  • +
  • Freesurfer format support by Krish Subramaniam and Alexandre Gramfort.

  • +
  • ECAT read write support by Cindee Madison and Félix C. Morency.

  • +
  • A DICOM fuse filesystem by Christian Haselgrove.

  • +
  • Much work on making data scaling on read and write more robust to rounding +error and overflow (MB).

  • +
  • Import of nipy functions for working with affine transformation matrices.

  • +
  • Added methods for working with nifti sform and qform fields by Bago +Amirbekian and MB, with useful discussion by Brendan Moloney.

  • +
  • Fixes to read / write of RGB analyze images by Bago Amirbekian.

  • +
  • Extensions to concat_images by Yannick Schwartz.

  • +
  • A new nib-ls script to display information about neuroimaging files, and +various other useful fixes by Yaroslav Halchenko.

  • +
+
+
+

1.1.0 (Thursday 28 April 2011)

+

Special thanks to Chris Burns, Jarrod Millman and Yaroslav Halchenko.

+
    +
  • New feature release

  • +
  • Python 3.2 support

  • +
  • Substantially enhanced gifti reading support (Stephan Gerhard)

  • +
  • Refactoring of trackvis read / write to allow reading and writing of voxel +points and mm points in tracks. Deprecate use of negative voxel sizes; +set voxel_order field in trackvis header. Thanks to Chris Filo +Gorgolewski for pointing out the problem and Ruopeng Wang in the trackvis +forum for clarifying the coordinate system of trackvis files.

  • +
  • Added routine to give approximate array orientation in form such as ‘RAS’ +or ‘LPS’

  • +
  • Fix numpy dtype hash errors for numpy 1.2.1

  • +
  • Other bug fixes as for 1.0.2

  • +
+
+
+

1.0.2 (Thursday 14 April 2011)

+
    +
  • Bugfix release

  • +
  • Make inference of data type more robust to changes in numpy dtype hashing

  • +
  • Fix incorrect thresholds in quaternion calculation (thanks to Yarik H for +pointing this one out)

  • +
  • Make parrec2nii pass over errors more gracefully

  • +
  • More explicit checks for missing or None field in trackvis and other +classes - thanks to Marc-Alexandre Cote

  • +
  • Make logging and error level work as expected - thanks to Yarik H

  • +
  • Loading an image does not change qform or sform - thanks to Yarik H

  • +
  • Allow 0 for nifti scaling as for spec - thanks to Yarik H

  • +
  • nifti1.save now correctly saves single or pair images

  • +
+
+
+

1.0.1 (Wednesday 23 Feb 2011)

+
    +
  • Bugfix release

  • +
  • Fix bugs in tests for data package paths

  • +
  • Fix leaks of open filehandles when loading images (thanks to Gael +Varoquaux for the report)

  • +
  • Skip rw tests for SPM images when scipy not installed

  • +
  • Fix various windows-specific file issues for tests

  • +
  • Fix incorrect reading of byte-swapped trackvis files

  • +
  • Workaround for odd numpy dtype comparisons leading to header errors for +some loaded images (thanks to Cindee Madison for the report)

  • +
+
+
+

1.0.0 (Thursday, 13, Oct 2010)

+
    +
  • This is the first public release of the NiBabel package.

  • +
  • NiBabel is a complete rewrite of the PyNifti package in pure python. It was +designed to make the code simpler and easier to work with. Like PyNifti, +NiBabel has fairly comprehensive NIfTI read and write support.

  • +
  • Extended support for SPM Analyze images, including orientation affines from +matlab .mat files.

  • +
  • Basic support for simple MINC 1.0 files (MB). Please let us know if you +have MINC files that we don’t support well.

  • +
  • Support for reading and writing PAR/REC images (MH)

  • +
  • parrec2nii script to convert PAR/REC images to NIfTI format (MH)

  • +
  • Very preliminary, limited and highly experimental DICOM reading support (MB, +Ian Nimmo Smith).

  • +
  • Some functions (nibabel.funcs) for basic image shape changes, including +the ability to transform to the image with data closest to the canonical +image orientation (first axis left-to-right, second back-to-front, third +down-to-up) (MB, Jonathan Taylor)

  • +
  • Gifti format read and write support (preliminary) (Stephen Gerhard)

  • +
  • Added utilities to use nipy-style data packages, by rip then edit of nipy +data package code (MB)

  • +
  • Some improvements to release support (Jarrod Millman, MB, Fernando Perez)

  • +
  • Huge downward step in the quality and coverage by the docs, caused by MB, +mostly fixed by a lot of good work by MH.

  • +
  • NiBabel will not work with Python < 2.5, and we haven’t even tested it with +Python 3. We will get to it soon…

  • +
+
+
+
+

PyNifti releases

+

Modifications are done by Michael Hanke, if not indicated otherwise. ‘Closes’ +statement IDs refer to the Debian bug tracking system and can be queried by +visiting the URL:

+
http://bugs.debian.org/<bug id>
+
+
+
+

0.20100706.1 (Tue, 6 Jul 2010)

+
    +
  • Bugfix: NiftiFormat.vx2s() used the qform not the sform. Thanks to Tom +Holroyd for reporting.

  • +
+
+
+

0.20100412.1 (Mon, 12 Apr 2010)

+
    +
  • Bugfix: Unfortunate interaction between Python garbage collection and C +library caused memory problems. Thanks to Yaroslav Halchenko for the +diagnose and fix.

  • +
+
+
+

0.20090303.1 (Tue, 3 Mar 2009)

+
    +
  • Bugfix: Updating the NIfTI header from a dictionary was broken.

  • +
  • Bugfix: Removed left-over print statement in extension code.

  • +
  • Bugfix: Prevent saving of bogus ‘None.nii’ images when the filename +was previously assign, before calling NiftiImage.save() (Closes: #517920).

  • +
  • Bugfix: Extension length was to short for all edata whose length matches +n*16-8, for all integer n.

  • +
+
+
+

0.20090205.1 (Thu, 5 Feb 2009)

+
    +
  • This release is the first in a series that aims stabilize the API and +finally result in PyNIfTI 1.0 with full support of the NIfTI1 standard.

  • +
  • The whole package was restructured. The included renaming +nifti.nifti(image,format,clibs) to nifti.(image,format,clibs). Redirect +modules make sure that existing user code will not break, but they will +issue a DeprecationWarning and will be removed with the release of PyNIfTI +1.0.

  • +
  • Added a special extension that can embed any serializable Python object +into the NIfTI file header. The contents of this extension is +automatically expanded upon request into the .meta attribute of each +NiftiImage. When saving files to disk the content of the dictionary is also +automatically dumped into this extension. +Embedded meta data is not loaded automatically, since this has security +implications, because code from the file header is actually executed. +The documentation explicitly mentions this risk.

  • +
  • Added NiftiExtensions. This is a container-like +handler to access and manipulate NIfTI1 header extensions.

  • +
  • Exposed MemMappedNiftiImage in the root module.

  • +
  • Moved cropImage() into the utils module.

  • +
  • From now on Sphinx is used to generate the documentation. This includes a +module reference that replaces that old API reference.

  • +
  • Added methods vx2q() and +vx2s() to convert voxel indices into +coordinates defined by qform or sform respectively.

  • +
  • Updating the cal_min and cal_max values in the NIfTI header when +saving a file is now conditional, but remains enabled by default.

  • +
  • Full set of methods to query and modify axis units. This includes +expanding the previous xyzt_units field in the header dictionary into +editable xyz_unit and time_unit fields. The former xyzt_units field +is no longer available. See: +getXYZUnit(), +setXYZUnit(), +getTimeUnit(), +setTimeUnit(), +xyz_unit, +time_unit

  • +
  • Full set of methods to query and manuipulate qform and sform codes. See: +getQFormCode(), +setQFormCode(), +getSFormCode(), +setSFormCode(), +qform_code, +sform_code

  • +
  • Each image instance is now able to generate a human-readable dump of its +most important header information via __str__().

  • +
  • NiftiImage objects can now be pickled.

  • +
  • Switched to NumPy’s distutils for building the package. Cleaned and +simplified the build procedure. Added optimization flags to SWIG call.

  • +
  • nifti.image.NiftiImage.filename can now also be used to assign a +filename.

  • +
  • Introduced nifti.__version__ as canonical version string.

  • +
  • Removed updateQFormFromQuarternion() from the list of public methods of +NiftiFormat. This is an internal method that +should not be used in user code. However, a redirect to the new method +will remain in-place until PyNIfTI 1.0.

  • +
  • Bugfix: getScaledData() returns a +unmodified data array if slope is set to zero (as required by the NIfTI +standard). Thanks to Thomas Ross for reporting.

  • +
  • Bugfix: Unicode filenames are now handled properly, as long as they do not +contain pure-unicode characters (since the NIfTI library does not support +them). Thanks to Gaël Varoquaux for reporting this issue.

  • +
+
+
+

0.20081017.1 (Fri, 17 Oct 2008)

+
    +
  • Updated included minimal copy of the nifticlibs to version 1.1.0.

  • +
  • Few changes to the Makefiles to enhance Posix compatibility. Thanks to +Chris Burns.

  • +
  • When building on non-Debian systems, only add include and library paths +pointing to the local nifticlibs copy, when it is actually built. +On Debian system the local copy is still not used at all, as a proper +nifticlibs package is guaranteed to be available.

  • +
  • Added minimal setup_egg.py for setuptools users. Thanks to Gaël Varoquaux.

  • +
  • PyNIfTI now does a proper wrapping of the image data with NumPy arrays, +which no longer leads to accidental memory leaks, when accessing array +data that has not been copied before (e.g. via the data property of +NiftiImage). Thanks to Gaël Varoquaux for mentioning this possibility.

  • +
+
+
+

0.20080710.1 (Thu, 7 Jul 2008)

+
    +
  • Bugfix: Pointer bug introduced by switch to new NumPy API in 0.20080624 +Thanks to Christopher Burns for fixing it.

  • +
  • Bugfix: Honored DeprecationWarning: sync() -> flush() for memory mapped +arrays. Again thanks to Christopher Burns.

  • +
  • More unit tests and other improvements (e.g. fixed circular imports) done +by Christopher Burns.

  • +
+
+
+

0.20080630.1 (Tue, 30 Jun 2008)

+
    +
  • Bugfix: NiftiImage caused a memory leak by not calling the NiftiFormat +destructor.

  • +
  • Bugfix: Merged bashism-removal patch from Debian packaging.

  • +
+
+
+

0.20080624.1 (Tue, 24 Jun 2008)

+
    +
  • Converted all documentation (including docstrings) into the restructured +text format.

  • +
  • Improved Makefile.

  • +
  • Included configuration and Makefile support for profiling, API doc +generation (via epydoc) and code quality checks (with PyLint).

  • +
  • Consistently import NumPy as N.

  • +
  • Bugfix: Proper handling of [qs]form codes, which previously have not been +handled at all. Thanks to Christopher Burns for pointing it out.

  • +
  • Bugfix: Make NiftiFormat work without setFilename(). Thanks to Benjamin +Thyreau for reporting.

  • +
  • Bugfix: setPixDims() stored meaningless values.

  • +
  • Use new NumPy API and replace deprecated function calls +(PyArray_FromDimsAndData).

  • +
  • Initial support for memory mapped access to uncompressed NIfTI files +(MemMappedNiftiImage).

  • +
  • Add a proper Makefile and setup.cfg for compiling PyNIfTI under Windows +with MinGW.

  • +
  • Include a minimal copy of the most recent nifticlibs (just libniftiio and +znzlib; version 1.0), to lower the threshold to build PyNIfTI on systems +that do not provide a developer package for those libraries.

  • +
+
+
+

0.20070930.1 (Sun, 30 Sep 2007)

+ +
+
+

0.20070917.1 (Mon, 17 Sep 2007)

+
    +
  • Bugfix: Can now update NIfTI header data when no filename is set +(Closes: #442175).

  • +
  • Unloading of image data without a filename set is no checked and prevented +as it would damage data integrity and the image data could not be +recovered.

  • +
  • Added ‘pixdim’ property (Yaroslav Halchenko).

  • +
+
+
+

0.20070905.1 (Wed, 5 Sep 2007)

+
    +
  • Fixed a bug in the qform/quaternion handling that caused changes to the +qform to vanish when saving to file (Yaroslav Halchenko).

  • +
  • Added more unit tests.

  • +
  • ‘dim’ vector in the NIfTI header is now guaranteed to only contain +non-zero elements. This caused problems with some applications.

  • +
+
+
+

0.20070803.1 (Fri, 3 Aug 2007)

+
    +
  • Does not depend on SciPy anymore.

  • +
  • Initial steps towards a unittest suite.

  • +
  • pynifti_pst can now print the peristimulus signal matrix for a single +voxel (onsets x time) for easier processing of this information in +external applications.

  • +
  • utils.getPeristimulusTimeseries() can now be used to compute mean and +variance of the signal (among others).

  • +
  • pynifti_pst is able to compute more than just the mean peristimulus +timeseries (e.g. variance and standard deviation).

  • +
  • Set default image description when saving a file if none is present.

  • +
  • Improved documentation.

  • +
+
+
+

0.20070425.1 (Wed, 25 Apr 2007)

+
    +
  • Improved documentation. Added note about the special usage of the header +property. Also added notes about the relevant properties in the docstring +of the corresponding accessor methods.

  • +
  • Added property and accessor methods to access/modify the repetition time +of timeseries (dt).

  • +
  • Added functions to manipulate the pixdim values.

  • +
  • Added utils.py with some utility functions.

  • +
  • Added functions/property to determine the bounding box of an image.

  • +
  • Fixed a bug that caused a corrupted sform matrix when converting a NumPy +array and a header dictionary into a NIfTI image.

  • +
  • Added script to compute peristimulus timeseries (pynifti_pst).

  • +
  • Package now depends on python-scipy.

  • +
+
+
+

0.20070315.1 (Thu, 15 Mar 2007)

+
    +
  • Removed functionality for “NiftiImage.save() raises an IOError +exception when writing the image file fails.” (Yaroslav Halchenko)

  • +
  • Added ability to force a filetype when setting the filename or saving +a file.

  • +
  • Reverse the order of the ‘header’ and ‘load’ argument in the NiftiImage +constructor. ‘header’ is now first as it seems to be used more often.

  • +
  • Improved the source code documentation.

  • +
  • Added getScaledData() method to NiftiImage that returns a copy of the data +array that is scaled with the slope and intercept stored in the NIfTI +header.

  • +
+
+
+

0.20070301.2 (Thu, 1 Mar 2007)

+
    +
  • Fixed wrong link to the source tarball in README.html.

  • +
+
+
+

0.20070301.1 (Thu, 1 Mar 2007)

+
    +
  • Initial upload to the Debian archive. (Closes: #413049)

  • +
  • NiftiImage.save() raises an IOError exception when writing the image file +fails.

  • +
  • Added extent, volextent, and timepoints properties to NiftiImage +class (Yaroslav Halchenko).

  • +
+
+
+

0.20070220.1 (Tue, 20 Feb 2007)

+
    +
  • NiftiFile class is renamed to NiftiImage.

  • +
  • SWIG-wrapped libniftiio functions are no available in the nifticlib +module.

  • +
  • Fixed broken NiftiImage from Numpy array constructor.

  • +
  • Added initial documentation in README.html.

  • +
  • Fulfilled a number of Yarik’s wishes ;)

  • +
+
+
+

0.20070214.1 (Wed, 14 Feb 2007)

+
    +
  • Does not depend on libfslio anymore.

  • +
  • Up to seven-dimensional dataset are supported (as much as NIfTI can do).

  • +
  • The complete NIfTI header dataset is modifiable.

  • +
  • Most image properties are accessible via class attributes and accessor +methods.

  • +
  • Improved documentation (but still a long way to go).

  • +
+
+
+

0.20061114 (Tue, 14 Nov 2006)

+
    +
  • Initial release.

  • +
+
+
+
+ + +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/codecov.yml b/codecov.yml deleted file mode 100644 index 0285fa4b06..0000000000 --- a/codecov.yml +++ /dev/null @@ -1,2 +0,0 @@ -fixes: - - "venv/Lib/site-packages/::" diff --git a/coordinate_systems.html b/coordinate_systems.html new file mode 100644 index 0000000000..e5e9e5b73d --- /dev/null +++ b/coordinate_systems.html @@ -0,0 +1,928 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + + +
+
+ +
+
+

NiBabel

+

Access a cacophony of neuro-imaging file formats

+
+
+ + + + +
+
+
+
+ +
+

Coordinate systems and affines

+

A nibabel (and nipy) image is the association of three things:

+
    +
  • The image data array: a 3D or 4D array of image data

  • +
  • An affine array that tells you the position of the image array data in +a reference space.

  • +
  • image metadata (data about the data) describing the image, usually in the +form of an image header.

  • +
+

This document describes how the affine array describes the position of the +image data in a reference space. On the way we will define what we mean by +reference space, and the reference spaces that Nibabel uses.

+
+

Introducing Someone

+

We have scanned someone called “Someone”, and we have a two MRI images of +their brain, a single EPI volume, and a structural scan. In general we never +use the person’s name in the image filenames, but we make an +exception in this case:

+ +

We can load up the EPI image to get the image data array:

+
>>> import nibabel as nib
+>>> epi_img = nib.load('downloads/someones_epi.nii.gz')
+>>> epi_img_data = epi_img.get_fdata()
+>>> epi_img_data.shape
+(53, 61, 33)
+
+
+

Then we have a look at slices over the first, second and third dimensions of +the array.

+
>>> import matplotlib.pyplot as plt
+>>> def show_slices(slices):
+...    """ Function to display row of image slices """
+...    fig, axes = plt.subplots(1, len(slices))
+...    for i, slice in enumerate(slices):
+...        axes[i].imshow(slice.T, cmap="gray", origin="lower")
+>>>
+>>> slice_0 = epi_img_data[26, :, :]
+>>> slice_1 = epi_img_data[:, 30, :]
+>>> slice_2 = epi_img_data[:, :, 16]
+>>> show_slices([slice_0, slice_1, slice_2])
+>>> plt.suptitle("Center slices for EPI image")  
+
+
+

(png, hires.png, pdf)

+
+_images/coordinate_systems-2.png +
+

We collected an anatomical image in the same session. We can load that image +and look at slices in the three axes:

+
>>> anat_img = nib.load('downloads/someones_anatomy.nii.gz')
+>>> anat_img_data = anat_img.get_fdata()
+>>> anat_img_data.shape
+(57, 67, 56)
+>>> show_slices([anat_img_data[28, :, :],
+...              anat_img_data[:, 33, :],
+...              anat_img_data[:, :, 28]])
+>>> plt.suptitle("Center slices for anatomical image")  
+
+
+
+_images/coordinate_systems-3_00.png +
+

(png, hires.png, pdf)

+
+
+
+_images/coordinate_systems-3_01.png +
+

(png, hires.png, pdf)

+
+
+

As is usually the case, we had a different field of view for the anatomical +scan, and so the anatomical image has a different shape, size, and orientation +in the magnet.

+
+
+

Voxel coordinates are coordinates in the image data array

+

As y’all know, a voxel is a pixel with volume.

+

In the code above, slice_0 from the EPI data is a 2D slice from a 3D +image. The plot of the EPI slices displays the slices in grayscale (graded +between black for the minimum value, white for the maximum). Each pixel in +the slice grayscale image also represents a voxel, because this 2D image +represents a slice from the 3D image with a certain thickness.

+

The 3D array is therefore also a voxel array. As for any array, we can select +particular values by indexing. For example, we can get the value for the +middle voxel in the EPI data array like this:

+
>>> n_i, n_j, n_k = epi_img_data.shape
+>>> center_i = (n_i - 1) // 2  # // for integer division
+>>> center_j = (n_j - 1) // 2
+>>> center_k = (n_k - 1) // 2
+>>> center_i, center_j, center_k
+(26, 30, 16)
+>>> center_vox_value = epi_img_data[center_i, center_j, center_k]
+>>> center_vox_value
+81.5492877960205...
+
+
+

The values (26, 30, 16) are indices into the data array epi_img_data. (26, +30, 16) is therefore a ‘voxel coordinate’ - a coordinate into the voxel array.

+

A coordinate is a set of numbers giving positions relative to a set of axes. +In this case 26 is a position on the first array axis, where the axis is of +length epi_img_data.shape[0], and therefore goes from 0 to 52 +(epi_img_data.shape == (53, 61, 33)). Similarly 30 gives a position on +the second axis (0 to 60) and 16 is the position on the third axis (0 to 32).

+
+
+

Voxel coordinates and points in space

+

The voxel coordinate tells us almost nothing about where the data came from +in terms of position in the scanner. For example, let’s say we have the voxel +coordinate (26, 30, 16). Without more information we have no idea whether +this voxel position is on the left or right of the brain, or came from the +left or right of the scanner.

+

This is because the scanner allows us to collect voxel data in almost any +arbitrary position and orientation within the magnet.

+

In the case of Someone’s EPI, we took transverse slices at a moderate angle to +the floor to ceiling direction. This localizer image from the scanner console +has a red box that shows the position of the slice block for +someones_epi.nii.gz and a blue box for the slice block of +someones_anatomy.nii.gz:

+_images/localizer.png +

The localizer is oriented to the magnet, so that the left and right borders of +the image are parallel to the floor of the scanner room, with the left border +being towards the floor and the right border towards the ceiling.

+

You will see from the labels on the localizer that the center of the EPI voxel +data block (at 26, 30, 16 in epi_img_data) is not quite at the center of +magnet bore (the magnet isocenter).

+

We have an anatomical and an EPI scan, and later on we will surely want to be +able to relate the data from someones_epi.nii.gz to +someones_anatomy.nii.gz. We can’t easily do this at the moment, because +we collected the anatomical image with a different field of view and +orientation to the EPI image, so the voxel coordinates in the EPI image refer +to different locations in the magnet to the voxel coordinates in the +anatomical image.

+

We solve this problem by keeping track of the relationship of voxel +coordinates to some reference space. In particular, the affine array +stores the relationship between voxel coordinates in the image data array and +coordinates in the reference space. We store the relationship of voxel +coordinates from someones_epi.nii.gz and the reference space, and also the +(different) relationship of voxel coordinates in someones_anatomy.nii.gz +to the same reference space. Because we know the relationship of (voxel +coordinates to the reference space) for both images, we can use this +information to relate voxel coordinates in someones_epi.nii.gz to spatially +equivalent voxel coordinates in someones_anatomy.nii.gz.

+
+
+

The scanner-subject reference space

+

What does “space” mean in the phrase “reference space”? The space is defined +by an ordered set of axes. For our 3D spatial world, it is a set of 3 +independent axes.

+

We can decide what space we want to use, by choosing these axes. We need to +choose the origin of the axes, their direction and their units.

+

To start with, we define a set of three orthogonal scanner axes.

+
+

The scanner axes

+
    +
  • The origin of the axes is at the magnet isocenter. This is coordinate (0, 0, +0) in our reference space. All three axes pass through the isocenter.

  • +
  • The units for all three axes are millimeters.

  • +
  • Imagine an observer standing behind the scanner looking through the magnet +bore towards the end of the scanner bed. Imagine a line traveling towards +the observer through the center of the magnet bore, parallel to the bed, +with the zero point at the magnet isocenter, and positive values closer to +the observer. Call this line the scanner-bore axis.

  • +
  • Draw a line traveling from the scanner room floor up through the magnet +isocenter towards the ceiling, at right angles to the scanner bore axis. +0 is at isocenter and positive values are towards the ceiling. Call this +the scanner-floor/ceiling axis.

  • +
  • Draw a line at right angles to the other two lines, traveling from the +observer’s left, parallel to the floor, and through the magnet isocenter to +the observer’s right. 0 is at isocenter and positive values are to the +right. Call this the scanner-left/right.

  • +
+

If we make the axes have order (scanner left-right; scanner floor-ceiling; +scanner bore) then we have an ordered set of 3 axes and therefore the +definition of a 3D space. Call the first axis the “X” axis, the second “Y” +and the third “Z”. A coordinate of \((x, y, z) = (10, -5, -3)\) in this space +refers to the point in space 10mm to the (fictional observer’s) right of +isocenter, 5mm towards the floor from the isocenter, and 3mm towards the foot +of the scanner bed. This reference space is sometimes known as “scanner XYZ”. +It was the standard reference space for the predecessor to DICOM, called ACR / +NEMA 2.0.

+
+
+

From scanner to subject

+

If the subject is lying in the usual position for a brain scan, face up +and head first in the scanner, then scanner-left/right is also the left-right +axis of the subject’s head, scanner-floor/ceiling is the posterior-anterior +axis of the head and scanner-bore is the inferior-superior axis of the head.

+

Sometimes the subject is not lying in the standard position. For example, the +subject may be lying with their face pointing to the right (in terms of the +scanner-left/right axis). In that case “scanner XYZ” will not tell us about +the subject’s left and right, but only the scanner left and right. We might +prefer to know where we are in terms of the subject’s left and right.

+

To deal with this problem, most reference spaces use subject- or patient- +centered scanner coordinate systems. In these systems, the axes are still the +scanner axes above, but the ordering and direction of the axes comes from the +position of the subject. The most common subject-centered scanner coordinate +system in neuroimaging is called “scanner RAS” (right, anterior, superior). +Here the scanner axes are reordered and flipped so that the first axis is the +scanner axis that is closest to the left to right axis of the subject, the +second is the closest scanner axis to the posterior-anterior axis of the +subject, and the third is the closest scanner axis to the inferior-superior +axis of the subject. For example, if the subject was lying face to the right +in the scanner, then the first (X) axis of the reference system would be +scanner-floor/ceiling, but reversed so that positive values are towards the +floor. This axis goes from left to right in the subject, with positive values +to the right. The second (Y) axis would be scanner-left/right +(posterior-anterior in the subject), and the Z axis would be scanner-bore +(inferior-superior).

+
+
+

Naming reference spaces

+

Reading names of reference spaces can be confusing because of different +meanings that authors use for the same terms, such as ‘left’ and ‘right’.

+

We are using the term “RAS” to mean that the axes are (in terms of the +subject): left to Right; posterior to Anterior; and inferior to Superior, +respectively. Although it is common to call this convention “RAS”, it is not +quite universal, because some use “R”, “A” and “S” in “RAS” to mean that the +axes starts on the right, anterior, superior of the subject, rather than +ending on the right, anterior, superior. In other words, they would use +“RAS” to refer to a coordinate system we would call “LPI”. To be safe, we’ll +call our interpretation of the RAS convention “RAS+”, meaning that Right, +Anterior, Superior are all positive values on these axes.

+

Some people also use “right” to mean the right hand side when an observer +looks at the front of the scanner, from the foot the scanner bed. +Unfortunately, this means that you have to read coordinate system definitions +carefully if you are not familiar with a particular convention. We nibabel / +nipy folks agree with most of our brain imaging friends and many of our +enemies in that we always use “right” to mean the subject’s right.

+
+
+
+

Voxel coordinates are in voxel space

+

We have not yet made this explicit, but voxel coordinates are also in a space. +In this case the space is defined by the three voxel axes (first axis, second +axis, third axis), where 0, 0, 0 is the center of the first voxel in the +array and the units on the axes are voxels. Voxel coordinates are therefore +defined in a reference space called voxel space.

+
+
+

The affine matrix as a transformation between spaces

+

We have voxel coordinates (in voxel space). We want to get scanner RAS+ +coordinates corresponding to the voxel coordinates. We need a coordinate +transform to take us from voxel coordinates to scanner RAS+ coordinates.

+

In general, we have some voxel space coordinate \((i, j, k)\), and we want to +generate the reference space coordinate \((x, y, z)\).

+

Imagine we had solved this, and we had a coordinate transform function \(f\) +that accepts a voxel coordinate and returns a coordinate in the reference +space:

+
+\[(x, y, z) = f(i, j, k)\]
+

\(f\) accepts a coordinate in the input space and returns a coordinate in the +output space. In our case the input space is voxel space and the output +space is scanner RAS+.

+

In theory \(f\) could be a complicated non-linear function, but in practice, we +know that the scanner collects data on a regular grid. This means that the +relationship between \((i, j, k)\) and \((x, y, z)\) is linear (actually +affine), and can be encoded with linear (actually affine) transformations +comprising translations, rotations and zooms (wikipedia linear transform, +wikipedia affine transform).

+

Scaling (zooming) in three dimensions can be represented by a diagonal 3 by 3 +matrix. Here’s how to zoom the first dimension by \(p\), the second by \(q\) and +the third by \(r\) units:

+
+\[\begin{split}\begin{bmatrix} +x\\ +y\\ +z\\ +\end{bmatrix} = +\begin{bmatrix} +p i\\ +q j\\ +r k\\ +\end{bmatrix} = +\begin{bmatrix} +p & 0 & 0 \\ +0 & q & 0 \\ +0 & 0 & r \\ +\end{bmatrix} +\begin{bmatrix} +i\\ +j\\ +k\\ +\end{bmatrix}\end{split}\]
+

A rotation in three dimensions can be represented as a 3 by 3 rotation +matrix (wikipedia rotation matrix). For example, here is a rotation by +\(\theta\) radians around the third array axis:

+
+\[\begin{split}\begin{bmatrix} +x\\ +y\\ +z\\ +\end{bmatrix} = +\begin{bmatrix} +\cos(\theta) & -\sin(\theta) & 0 \\ +\sin(\theta) & \cos(\theta) & 0 \\ +0 & 0 & 1 \\ +\end{bmatrix} +\begin{bmatrix} +i\\ +j\\ +k\\ +\end{bmatrix}\end{split}\]
+

This is a rotation by \(\phi\) radians around the second array axis:

+
+\[\begin{split}\begin{bmatrix} +x\\ +y\\ +z\\ +\end{bmatrix} = +\begin{bmatrix} +\cos(\phi) & 0 & \sin(\phi) \\ +0 & 1 & 0 \\ +-\sin(\phi) & 0 & \cos(\phi) \\ +\end{bmatrix} +\begin{bmatrix} +i\\ +j\\ +k\\ +\end{bmatrix}\end{split}\]
+

A rotation of \(\gamma\) radians around the first array axis:

+
+\[\begin{split}\begin{bmatrix} +x\\ +y\\ +z\\ +\end{bmatrix} = +\begin{bmatrix} +1 & 0 & 0 \\ +0 & \cos(\gamma) & -\sin(\gamma) \\ +0 & \sin(\gamma) & \cos(\gamma) \\ +\end{bmatrix} +\begin{bmatrix} +i\\ +j\\ +k\\ +\end{bmatrix}\end{split}\]
+

Zoom and rotation matrices can be combined by matrix multiplication.

+

Here’s a scaling of \(p, q, r\) units followed by a rotation of \(\theta\) radians +around the third axis followed by a rotation of \(\phi\) radians around the +second axis:

+
+\[\begin{split}\begin{bmatrix} +x\\ +y\\ +z\\ +\end{bmatrix} = +\begin{bmatrix} +\cos(\phi) & 0 & \sin(\phi) \\ +0 & 1 & 0 \\ +-\sin(\phi) & 0 & \cos(\phi) \\ +\end{bmatrix} +\begin{bmatrix} +\cos(\theta) & -\sin(\theta) & 0 \\ +\sin(\theta) & \cos(\theta) & 0 \\ +0 & 0 & 1 \\ +\end{bmatrix} +\begin{bmatrix} +p & 0 & 0 \\ +0 & q & 0 \\ +0 & 0 & r \\ +\end{bmatrix} +\begin{bmatrix} +i\\ +j\\ +k\\ +\end{bmatrix}\end{split}\]
+

This can also be written:

+
+\[ \begin{align}\begin{aligned}\begin{split}M = +\begin{bmatrix} +\cos(\phi) & 0 & \sin(\phi) \\ +0 & 1 & 0 \\ +-\sin(\phi) & 0 & \cos(\phi) \\ +\end{bmatrix} +\begin{bmatrix} +\cos(\theta) & -\sin(\theta) & 0 \\ +\sin(\theta) & \cos(\theta) & 0 \\ +0 & 0 & 1 \\ +\end{bmatrix} +\begin{bmatrix} +p & 0 & 0 \\ +0 & q & 0 \\ +0 & 0 & r \\ +\end{bmatrix}\end{split}\\\begin{split}\begin{bmatrix} +x\\ +y\\ +z\\ +\end{bmatrix} = M +\begin{bmatrix} +i\\ +j\\ +k\\ +\end{bmatrix}\end{split}\end{aligned}\end{align} \]
+

This might be obvious because the matrix multiplication is the result of +applying each transformation in turn on the coordinates output from the +previous transformation. Combining the transformations into a single matrix +\(M\) works because matrix multiplication is associative – \(ABCD = (ABC)D\).

+

A translation in three dimensions can be represented as a length 3 vector to +be added to the length 3 coordinate. For example, a translation of \(a\) units +on the first axis, \(b\) on the second and \(c\) on the third might be written +as:

+
+\[\begin{split}\begin{bmatrix} +x\\ +y\\ +z\\ +\end{bmatrix} = +\begin{bmatrix} +i\\ +j\\ +k\\ +\end{bmatrix} + +\begin{bmatrix} +a \\ +b \\ +c \\ +\end{bmatrix}\end{split}\]
+

We can write our function \(f\) as a combination of matrix multiplication by +some 3 by 3 rotation / zoom matrix \(M\) followed by addition of a 3 by 1 +translation vector \((a, b, c)\)

+
+\[\begin{split}\begin{bmatrix} +x\\ +y\\ +z\\ +\end{bmatrix} = M +\begin{bmatrix} +i\\ +j\\ +k\\ +\end{bmatrix} + +\begin{bmatrix} +a\\ +b\\ +c\\ +\end{bmatrix}\end{split}\]
+

We could record the parameters necessary for \(f\) as the 3 by 3 matrix, \(M\) +and the 3 by 1 vector \((a, b, c)\).

+

In fact, the 4 by 4 image affine array does include exactly this +information. If \(m_{i,j}\) is the value in row \(i\) column \(j\) of matrix \(M\), +then the image affine matrix \(A\) is:

+
+\[\begin{split}A = +\begin{bmatrix} +m_{1,1} & m_{1,2} & m_{1,3} & a \\ +m_{2,1} & m_{2,2} & m_{2,3} & b \\ +m_{3,1} & m_{3,2} & m_{3,3} & c \\ +0 & 0 & 0 & 1 \\ +\end{bmatrix}\end{split}\]
+

Why the extra row of \([0, 0, 0, 1]\)? We need this row because we have +rephrased the combination of rotations / zooms and translations as a +transformation in homogeneous coordinates (see wikipedia homogeneous +coordinates). This is a trick that allows us to put the translation part +into the same matrix as the rotations / zooms, so that both translations and +rotations / zooms can be applied by matrix multiplication. In order to make +this work, we have to add an extra 1 to our input and output coordinate +vectors:

+
+\[\begin{split}\begin{bmatrix} +x\\ +y\\ +z\\ +1\\ +\end{bmatrix} = +\begin{bmatrix} +m_{1,1} & m_{1,2} & m_{1,3} & a \\ +m_{2,1} & m_{2,2} & m_{2,3} & b \\ +m_{3,1} & m_{3,2} & m_{3,3} & c \\ +0 & 0 & 0 & 1 \\ +\end{bmatrix} +\begin{bmatrix} +i\\ +j\\ +k\\ +1\\ +\end{bmatrix}\end{split}\]
+

This results in the same transformation as applying \(M\) and \((a, b, c)\) +separately. One advantage of encoding transformations this way is that we can +combine two sets of [rotations, zooms, translations] by matrix multiplication +of the two corresponding affine matrices.

+

In practice, although it is common to combine 3D transformations using 4 by 4 +affine matrices, we usually apply the transformations by breaking up the +affine matrix into its component \(M\) matrix and \((a, b, c)\) vector and doing:

+
+\[\begin{split}\begin{bmatrix} +x\\ +y\\ +z\\ +\end{bmatrix} = M +\begin{bmatrix} +i\\ +j\\ +k\\ +\end{bmatrix} + +\begin{bmatrix} +a\\ +b\\ +c\\ +\end{bmatrix}\end{split}\]
+

As long as the last row of the 4 by 4 is \([0, 0, 0, 1]\), applying the +transformations in this way is mathematically the same as using the full 4 by +4 form, without the inconvenience of adding the extra 1 to our input and +output vectors.

+
+
+

The inverse of the affine gives the mapping from scanner to voxel

+

The affine arrays we have described so far have another pleasant property — +they are usually invertible. As y’all know, the inverse of a matrix \(A\) is +the matrix \(A^{-1}\) such that \(I = A^{-1} A\), where \(I\) is the identity +matrix. Put another way:

+
+\[ \begin{align}\begin{aligned}\begin{split}\begin{bmatrix} +x\\ +y\\ +z\\ +1\\ +\end{bmatrix} = A +\begin{bmatrix} +i\\ +j\\ +k\\ +1\\ +\end{bmatrix}\end{split}\\\begin{split}A^{-1}\begin{bmatrix} +x\\ +y\\ +z\\ +1\\ +\end{bmatrix} = A^{-1} A +\begin{bmatrix} +i\\ +j\\ +k\\ +1\\ +\end{bmatrix}\end{split}\\\begin{split}\begin{bmatrix} +i\\ +j\\ +k\\ +1\\ +\end{bmatrix} = A^{-1} +\begin{bmatrix} +x\\ +y\\ +z\\ +1\\ +\end{bmatrix}\end{split}\end{aligned}\end{align} \]
+

That means that the inverse of the affine matrix gives the transformation from +scanner RAS+ coordinates to voxel coordinates in the image data.

+

Now imagine we have affine array \(A\) for someones_epi.nii.gz, and affine array +\(B\) for someones_anatomy.nii.gz. \(A\) gives the mapping from voxels in the +image data array of someones_epi.nii.gz to millimeters in scanner RAS+. \(B\) +gives the mapping from voxels in image data array of +someones_anatomy.nii.gz to the same scanner RAS+. Now let’s say we have +a particular voxel coordinate \((i, j, k)\) in the data array of +someones_epi.nii.gz, and we want to find the voxel in +someones_anatomy.nii.gz that is in the same spatial position. Call this +matching voxel coordinate \((i', j', k')\) . We first apply the transform from +someones_epi.nii.gz voxels to scanner RAS+ (\(A\)) and then apply the transform +from scanner RAS+ to voxels in someones_anatomy.nii.gz (\(B^{-1}\)):

+
+\[\begin{split}\begin{bmatrix} +i'\\ +j'\\ +k'\\ +1\\ +\end{bmatrix} = B^{-1} A +\begin{bmatrix} +i\\ +j\\ +k\\ +1\\ +\end{bmatrix}\end{split}\]
+
+
+

The affine by example

+

We can get the affine from the nibabel image object. Here is the affine for +the EPI scan:

+
>>> # Set numpy to print 3 decimal points and suppress small values
+>>> import numpy as np
+>>> np.set_printoptions(precision=3, suppress=True)
+>>> # Print the affine
+>>> epi_img.affine
+array([[  3.   ,   0.   ,   0.   , -78.   ],
+       [  0.   ,   2.866,  -0.887, -76.   ],
+       [  0.   ,   0.887,   2.866, -64.   ],
+       [  0.   ,   0.   ,   0.   ,   1.   ]])
+
+
+

As you see, the last row is \([0, 0, 0, 1]\)

+
+

Applying the affine

+

To make the affine simpler to apply, we split it into \(M\) and \((a, b, c)\):

+
>>> M = epi_img.affine[:3, :3]
+>>> abc = epi_img.affine[:3, 3]
+
+
+

Then we can define our function \(f\):

+
>>> def f(i, j, k):
+...    """ Return X, Y, Z coordinates for i, j, k """
+...    return M.dot([i, j, k]) + abc
+
+
+

The labels on the localizer image give the impression +that the center voxel of someones_epi.nii.gz was a little above the magnet +isocenter. Now we can check:

+
>>> epi_vox_center = (np.array(epi_img_data.shape) - 1) / 2.
+>>> f(epi_vox_center[0], epi_vox_center[1], epi_vox_center[2])
+array([ 0.   , -4.205,  8.453])
+
+
+

That means the center of the image field of view is at the isocenter of the +magnet on the left to right axis, and is around 4.2mm posterior to the +isocenter and ~8.5 mm above the isocenter.

+

The parameters in the affine array can therefore give the position of any +voxel coordinate, relative to the scanner RAS+ reference space.

+

We get the same result from applying the affine directly instead of using \(M\) +and \((a, b, c)\) in our function. As above, we need to add a 1 +to the end of the vector to apply the 4 by 4 affine matrix.

+
>>> epi_img.affine.dot(list(epi_vox_center) + [1])
+array([ 0.   , -4.205,  8.453,  1.   ])
+
+
+

In fact nibabel has a function apply_affine that applies an affine to an +\((i, j, k)\) point by splitting the affine into \(M\) and \(abc\) then multiplying +and adding as above:

+
>>> from nibabel.affines import apply_affine
+>>> apply_affine(epi_img.affine, epi_vox_center)
+array([ 0.   , -4.205,  8.453])
+
+
+

Now we can apply the affine, we can use matrix inversion on the anatomical +affine to map between voxels in the EPI image and voxels in the anatomical +image.

+
>>> import numpy.linalg as npl
+>>> epi_vox2anat_vox = npl.inv(anat_img.affine).dot(epi_img.affine)
+
+
+

What is the voxel coordinate in the anatomical corresponding to the voxel +center of the EPI image?

+
>>> apply_affine(epi_vox2anat_vox, epi_vox_center)
+array([28.364, 31.562, 36.165])
+
+
+

The voxel coordinate of the center voxel of the anatomical image is:

+
>>> anat_vox_center = (np.array(anat_img_data.shape) - 1) / 2.
+>>> anat_vox_center
+array([28. , 33. , 27.5])
+
+
+

The voxel location in the anatomical image that matches the center voxel of +the EPI image is nearly exactly half way across the first axis, a voxel or two +back from the anatomical voxel center on the second axis, and about 9 voxels +above the anatomical voxel center. We can check the localizer image by eye to see whether this makes sense, by seeing how the +red EPI field of view center relates to the blue anatomical field of view +center and the blue anatomical image field of view.

+
+
+

The affine as a series of transformations

+

You can think of the image affine as a combination of a series of +transformations to go from voxel coordinates to mm coordinates in terms of the +magnet isocenter. Here is the EPI affine broken down into a series of +transformations, with the results shown on the localizer image:

+_images/illustrating_affine.png +

We start by putting the voxel grid onto the isocenter coordinate +system, so a translation of one voxel equates to a translation of one +millimeter in the isocenter coordinate system. Our EPI image would then have +the black bounding box in the image above. Next we scale the voxels to +millimeters by scaling by the voxel size (green bounding box). We could do +this with an affine:

+
>>> scaling_affine = np.array([[3, 0, 0, 0],
+...                            [0, 3, 0, 0],
+...                            [0, 0, 3, 0],
+...                            [0, 0, 0, 1]])
+
+
+

After applying this affine, when we move one voxel in any direction, we are +moving 3 millimeters in that direction:

+
>>> one_vox_axis_0 = [1, 0, 0]
+>>> apply_affine(scaling_affine, one_vox_axis_0)
+array([3, 0, 0])
+
+
+

Next we rotate the scaled voxels around the first axis by 0.3 radians (see +rotate around first axis):

+
>>> cos_gamma = np.cos(0.3)
+>>> sin_gamma = np.sin(0.3)
+>>> rotation_affine = np.array([[1, 0, 0, 0],
+...                            [0, cos_gamma, -sin_gamma, 0],
+...                            [0, sin_gamma, cos_gamma, 0],
+...                            [0, 0, 0, 1]])
+>>> affine_so_far = rotation_affine.dot(scaling_affine)
+>>> affine_so_far
+array([[ 3.   ,  0.   ,  0.   ,  0.   ],
+       [ 0.   ,  2.866, -0.887,  0.   ],
+       [ 0.   ,  0.887,  2.866,  0.   ],
+       [ 0.   ,  0.   ,  0.   ,  1.   ]])
+
+
+

The EPI voxel block coordinates transformed by affine_so_far are at the +position of the yellow box on the figure.

+

Finally we translate the 0, 0, 0 coordinate at the bottom, posterior, left +corner of our array to be at its final position relative to the isocenter, +which is -78, -76, -64:

+
>>> translation_affine = np.array([[1, 0, 0, -78],
+...                                [0, 1, 0, -76],
+...                                [0, 0, 1, -64],
+...                                [0, 0, 0, 1]])
+>>> whole_affine = translation_affine.dot(affine_so_far)
+>>> whole_affine
+array([[  3.   ,   0.   ,   0.   , -78.   ],
+       [  0.   ,   2.866,  -0.887, -76.   ],
+       [  0.   ,   0.887,   2.866, -64.   ],
+       [  0.   ,   0.   ,   0.   ,   1.   ]])
+
+
+

This brings the affine-transformed voxel coordinates to the red box on the +figure, matching the position on the localizer.

+
+
+
+

Other reference spaces

+

The scanner RAS+ reference space is a “real-world” space, in the sense that a +coordinate in this space refers to a position in the real world, in a +particular scanner in a particular room.

+

Imagine that we used some fancy software to register someones_epi.nii.gz +to a template image, such as the Montreal Neurological Institute (MNI) +template brain. The registration has moved the voxels around in complicated +ways — the image has changed shape to match the template brain. We +probably do not want to know how the voxel locations relate to the original +scanner, but how they relate to the template brain. So, what reference space +should we use?

+

In this case we use a space defined in terms of the template brain — the MNI +reference space.

+
    +
  • The origin (0, 0, 0) point is defined to be the point that the anterior +commissure of the MNI template brain crosses the midline (the AC point).

  • +
  • Axis units are millimeters.

  • +
  • The Y axis follows the midline of the MNI brain between the left and right +hemispheres, going from posterior (negative) to anterior (positive), passing +through the AC point. The template defines this line.

  • +
  • The Z axis is at right angles to the Y axis, going from inferior (negative) +to superior (positive), with the superior part of the line passing between +the two hemispheres.

  • +
  • The X axis is a line going from the left side of the brain (negative) to +right side of the brain (positive), passing through the AC point, and at +right angles to the Y and Z axes.

  • +
+

These axes are defined with reference to the template. The exact position of +the Y axis, for example, is somewhat arbitrary, as is the definition of the +origin. Left and right are left and right as defined by the template. These +are the axes and the space that MNI defines for its template.

+

A coordinate in this reference system gives a position relative to the +particular brain template. It is not a real-world space because it does not +refer to any particular place but to a position relative to a template.

+

The axes are still left to right, posterior to anterior and inferior to +superior in terms of the template subject. This is still an RAS+ space — +the MNI RAS+ space.

+

An image aligned to this template will therefore have an affine giving the +relationship between voxels in the aligned image and the MNI RAS+ space.

+

There are other reference spaces. For example, we might align an image to the +Talairach atlas brain. This brain has a different shape and size than the MNI +brain. The origin is the AC point, but the Y axis passes through the point +that the posterior commissure crosses the midline (the PC point), giving a +slightly different trajectory from the MNI Y axis. Like the MNI RAS+ space, +the Talairach axes also run left to right, posterior to anterior and inferior +superior, so this is the Talairach RAS+ space.

+

There are conventions other than RAS+ for the reference space. For example, +DICOM files map input voxel coordinates to coordinates in scanner LPS+ space. +Scanner LPS+ space uses the same scanner axes and isocenter as scanner RAS+, +but the X axis goes from right to the subject’s Left, the Y axis goes from +anterior to Posterior, and the Z axis goes from inferior to Superior. A +positive X coordinate in this space would mean the point was to the subject’s +left compared to the magnet isocenter.

+
+
+

Nibabel always uses an RAS+ output space

+

Nibabel images always use RAS+ output coordinates, regardless of the preferred +output coordinates of the underlying format. For example, we convert affines +for DICOM images to output RAS+ coordinates instead of LPS+ coordinates. We +chose this convention because it is the most popular in neuroimaging; for +example, it is the standard used by NIfTI and MINC formats.

+

Nibabel does not enforce a particular RAS+ space. For example, NIfTI images +contain codes that specify whether the affine maps to scanner or MNI or +Talairach RAS+ space. For the moment, you have to consult the specifics of +each format to find which RAS+ space the affine maps to.

+

See also Radiological vs neurological conventions

+
+
+ + +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/dev-requirements.txt b/dev-requirements.txt deleted file mode 100644 index 69302061bc..0000000000 --- a/dev-requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -# Requirements for running tests --r requirements.txt -pytest diff --git a/devel/add_image_format.html b/devel/add_image_format.html new file mode 100644 index 0000000000..5b21c99c22 --- /dev/null +++ b/devel/add_image_format.html @@ -0,0 +1,262 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
+
+ +
+
+

NiBabel

+

Access a cacophony of neuro-imaging file formats

+
+
+ + + + +
+
+
+
+ +
+

How to add a new image format to nibabel

+

These are some work-in-progress notes in the hope that they will help adding a +new image format to NiBabel.

+
+

Philosophy

+

As usual, the general idea is to make your image as explicit and transparent +as possible.

+

From the Zen of Python (import this), these guys spring to mind:

+
    +
  • Explicit is better than implicit.

  • +
  • Errors should never pass silently.

  • +
  • In the face of ambiguity, refuse the temptation to guess.

  • +
  • Now is better than never.

  • +
  • If the implementation is hard to explain, it’s a bad idea.

  • +
+

So far we have tried to make the nibabel version of the image as close as +possible to the way the user of the particular format is expecting to see it.

+

For example, the NIfTI format documents describe the image with the first +dimension of the image data array being the fastest varying in memory (and on +disk). Numpy defaults to having the last dimension of the array being the +fastest varying in memory. We chose to have the first dimension vary fastest +in memory to match the conventions in the NIfTI specification.

+
+
+

Helping us to review your code

+

You are likely to know the image format much much better than the rest of us +do, but to help you with the code, we will need to learn. The following will +really help us get up to speed:

+
    +
  1. Links in the code or in the docs to the information on the file format. +For example, you’ll see the canonical links for the NIfTI 2 format at the +top of the nifti2 file, in the module docstring;

  2. +
  3. Example files in the format; see Adding test data;

  4. +
  5. Good test coverage. The tests help us see how you are expecting the code +and the format to be used. We recommend writing the tests first; the tests +do an excellent job in helping us and you see how the API is going to work.

  6. +
+
+
+

The format can be read-only

+

Read-only access to a format is better than no access to a format, and often +much better. For example, we can read but not write PAR / REC and MINC files. +Having the code to read the files makes it easier to work with these files in +Python, and easier for someone else to add the ability to write the format +later.

+
+
+

The image API

+

An image should conform to the image API. See the module docstring for +spatialimages for a description of the API.

+

You should test whether your image does conform to the API by adding a test +class for your image in nibabel.tests.test_image_api. For example, the +API test for the PAR / REC image format looks like:

+
class TestPARRECAPI(LoadImageAPI):
+    def loader(self, fname):
+        return parrec.load(fname)
+
+    example_images = PARREC_EXAMPLE_IMAGES
+
+
+

where your work is to define the EXAMPLE_IMAGES list — see the +nibabel.tests.test_parrec file for the PAR / REC example images +definition.

+
+
+

Where to start with the code

+

There is no API requirement that a new image format inherit from the general +SpatialImage class, but in fact all our image +formats do inherit from this class. We strongly suggest you do the same, to +get many simple methods implemented for free. You can always override the +ones you don’t want.

+

There is also a generic header class you might consider building on to contain +your image metadata — Header. See that +class for the header API.

+

The API does not require it, but if it is possible, it may be good to +implement the image data as loaded from disk as an array proxy. See the +docstring of arrayproxy for a description of the API, and see the +module code for an implementation of the API. You may be able to use the +unmodified ArrayProxy class for your image type.

+

If you write a new array proxy class, add tests for the API of the class in +nibabel.tests.test_proxy_api. See +TestPARRECAPI for an example.

+

A nibabel image is the association of:

+
    +
  1. The image array data (as implemented by an array proxy or a numpy array);

  2. +
  3. An affine relating the image array coordinates to an RAS+ world (see +Coordinate systems and affines);

  4. +
  5. Image metadata in the form of a header.

  6. +
+

Your new image constructor may well be the default from +SpatialImage, which looks like this:

+
def __init__(self, dataobj, affine, header=None,
+             extra=None, file_map=None):
+
+
+

Your job when loading a file is to create:

+
    +
  1. dataobj - an array or array proxy;

  2. +
  3. affine - 4 by 4 array relating array coordinates to world coordinates;

  4. +
  5. header - a metadata container implementing at least get_data_dtype, +get_data_shape.

  6. +
+

You will likely implement this logic in the from_file_map method of the +image class. See PARRECImage for an example.

+
+
+

A recipe for writing a new image format

+
    +
  1. Find one or more examples images;

  2. +
  3. Put them in nibabel/tests/data or a data submodule (see +Adding test data);

  4. +
  5. Create a file nibabel/tests/test_my_format_name_here.py;

  6. +
  7. Use some program that can read the format correctly to fill out the needed +fields for an EXAMPLE_IMAGES list (see +nibabel.tests.test_parrec.py for example);

  8. +
  9. Add a test class using your EXAMPLE_IMAGES to +nibabel.tests.test_image_api, using the PARREC image test class as +an example. Now you have some failing tests — good job!;

  10. +
  11. If you can, extract the metadata information from the test file, so it is +small enough to fit as a small test file into nibabel/tests/data (don’t +forget the license);

  12. +
  13. Write small maybe private functions to extract the header metadata from +your new test file, testing these functions in +test_my_format_name_here.py. See parrec for examples;

  14. +
  15. When that is working, try sub-classing Header, and working out how +to make the __init__ and from_fileboj methods for that class. Test +in test_my_format_name_here.py;

  16. +
  17. When that is working, try sub-classing SpatialImage and working +out how to load the file with the from_file_map class;

  18. +
  19. Now try seeing if you can get your test_image_api.py tests to pass;

  20. +
  21. Consider adding more test data files, maybe to a test data repository +submodule (Adding test data). Check you can read these files correctly +(see nibabel.tests.test_parrec_data for an example).

  22. +
  23. Ask for advice as early and as often as you can, either with a +work-in-progress pull request (the easiest way for us to review) or on +the mailing list or via github issues.

  24. +
+
+
+ + +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/devel/add_test_data.html b/devel/add_test_data.html new file mode 100644 index 0000000000..b514a77238 --- /dev/null +++ b/devel/add_test_data.html @@ -0,0 +1,243 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
+
+ +
+
+

NiBabel

+

Access a cacophony of neuro-imaging file formats

+
+
+ + + + +
+
+
+
+ +
+

Adding test data

+
    +
  1. We really, really like test images, but

  2. +
  3. We are rather conservative about the size of our code repository.

  4. +
+

So, we have two different ways of adding test data.

+
    +
  1. Small, open licensed files can go in the nibabel/tests/data directory +(see below);

  2. +
  3. Larger files or files with extra licensing terms can go in their own git +repositories and be added as submodules to the nibabel-data directory.

  4. +
+
+

Small files

+

Small files are around 50K or less when compressed. By “compressed”, we mean, +compressed with zlib, which is what git uses when storing the file in the +repository. You can check the exact length directly with Python and a script +like:

+
import sys
+import zlib
+
+for fname in sys.argv[1:]:
+    with open(fname, 'rb') as fobj:
+        contents = fobj.read()
+    compressed = zlib.compress(contents)
+    print(fname, len(compressed) / 1024.)
+
+
+

One way of making files smaller when compressed is to set uninteresting values +to zero or some other number so that the compression algorithm can be more +effective.

+

Please don’t compress the file yourself before committing to a git repo unless +there’s a really good reason; git will do this for you when adding to the +repository, and it’s a shame to make git compress a compressed file.

+
+
+

Files with open licenses

+

We very much prefer files with completely open licenses such as the PDDL +1.0 or the CC0 license.

+

The files in the nibabel/tests/data will get distributed with the nibabel +source code, and this can easily get installed without the user having an +opportunity to review the full license. We don’t think this is compatible +with extra license terms like agreeing to cite the people who provided the +data or agreeing not to try and work out the identity of the person who has +been scanned, because it would be too easy to miss these requirements when +using nibabel. It is fine to use files with these kind of licenses, but they +should go in their own repository to be used as a submodule, so they do not +need to be distributed with nibabel.

+
+
+

Adding the file to nibabel/tests/data

+

If the file is less then about 50K compressed, and the license is open, then +you might want to commit the file under nibabel/tests/data.

+

Put the license for any new files in the COPYING file at the top level of the +nibabel repo. You’ll see some examples in that file already.

+
+
+

Adding as a submodule to nibabel-data

+

Make a new git repository with the data.

+

There are example repos at

+ +

Despite the fact that both the examples are on github, Bitbucket is good for +repos like this because they don’t enforce repository size limits.

+

Don’t forget to include a LICENSE and README file in the repo.

+

When all is done, and the repository is safely on the internet and accessible, +add the repo as a submodule to the nitests-data directory, with something +like this:

+
git submodule add https://bitbucket.org/nipy/rosetta-samples.git nitests-data/rosetta-samples
+
+
+

You should now have a checked out copy of the rosetta-samples repository +in the nibabel-data/rosetta-samples directory. Commit the submodule that +is now in your git staging area.

+

If you are writing tests using files from this repository, you should use the +needs_nibabel_data decorator to skip the tests if the data has not been +checked out into the submodules. See nibabel/tests/test_parrec_data.py +for an example. For our example repository above it might look something +like:

+
from .nibabel_data import get_nibabel_data, needs_nibabel_data
+
+ROSETTA_DATA = pjoin(get_nibabel_data(), 'rosetta-samples')
+
+@needs_nibabel_data('rosetta-samples')
+def test_something():
+    # Some test using the data
+
+
+
+

Using submodules for tests

+

Tests run via nibabel on travis start with an automatic checkout of all +submodules in the project, so all test data submodules get checked out by +default.

+

If you are running the tests locally, you may well want to do:

+
git submodule update --init
+
+
+

from the root nibabel directory. This will checkout all the test data +repositories.

+
+
+

How much data should go in a single submodule?

+

The limiting factor is how long it takes travis-ci to checkout the data for +the tests. Up to a hundred megabytes in one repository should be OK. The joy +of submodules is we can always drop a submodule, split the repository into two +and add only one back, so you aren’t committing us to anything awful if you +accidentally put some very large files into your own data repository.

+
+
+

If in doubt

+

If you are not sure, try us with a pull request to nibabel github, or on the +nipy mailing list, we will try to help.

+
+
+
+ + +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/devel/advanced_testing.html b/devel/advanced_testing.html new file mode 100644 index 0000000000..7f801cc601 --- /dev/null +++ b/devel/advanced_testing.html @@ -0,0 +1,135 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
+
+ +
+
+

NiBabel

+

Access a cacophony of neuro-imaging file formats

+
+
+ + + + +
+
+
+
+ +
+

Advanced Testing

+
+

Setup

+

Before running advanced tests, please update all submodules of nibabel, by +running git submodule update --init

+
+
+

Long-running tests

+

Long-running tests are not enabled by default, and can be resource-intensive. To run these tests:

+
    +
  • Set environment variable NIPY_EXTRA_TESTS=slow;

  • +
  • Run pytest nibabel.

  • +
+

Note that some tests may require a machine with >4GB of RAM.

+
+
+ + +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/devel/biaps/biap_0000.html b/devel/biaps/biap_0000.html new file mode 100644 index 0000000000..a649c135d1 --- /dev/null +++ b/devel/biaps/biap_0000.html @@ -0,0 +1,368 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
+
+ +
+
+

NiBabel

+

Access a cacophony of neuro-imaging file formats

+
+
+ + + + +
+
+
+
+ +
+

BIAP 0 - Purpose and process

+
+
Author:
+

Jarrod Millman <millman@berkeley.edu>

+
+
Status:
+

Draft

+
+
Type:
+

Process

+
+
Created:
+

2020-06-25

+
+
+
+

What is a BIAP?

+

BIAP stands for Nibabel Enhancement Proposal. BIAPs are the primary +mechanisms for proposing major new features, for collecting community input on +an issue, and for documenting the design decisions that have gone into +Nibabel. A BIAP should provide a concise technical specification of the +feature and a rationale for the feature. The BIAP author is responsible for +building consensus within the community and documenting dissenting opinions.

+

Because the BIAPs are maintained as text files in a versioned +repository, their revision history is the historical record of the +feature proposal [1].

+
+

Types

+

There are three kinds of BIAPs:

+
    +
  1. A Standards Track BIAP describes a new feature or implementation +for Nibabel.

  2. +
  3. An Informational BIAP describes a Nibabel design issue, or provides +general guidelines or information to the Python community, but does not +propose a new feature. Informational BIAPs do not necessarily represent a +Nibabel community consensus or recommendation, so users and implementers are +free to ignore Informational BIAPs or follow their advice.

  4. +
  5. A Process BIAP describes a process surrounding Nibabel, or +proposes a change to (or an event in) a process. Process BIAPs are +like Standards Track BIAPs but apply to areas other than the Nibabel +language itself. They may propose an implementation, but not to +Nibabel’s codebase; they require community consensus. Examples include +procedures, guidelines, changes to the decision-making process, and +changes to the tools or environment used in Nibabel development. +Any meta-BIAP is also considered a Process BIAP.

  6. +
+
+
+
+

BIAP Workflow

+

The BIAP process begins with a new idea for Nibabel. It is highly +recommended that a single BIAP contain a single key proposal or new +idea. Small enhancements or patches often don’t need +a BIAP and can be injected into the Nibabel development workflow with a +pull request to the Nibabel repo. The more focused the +BIAP, the more successful it tends to be. +If in doubt, split your BIAP into several well-focused ones.

+

Each BIAP must have a champion—someone who writes the BIAP using the style +and format described below, shepherds the discussions in the appropriate +forums, and attempts to build community consensus around the idea. The BIAP +champion (a.k.a. Author) should first attempt to ascertain whether the idea is +suitable for a BIAP. Posting to the Nibabel discussion mailing list is the +best way to go about doing this.

+

The proposal should be submitted as a draft BIAP via a GitHub pull request +to the doc/source/devel/biaps directory with the name biap_<n>.rst +where <n> is an appropriately assigned four-digit number (e.g., +biap_0000.rst). The draft must use the BIAP X — Template and Instructions file.

+

Once the PR for the BIAP is in place, a post should be made to the +mailing list containing the sections up to “Backward compatibility”, +with the purpose of limiting discussion there to usage and impact. +Discussion on the pull request will have a broader scope, also including +details of implementation.

+

At the earliest convenience, the PR should be merged (regardless of +whether it is accepted during discussion). Additional PRs may be made +by the Author to update or expand the BIAP, or by maintainers to set +its status, discussion URL, etc.

+

Standards Track BIAPs consist of two parts, a design document and a +reference implementation. It is generally recommended that at least a +prototype implementation be co-developed with the BIAP, as ideas that sound +good in principle sometimes turn out to be impractical when subjected to the +test of implementation. Often it makes sense for the prototype implementation +to be made available as PR to the Nibabel repo (making sure to appropriately +mark the PR as a WIP).

+
+

Review and Resolution

+

BIAPs are discussed on the mailing list. The possible paths of the +status of BIAPs are as follows:

+../../_images/biap_flowchart.png +

All BIAPs should be created with the Draft status.

+

Eventually, after discussion, there may be a consensus that the BIAP +should be accepted – see the next section for details. At this point +the status becomes Accepted.

+

Once a BIAP has been Accepted, the reference implementation must be +completed. When the reference implementation is complete and incorporated +into the main source code repository, the status will be changed to Final.

+

To allow gathering of additional design and interface feedback before +committing to long term stability for a language feature or standard library +API, a BIAP may also be marked as “Provisional”. This is short for +“Provisionally Accepted”, and indicates that the proposal has been accepted for +inclusion in the reference implementation, but additional user feedback is +needed before the full design can be considered “Final”. Unlike regular +accepted BIAPs, provisionally accepted BIAPs may still be Rejected or Withdrawn +even after the related changes have been included in a Python release.

+

Wherever possible, it is considered preferable to reduce the scope of a +proposal to avoid the need to rely on the “Provisional” status (e.g. by +deferring some features to later BIAPs), as this status can lead to version +compatibility challenges in the wider Nibabel ecosystem.

+

A BIAP can also be assigned status Deferred. The BIAP author or a +core developer can assign the BIAP this status when no progress is being made +on the BIAP.

+

A BIAP can also be Rejected. Perhaps after all is said and done it +was not a good idea. It is still important to have a record of this +fact. The Withdrawn status is similar—it means that the BIAP author +themselves has decided that the BIAP is actually a bad idea, or has +accepted that a competing proposal is a better alternative.

+

When a BIAP is Accepted, Rejected, or Withdrawn, the BIAP should be +updated accordingly. In addition to updating the status field, at the very +least the Resolution header should be added with a link to the relevant +thread in the mailing list archives.

+

BIAPs can also be Superseded by a different BIAP, rendering the +original obsolete. The Replaced-By and Replaces headers +should be added to the original and new BIAPs respectively.

+

Process BIAPs may also have a status of Active if they are never +meant to be completed, e.g. BIAP 0 (this BIAP).

+
+
+

How a BIAP becomes Accepted

+

A BIAP is Accepted by consensus of all interested contributors. We +need a concrete way to tell whether consensus has been reached. When +you think a BIAP is ready to accept, send an email to the +Nibabel discussion mailing list with a subject like:

+
+

Proposal to accept BIAP #<number>: <title>

+
+

In the body of your email, you should:

+
    +
  • link to the latest version of the BIAP,

  • +
  • briefly describe any major points of contention and how they were +resolved,

  • +
  • include a sentence like: “If there are no substantive objections +within 7 days from this email, then the BIAP will be accepted; see +BIAP 0 for more details.”

  • +
+

After you send the email, you should make sure to link to the email +thread from the Discussion section of the BIAP, so that people can +find it later.

+

Generally the BIAP author will be the one to send this email, but +anyone can do it – the important thing is to make sure that everyone +knows when a BIAP is on the verge of acceptance, and give them a final +chance to respond. If there’s some special reason to extend this final +comment period beyond 7 days, then that’s fine, just say so in the +email. You shouldn’t do less than 7 days, because sometimes people are +travelling or similar and need some time to respond.

+

In general, the goal is to make sure that the community has consensus, +not provide a rigid policy for people to try to game. When in doubt, +err on the side of asking for more feedback and looking for +opportunities to compromise.

+

If the final comment period passes without any substantive objections, +then the BIAP can officially be marked Accepted. You should send a +followup email notifying the list (celebratory emoji optional but +encouraged), and then update the BIAP by setting its :Status: to +Accepted, and its :Resolution: header to a link to your followup +email.

+

If there are substantive objections, then the BIAP remains in +Draft state, discussion continues as normal, and it can be +proposed for acceptance again later once the objections are resolved.

+

In unusual cases, disagreements about the direction or approach may +require escalation to the Nibabel Steering Council who +then decide whether a controversial BIAP is Accepted.

+
+
+

Maintenance

+

In general, Standards track BIAPs are no longer modified after they have +reached the Final state as the code and project documentation are considered +the ultimate reference for the implemented feature. +However, finalized Standards track BIAPs may be updated as needed.

+

Process BIAPs may be updated over time to reflect changes +to development practices and other details. The precise process followed in +these cases will depend on the nature and purpose of the BIAP being updated.

+
+
+
+

Format and Template

+

BIAPs are UTF-8 encoded text files using the reStructuredText format. Please +see the BIAP X — Template and Instructions file and the reStructuredTextPrimer for more +information. We use Sphinx to convert BIAPs to HTML for viewing on the web +[2].

+
+

Header Preamble

+

Each BIAP must begin with a header preamble. The headers +must appear in the following order. Headers marked with * are +optional. All other headers are required.

+
  :Author: <list of authors' real names and optionally, email addresses>
+  :Status: <Draft | Active | Accepted | Deferred | Rejected |
+           Withdrawn | Final | Superseded>
+  :Type: <Standards Track | Process>
+  :Created: <date created on, in dd-mmm-yyyy format>
+* :Requires: <BIAP numbers>
+* :Nibabel-Version: <version number>
+* :Replaces: <BIAP number>
+* :Replaced-By: <BIAP number>
+* :Resolution: <url>
+
+
+

The Author header lists the names, and optionally the email addresses +of all the authors of the BIAP. The format of the Author header +value must be

+
+

Random J. User <address@dom.ain>

+
+

if the email address is included, and just

+
+

Random J. User

+
+

if the address is not given. If there are multiple authors, each should be on +a separate line.

+
+
+
+

References and Footnotes

+ +
+
+ + +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/devel/biaps/biap_0001.html b/devel/biaps/biap_0001.html new file mode 100644 index 0000000000..02e3674f96 --- /dev/null +++ b/devel/biaps/biap_0001.html @@ -0,0 +1,412 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
+
+ +
+
+

NiBabel

+

Access a cacophony of neuro-imaging file formats

+
+
+ + + + +
+
+
+
+ +
+

BIAP1 - Towards immutable images

+
+
Author:
+

Matthew Brett

+
+
Status:
+

Rejected

+
+
Type:
+

Standards

+
+
Created:
+

2011-03-23

+
+
+
+

Resolution

+

Retired as of nibabel 2.0 in favor of exposed dataobj property. See:

+ +

See image in_memory attribute and uncache method.

+

We haven’t implemented an is_as_loaded attribute yet.

+
+
+

Background

+

Nibabel implicitly has two types of images

+
    +
  • array images

  • +
  • proxy images

  • +
+
+

Array images

+

Array images are the images you get from a typical constructor call:

+
import numpy as np
+import nibabel as nib
+arr = np.arange(24).reshape((2,3,4))
+img = nib.Nifti1Image(arr, np.eye(4))
+
+
+

img here is an array image, that is to say that, internally, the private +img._data attribute is reference to arr above. img.get_data() just +returns img._data. If you modify arr, you will modify the result of +img.get_data().

+
+
+

Proxy images

+

Proxy images are what you get from a call to load:

+
px_img = nib.load('test.nii')
+
+
+

It’s a proxy image in the sense that, internally, px_arr._data is a proxy +object that does not yet contain an array, but can get an array by the +application of:

+
actual_arr = np.asarray(px_img._data)
+
+
+

This is in fact what px_img.get_data() does. Actually, +px_img.get_data() also stores the read array in px_img._data, so that:

+
px_img = nib.load('test.nii')
+assert not isinstance(px_img._data, np.ndarray) # it's a proxy
+actual_arr = px_img.get_data()
+assert isinstance(px_img._data, np.ndarray) # it's an array now
+
+
+

So, at this point, if you change actual_arr you’ll also be changing +px_img._data and therefore the result of px_img.get_data().

+

In other words, actual_arr = px_img.get_data() turns the proxy image into an +array image.

+
+
+

Issues for design

+

The code at the moment is a little bit confusing because:

+
    +
  • there isn’t an explicit API to check if you have an array image or a proxy +image and

  • +
  • there isn’t anywhere in the docs that you can go and see this distinction.

  • +
+
+
+

Use cases

+
+

Loading images, minimizing memory

+

I want to load lots of images, or several large images. I’m going to do +something with the image data. I want to minimize memory use. This tempts me +to do something like this:

+
large_img1 = nib.load('large1.nii')
+large_img2 = nib.load('large2.nii')
+li1_mean = large_img1.get_data().mean()
+li2_mean = large_img2.get_data().mean()
+
+
+

The problem with the current design is that, after the li1_mean = line, +large_img1 got unproxied, and there’s a huge array inside it.

+
+
+

Loading images, maximizing speed

+

On the other hand, I might want to do the same thing, but each call to unproxy +the data (loading off disk, applying scalefactors) will be expensive. So, when +I do li1_mean = large_img1.get_data().mean() I want any subsequent call to +to large_img1.get_data() to be much faster. This is the case at the moment, +at the expense of the memory hit above.

+
+
+

Loading images, assert not modified

+

In pipelines in particular, we frequently want to load images, maybe have a +look at some parameters, and then pass that image filename to some other +program such as SPM or FSL. At the moment we’ve got a problem:

+
img = nib.load('test.nii')
+# do stuff
+run_spm_thing_on(img) # is 'img' the same as test.nii?
+
+
+

The problem is that when the routine run_spn_thing receives img, it +can know that img has a filename, test.nii, but it can’t currently +know if img is the same object that it was when it was loaded. That is, +it can’t know whether test.img still corresponds to img or not. In +practice that means that run_spm_thing will need to save every img to +another file before passing that filename to the SPM routine, just in case +img has been modified. So, we would like a dirty bit for the image, +something like:

+
# Not implemented yet
+if not img.is_as_loaded():
+    save(img, 'some_filename.nii')
+
+
+

The last line, like it or not, modifies img in-place.

+
+
+
+

Array images, proxy images, copy, view

+

With thanks to Roberto Viviani for some clarifying thoughts on the nipy +mailing list.

+

At the moment, img.get_data() always returns a reference to an array. +That is, whenever you call:

+
data = img.get_data()
+
+
+

Then, if you modify data you will modify the next result of +img.get_data().

+

In particular, the interface currently intends that there should be no +functional difference between proxied images and non-proxied images. The +proposal below exposes a functional difference between them.

+
+

When do you want a copy and when do you want a view?

+

This is a discussion of this proposal:

+
img.get_data(copy=True|False)
+
+
+

compared to:

+
img.get_data(unproxy=True|False)
+
+
+

Summary:

+
    +
  • array images - you nearly always want a view

  • +
  • proxy images - you may want a copy, but you want a copy only because you +want to leave the image as a proxy. You might want to leave the image as a +proxy because you want to be sure the image corresponds to the file, or save +memory.

  • +
+

For array images, it doesn’t make sense to return a copy from +img.get_data(), because it buys you nothing that you would not get from +data = img.get_data().copy(). This is because you can’t save memory (the +image already contains the whole array), and it won’t help you be sure that +the image has not been modified compared to the original array, because there +may be references to the array that existed before the image was made, that +can be used to modify the data. So, for array images, you always want a +reference, or you want to do a manual copy, as above.

+

For proxied images, it does make sense to get a copy, because a) you want to +preserve memory by not unproxying the image, and / or b) you want to be able +to be sure that the file associated with the image still corresponds to the +data.

+

For the img.get_data(copy=False) proposal, on a proxied image, the +copy=False call, in order to return a view, must implicitly unproxy the +image.

+

Similarly, img.get_data(unproxy=False) must implicitly copy the image.

+

It seems to me (MB) that an implicit copy is familiar to a numpy user, but the +implicit unproxying may be less obvious.

+

My (MBs) reasons then for preferring ‘unproxy’ to ‘copy=True’ or ‘copy=False’ +or get_data_copy() is that ‘unproxy’ is closer to how I think the user would +think about deciding what they wanted to do.

+

The unproxy=False case covers the situation where you want to preserve +memory. It doesn’t fully cover the cases where we want to keep track of when +the image data has been modified.

+

Here there are three cases:

+
    +
  • array image, instantiated with an array; the image data can be modified +using the array reference passed into the image - we can’t know whether the +data has been modified without doing hashing or similar.

  • +
  • proxy image; the array data is still in the file, so we know it corresponds +to the file.

  • +
  • proxy images that have been converted to array images, but have not passed +out a reference to the data. Let’s call these shy unproxied images. For +example, with an API like this:

    +
    img = load('test.nii')
    +data = img.get_data(copy=True)
    +
    +
    +

    the img is now an array image, but there’s no public reference to the +internal array object. Someone could get one by cheating with ref = +img._data, but, we don’t need to worry about that - following Python’s “mess +around if you like but take the consequences” philosophy.

    +
  • +
+
+
+
+

Proposal

+

An is_proxy property:

+
img.is_proxy
+
+
+

This is just for clarity.

+

Allow the user to specify what unproxying they want with a kwarg to +get_data():

+
arr = large_img1.get_data(unproxy=False)
+
+
+
    +
  • for proxied images, unproxy=False would leave the underlying array data +as a pointer to the file. The returned arr would be therefore a copy of +the data as loaded from file, and arr[0] = 99 would have no effect on +the data in the image. unproxy=True would convert the proxy image into +an array image (load the data into memory, return reference). Here arr[0] += 99 would affect the data in the image

  • +
  • for array images, unproxy would always be ignored.

  • +
+

Thus unproxy=True in fact means, +unproxy_if_this_is_a_proxy_do_nothing_otherwise.

+

The default would continue to be unproxy=True so that the proxied image +would continue, by default, to behave the same way as an unproxied image +(get_data returns a view).

+

If img.is_proxy is True, then we know that the array data has not changed. +We then need to be sure that the header and affine data haven’t +changed. We might be able to do this with default copy kwargs to the +get_header and get_affine methods:

+
hdr = img.get_header(copy=True) # will be default
+aff = img.get_affine(copy=True) # will be default
+
+
+

We could also do that by caching the original header and affine, but the +header in particular can be rather large.

+

For the next version of nibabel, for backwards compatibility, we’ll set +copy=False to be the default, but warn about the upcoming change. After +that we’ll set copy=True as the default.

+

Now we can know whether the image has been modified, because if get_header +and get_affine have only been called with copy=True and img.is_proxy +== True - then it must be the same as when loaded.

+

This leads to an is_as_loaded property:

+
if img.is_as_loaded:
+    fname = img.get_filename()
+else:
+    fname = 'tempname.nii'
+    save(img, 'tempname.nii')
+
+
+
+
+

Questions

+

Should there also be a set_header and set_affine method?

+

The header may conflict with the affine. So, would we need something like:

+
img.set_header(hdr, hdr_affine_from='affine')
+
+
+

or some other nasty syntax. Or can we avoid this and just do:

+
img2 = nib.Nifti1Image(img.get_data(), new_affine, new_header)
+
+
+

?

+

How about the names in the proposal? is_proxy; unproxy=True?

+
+
+
+ + +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/devel/biaps/biap_0002.html b/devel/biaps/biap_0002.html new file mode 100644 index 0000000000..dc0bf7c4ee --- /dev/null +++ b/devel/biaps/biap_0002.html @@ -0,0 +1,281 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
+
+ +
+
+

NiBabel

+

Access a cacophony of neuro-imaging file formats

+
+
+ + + + +
+
+
+
+ +
+

BIAP2 - Slicecopy

+
+
Author:
+

Matthew Brett

+
+
Status:
+

Rejected

+
+
Type:
+

Standards

+
+
Created:
+

2011-03-26

+
+
+
+

Status

+

Alternative implementation as of Nibabel 2.0 with image proxy slicing : see +http://nipy.org/nibabel/images_and_memory.html#saving-time-and-memory

+
+
+

Background

+

Please see https://github.com/nipy/nibabel/issues#issue/9 for motivation.

+

Sometimes we have a biiig images and we don’t want to load the whole array into +memory. In this case it is useful to be able to load as a proxy:

+
img = load('my_huge_image.nii')
+
+
+

and then take out individual slices, as in something very approximately like:

+
slice0 = img.get_slice(0)
+
+
+
+

Questions

+
+

Should slice0 be a copy or a view?

+

As from the previous discussion - BIAP1 - Towards immutable images - an image may be a proxy +or an array.

+

If the image is an array, the most natural thing to return is a view. That is, +modifying slice0 will modify the underlying array in img.

+

If the image is a proxy, it would be self-defeating to return a view, because +that would involve reading the whole image into memory, exactly what we are +trying to avoid. So, for a proxied image, we’d nearly always want to return a +copy.

+
+
+

What slices should the slicing allow?

+

The img.get_slice(0) syntax needs us to know what slice 0 is. In a nifti +image of 3 dimensions, the first is fastest changing on disk. To be useful +0 will probably refer to the slowest changing on disk. Otherwise we’ll +have to load nearly the whole image anyway. So, for a nifti, 0 should be the +first slice in the last dimension.

+

For Minc on the other hand, you can and I (MB) think always do get C ordered +arrays back, so that the slowest changing dimension in the image array is the +first. Actually, I don’t know how to read a Minc file slice by slice, but the +general point is that, to know which slice is worth reading, you need to know +the relationship of the image array dimensions to fastest / slowest on disk.

+

We could always solve this by assuming that we always want to do this for +Analyze / Nifti1 files (Fortran ordered). It’s a little ugly of course.

+

Note that taking the slowest changing slice in a nifti image would be the +equivalent of taking a slice from the last dimension:

+
arr = img.get_data()
+slice0 = arr[...,0]
+
+
+

In general, we can get contiguous data off disk for the same data as contiguous +data in memory (perhaps obviously). So, all of these are contiguous in the +Fortran ordering case:

+
arr[...,0:5]
+arr[:,:,0]
+arr[:,0:,0]
+arr[0:,:,0]
+arr[:,1,0]
+arr[1,1,1]
+
+
+

That is, in general, : up until the first specified dimension, then +contiguous slices, followed by integer slices. So, all of these can be read +directly off disk as slices. Obviously the rules are the reverse for c-ordered +arrays.

+
+
+
+

Option 1: fancy slice object

+

It’s option 1 because it’s the first one I thought of:

+
slice0 = img.slicecopy[...,0]
+
+
+

Here we solve the copy or view problem with ‘always copy’. We solve the ‘what +slicing to allow’ by letting the object decide how to do the slicing. We could +obviously just do the full load (deproxy the image) and return a copy of the +sliced array, as in:

+
class SomeImage:
+    class Slicer:
+        def __init__(self, parent):
+            self.parent = parent
+        def __getitem__(self, slicedef):
+            data = parent._data
+            if is_proxy(data) and iscontinguous(slicedef, order='F'):
+                return read_off_disk_somehow(slicedef, data)
+            data = parent.get_data(unproxy=True)
+            return data.__getitem__(slicedef)
+    def __init__(self, stuff):
+        self.slicecopy = Slicer(self)
+
+
+

The problem with this is that:

+
slice0 = img.slicecopy[...,1]
+
+
+

might unproxy the image. At the moment, it’s rather hidden whether the image +is proxied or not on the basis that it’s an optimization that should be +transparent.

+
+
+

Option 2: not-fancy method call

+
slice0 = img.get_slice(0, copy=True)
+
+
+

‘slice or view’ solved with explicit keyword. ‘which slice’ solved by assuming +you always mean one slice in the last dimension. Or we could also allow:

+
slices = img.get_slices(slice(0,3), copy=True)
+
+
+

This is ugly, but fairly clear. This simple ‘I mean the last dimension’ might +be annoying because it assumes the last dimension is the slowest changing, and +it does not get to optimize the more complex contiguous cases above. So we +could even allow full slicing with stuff like:

+
slice = img.get_slices((slice(None), slice(None), slice(3)), copy=True)
+
+
+

Again - this looks a lot more ugly than the slicecopy syntax above.

+

Now, when would you choose copy=True? I think, when the image is a proxy. +Otherwise you’d want a view. Probably. So what you mean, probably, is +something like this:

+
slices = img.get_slices(slicedef, copy_if='is_proxy')
+
+
+

But, we’ve established that for some slices, you’re going to have to load the +whole image anyway. So in fact probably what you want is to:

+
    +
  1. Take a view if this image is not a proxy

  2. +
  3. Take a copy if we can read this directly off disk

  4. +
  5. Unproxy the image if we have to read the whole thing off disk anyway to get +the slices we want, on the basis that we have to read the whole thing into +memory anyway, we might as well do that and save ourselves lots of disk +thrashing getting the individual slices.

  6. +
+

Of course that’s what option 1 boils down to. So I think I prefer version 1.

+
+
+
+ + +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/devel/biaps/biap_0003.html b/devel/biaps/biap_0003.html new file mode 100644 index 0000000000..cb940a26b0 --- /dev/null +++ b/devel/biaps/biap_0003.html @@ -0,0 +1,840 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + + +
+
+ +
+
+

NiBabel

+

Access a cacophony of neuro-imaging file formats

+
+
+ + + + +
+
+
+
+ +
+

BIAP3 - A JSON nifti header extension

+
+
Author:
+

Matthew Brett, Bob Dougherty

+
+
Status:
+

Draft

+
+
Type:
+

Standards

+
+
Created:
+

2011-03-26

+
+
+

The following Wiki documents should be merged with this one:

+ +
+

Abstract

+

A draft specification of the JSON header for Nibabel.

+
+
+

Background

+

DICOM files in particular have a lot of information in them that we might want +to carry with the image. There are other image file types like Minc or Nrrd +that have information we’d like to support but can’t with standard nifti.

+

One obvious place to store this information is in a nifti header extension.

+
+

Nifti extension types

+

From adding nifti extensions:

+ +
+
+

Alternatives

+

Summary: we need probably need our own extension format

+

There is a DICOM type extension - code 2. This might be OK for DICOM but:

+
    +
  1. We probably don’t want to have to dump the entire DICOM header for every +DICOM image. If we don’t that means we have to edit the DICOM header, and

  2. +
  3. The DICOM format is awful to work with, so it is not a pleasant prospect +making a new DICOM header for images (like Minc) that aren’t DICOM to start +with.

  4. +
  5. I (MB) can’t find any evidence that it’s being used in the wild.

  6. +
  7. It’s not completely clear what format the data should be in. See this +nifti thread.

  8. +
+

The AFNI extension format looks as if it is specific to AFNI.

+

The XCEDE format looks rather heavy. I’m (MB) trying to work out where the +most current schema is. Candidates are bxh-xcede-tools and the xcede +website. We’d need to validate the XML with the schema. It appears the +python standard library doesn’t support that so we’d need extra XML tools as a +dependency.

+

JIM is closed source.

+

fiswidgets seems to have been quiet recently. The link for code 12 is dead, I +had to go back to the http://www.archive.org to get an old copy +and that didn’t have the DTD or example links that we need to understand the +format.

+
+
+

Learning from NRRDs

+

Gordon Kindlmann’s NRRD format has gone through a few versions and has +considerable use particularly by the 3D slicer team. I’ve tried to +summarize the NRRD innovations not properly covered by nifti in +[[nifti-nrrd]].

+
+
+
+

Proposal

+

JSON, as y’all know, encodes strings, numbers, objects +and arrays, An object is like a Python dict, with strings as keys, and an +array is like a Python list.

+

In what follows, I will build dicts and lists corresponding to the objects and +arrays of the JSON header. In each case, the json.dumps of the given Python +object gives the corresponding JSON string.

+

I’ll use the term field to refer to a (key, value) pair from a Python dict / +JSON object.

+
+

General principles

+

We specify image axes by name in the header, and give the correspondence of the +names to the image array axes by the order of the names. This is the +axis_names field at the top level of the header.

+

If the user transposes or otherwise reorders the axes of the data array, the +header should change only in the ordering of the axis names in +axis_names. Call this the “axis transpose” principle.

+

The JSON header should make sense as a key, value pair store for DICOM +fields using a standard way of selecting DICOM fields – the simple DICOM +principle.

+

The NIfTI image also contains the standard image metadata in the NIfTI header +C-struct (the standard NIfTI header). Nibabel and Nipy will write JSON +headers correctly, and so the information in the NIfTI C-struct should always +match the information in the JSON header. Other software may write the JSON +incorrectly, or copy the JSON header into another image to which it may not +apply, but other software should always set the C-struct correctly. For that +reason the C-struct always overrides the JSON header, unless the C-struct has +values implying “not-set” or “don’t know”. This is the C-struct primacy +principle.

+
+
+

See also

+
    +
  • JSON-LD - provides a way of using json that can be +mapped into the Resource Description Framework (RDF). It is highly +recommended to take a look at the RDF Primer to get a sense of why we might want +to use JSON-LD/RDF, but essentially it boils down to a couple points:

    +
      +
    • JSON keys are turned into URIs

    • +
    • URIs can dereference to a Web URL with additional documentation, such as a +definition, a pretty label (e.g., nipy_header_version has_label +"NIPY Header Version"), etc.

    • +
    • The URI link to documentation makes the meaning of your JSON keys +explicit, in a machine readable way (i.e., the json key becomes a +“resource” on the Web that avoids name clashes)

    • +
    • JSON-LD/RDF has a full query language called SPARQL and a python library called +RDFLib that acts as a +parser, serializer, database, and query engine.

    • +
    • In the example below, the @context section provides the namespace +prefix dcm as a placeholder for the URL +http://neurolex.org/wiki/Category:, thus dcm:Echo_Time +dereferences to http://neurolex.org/wiki/Category:Echo_Time where +additional documentation is provided:

      +
      {
      +  "@context": {
      +    "dcm": "http://neurolex.org/wiki/Category:#"
      +  },
      +  "dcm:Echo_Time": 45,
      +  "dcm:Repetition_Time": 2,
      +}
      +
      +
      +
    • +
    +
  • +
+
+
+

The header must contain the header version

+
>>> hdr = dict(nipy_header_version='1.0')
+
+
+

We chose the name “nipy_header_version” in the hope that this would not often +occur in an unrelated JSON file.

+
    +
  • First version will be “1.0”.

  • +
  • Versioning will use Semantic Versioning of form +major.minor[.patch[-extra]] where major, minor, patch are +all integers, extra may be a string, and both patch and extra +are optional. Header versions with the same major value are forwards +compatible – that +is, a reader that can read a header with a particular major version should +be able to read any header with that major version. Specifically, any +changes to the header format within major version number should allow older +readers of that major version to read the header correctly, but can expand +on the information in the header, so that older readers can safely ignore +new information in the header.

  • +
  • All fields other than nipy_header_version are optional. The dict in +hdr above is therefore the minimal valid header.

  • +
+
+
+

The header will usually contain image metadata fields

+

The base level header will usually also have image metadata fields giving +information about the whole image. A field is an “image metadata field” if it +is defined at the top level of the header. For example:

+
>>> hdr = dict(nipy_header_version='1.0',
+...            Manufacturer="SIEMENS")
+
+
+

All image metadata fields are optional.

+

As for all keys in this standard, IM (Image Metadata) keys are case sensitive. +IM keys that begin with a capital letter must be from the DICOM data +dictionary standard short names (DICOM keyword). Call these “DICOM IM keys”. +This is to conform to the simple DICOM principle.

+

Keys beginning with “extended” will be read and written, but not further +processed by a header reader / writer. If you want to put extra fields into +the header that are outside this standard you could use a dict / object of +form:

+
>>> hdr = dict(nipy_header_version='1.0',
+...            extended=dict(my_field1=0.1, my_field2='a string'))
+
+
+

or:

+
>>> hdr = dict(nipy_header_version='1.0',
+...            extended_mysoft=dict(mysoft_one='expensive', mysoft_two=1000))
+
+
+

Values for DICOM IM keys are constrained by the DICOM standard. This standard +constrains values for (“nipy_header_version”, “axis_names”, “axis_metadata”). +Other values have no constraint.

+
+
+

Questions

+
    +
  • Should all DICOM values be allowed?

  • +
  • Should DICOM values be allowed at this level that in fact refer to a +particular axis, and therefore might go in the axis_metadata elements?

  • +
  • How should we relate the DICOM standard values to JSON? For example, how +should we store dates and times? One option would be to use the new DICOM +JSON encoding for DICOM values, but omitting the tag and value +representation (VR). For example, the DICOM JSON spec has:

    +
    "00080070": {
    +    "vr": "LO",
    +    "Value": [ "SIEMENS" ]
    +},
    +
    +
    +

    but we might prefer:

    +
    "Manufacturer": "SIEMENS"
    +
    +
    +

    Using the DICOM data dictionary we can reconstruct the necessary tag and VR, +so our version is lossless if the DICOM keyword exists in the DICOM data +dictionary. Of course this may well not be true for private tags, or if the +keyword comes from a DICOM dictionary that is later than the one we are +using to look up the keyword. For the latter, we could make sure we’re +always using the latest dictionary. For the private tags, we might want to +recode these in any case, maybe using our own dictionary. Maybe it is +unlikely we will want to reconstruct the private tags of a DICOM file from +the JSON. Comments welcome.

    +
  • +
+
+
+

The header will usually contain axis names

+

axis_names is a list of strings corresponding to the axes of the image data +to which the header refers.

+
>>> hdr = dict(nipy_header_version='1.0',
+...            axis_names=["frequency", "phase", "slice", "time"])
+
+
+

The names must be valid Python identifiers (should not begin with a digit, nor +contain spaces etc).

+

There must be the same number of names as axes in the image to which the header +refers. For example, the header above is valid for a 4D image but invalid for a +3D or 5D image.

+

The names appear in fastest-slowest order in which the image data is stored on +disk. The first name in axis_names corresponds to the axis over which +the data on disk varies fastest, and the last corresponds to the axis over which +the data varies slowest.

+

For a NIfTI image, nibabel (and nipy) will create an image where the axes have +this same fastest to slowest ordering in memory. For example, let’s say the +read image is called img. img has shape (4, 5, 6, 10), and a 2-byte +datatype such as int16. In the case of the NIfTI default fastest-slowest ordered +array, the distance in memory between img[0, 0, 0, 0] and img[1, 0, 0, +0] is 2 bytes, and the distance between img[0, 0, 0, 0] and img[0, 0, 0, +1] is 4 * 5 * 6 * 2 = 240 bytes. The names in axis_names will then refer +to the first, second, third and fourth axes respectively. In the example above, +“frequency” is the first axis and “time” is the last.

+

axis_names is optional only if axis_metadata is empty or absent. +Otherwise, the set() of axis_names must be a superset of the union of +all axis names specified in the applies_to fields of axis_metadata +elements.

+
+
+

The header will often contain axis metadata

+

axis_metadata is a list of axis metadata elements.

+

Each axis metadata element in the axis_metadata list gives data that +applies to a particular axis, or combination of axes. axis_metadata can +be empty:

+
>>> hdr['axis_metadata'] = []
+
+
+

We prefer you delete this section if it is empty, to avoid clutter, but hey, +mi casa, su casa.

+
+

The axis metadata element

+

An axis metadata element must contain a field applies_to, with a value that +is a list that contains one or more values from axis_names. From the above +example, the following would be valid axis metadata elements:

+
>>> hdr = dict(nipy_header_version='1.0',
+...            axis_names = ["frequency", "phase", "slice", "time"],
+...            axis_metadata = [
+...                dict(applies_to = ['time']),
+...                dict(applies_to = ['slice']),
+...                dict(applies_to = ['slice', 'time']),
+...            ])
+
+
+
+

Note

+

The applies_to field plays the role of a dictionary key for each axis +metadata element, where the rest of the fields in the element are a dict +giving the value. For example, in Python (but not in JSON, we could +represent the above as:

+
>>> hdr = dict(nipy_header_version='1.0',
+...            axis_names = ["frequency", "phase", "slice", "time"],
+...            axis_metadata = {
+...                'time': {},
+...                'slice': {},
+...                ('slice', 'time'): {},
+...            ])
+
+
+

We can’t do this in JSON because all object fields must be strings, so we +cannot represent the key ('slice', 'time') directly. The +applies_to field allows us to do that in JSON. See below for why we +might want to specify more than one axis.

+
+

As for image metadata keys, keys that begin with a capital letter are DICOM +standard keywords.

+

A single axis name for applies_to specifies that any axis metadata values in +the element apply to the named axis.

+

In this case, axis metadata values may be:

+
    +
  • a scalar. The value applies to every point along the corresponding image +axis OR

  • +
  • a vector of length N (where N is the length of the corresponding image +axis). Value \(v_i\) in the vector \(v\) corresponds to the image slice at +point \(i\) on the corresponding axis OR

  • +
  • an array of shape (1, …) where “…” can be any further shape, expressing +a vector or array that applies to all points on the given axis, OR

  • +
  • an array of shape (N, …) where “…” can be any further shape. The (N, +…) array N vectors or arrays with one (vector or array) corresponding to +each point in the image axis.

  • +
+

More than one axis name for applies_to specifies that any values in the +element apply to the combination of the given axes.

+

In the case of more than one axis for applies_to, the axis metadata values +apply to the Cartesian product of the image axis values. For example, if the +values of applies_to == ['slice', 'time'], and the slice and time axes +in the array are lengths (6, 10) respectively, then the values apply to all +combinations of the 6 possible values for slice indices and the 10 possible +values for the time indices (ie apply to all 6x10=60 values). The axis metadata +values in this case can be:

+
    +
  • a scalar. The value applies to every combination of (slice, time)

  • +
  • an array of shape (S, T) (where S is the length of the slice axis and T is +the length of the time axis). Value \(a_{i,j}\) in the array \(a\) corresponds +to the image slice at point \(i\) on the slice axis and \(j\) on the time axis.

  • +
  • an array of shape (S, T, …) where “…” can be any further shape. The (S, +T, …) case gives N vectors or arrays with one vector / array corresponding +to each combination of slice, time points in the image,

  • +
+

In contrast to the single axis case, we do not allow length 1 axes, to +indicate a value constant across an axis. For example, we do not allow shape +(1, T) arrays to indicate a value constant across slice but varying across +time, as this should be specified with the single time axis metadata element.

+

In general, for a given value applies_to, we can take the corresponding +axis lengths:

+
>>> shape_of_image = [4, 5, 6, 10]
+>>> image_names = ['frequency', 'phase', 'slice', 'time']
+>>> applies_to = ['slice', 'time']
+>>> axis_indices = [image_names.index(name) for name in applies_to]
+>>> axis_lengths = [shape_of_image[i] for i in axis_indices]
+>>> axis_lengths
+[6, 10]
+
+
+

The axis metadata value can therefore be of shape:

+
    +
  • () (a scalar) (a scalar value for every combination of points);

  • +
  • axis_lengths (a scalar value for each combination of points);

  • +
  • [1] + any_other_list if len(axis_lengths) == 1;

  • +
  • axis_lengths + any_other_list (an array or vector corresponding to each +combination of points, where the shape of the array or vector is given by +any_other_list)

  • +
+

For any unique ordered combination of axis names, there can only be on axis +metadata element. For example, this is valid:

+
>>> # VALID
+>>> hdr = dict(nipy_header_version='1.0',
+...            axis_names = ["frequency", "phase", "slice", "time"],
+...            axis_metadata = [
+...                dict(applies_to = ['time']),
+...                dict(applies_to = ['slice', 'time']),
+...                dict(applies_to = ['slice']),
+...            ])
+
+
+

This is not, because of the repeated combination of axis names:

+
>>> # NOT VALID because of repeated axis combination
+>>> hdr = dict(nipy_header_version='1.0',
+...            axis_names = ["frequency", "phase", "slice", "time"],
+...            axis_metadata = [
+...                dict(applies_to = ['time']),
+...                dict(applies_to = ['slice', 'time']),
+...                dict(applies_to = ['slice']),
+...                dict(applies_to = ['slice', 'time']),
+...            ])
+
+
+
+
+

The q_vector axis metadata field

+

We define an axis metadata field q_vector which gives the q vector +corresponding to the diffusion gradients applied.

+

The q_vector should apply to (applies_to) one axis, where that axis is +the image volume axis. The q_vector is a dict / object with two fields, +spatial_axes and array.

+

If there are T volumes then the array will be of shape (T, 3). One row from +this array corresponds to the direction of the diffusion gradient with axes +oriented to the three spatial axes of the data. To preserve the axis +transpose principle, the spatial_axes field value is a list of the +spatial image axes to which the first, second and third column of the +array refer.

+

For example:

+
>>> import numpy as np
+>>> element = dict(applies_to=['time'],
+...                q_vector = dict(
+...                   spatial_axes = ['frequency', 'phase', 'slice'],
+...                   array = [[0, 0, 0],
+...                            [1000, 0, 0],
+...                            [0, 1000, 0],
+...                            [0, 0, 1000],
+...                            [0, 0, 0],
+...                            [1000, 0, 0],
+...                            [0, 1000, 0],
+...                            [0, 0, 1000],
+...                            [0, 0, 0],
+...                            [1000, 0, 0]
+...                           ]))
+>>> np.array(element['q_vector']['array']).shape
+(10, 3)
+
+
+

An individual (3,) vector is the unit vector expressing the direction of the +gradient, multiplied by the scalar b value of the gradient. In the example, +there are three b == 0 scans (corresponding to volumes 0, 4, 8), with the rest +having b value of 1000.

+

The first value corresponds to the direction along the first named image axis +(‘frequency’), the second value to direction along the second named axis +(‘phase’), and the third to direction along the ‘slice’ axis.

+

Note that the q_vector is always specified in the axes of the image. This is +the same convention as FSL uses for its bvals and bvecs files.

+
+
+

acquisition_times field

+

This gives a list of times of acquisition of each spatial unit of data.

+

acquisition_times can apply to (applies_to) slices or to volumes or to +both.

+

Units are milliseconds and can be expressed as integers or as floating point. +Milliseconds is a reasonable choice for units because a Python integer can +decode / encode any integer number in the JSON correctly, a signed 32-bit int +can encode to around 6000 hours, and a 32-bit float can encode to 23 hours +without loss of precision.

+
+
+
+

acquisition_times applying to slices

+

If acquisition_times applies to an image axis representing slices, then the +array should be of shape (S,) where S is the number of slices. Each value +\(a_i\) represents the time of acquisition of slice \(i\), relative to the start +of the volume, in milliseconds. For example, to specify an ascending +sequential slice acquisition scheme:

+
>>> element = dict(applies_to=['slice'],
+...                acquisition_times=[0, 20, 40, 60, 80, 100])
+
+
+

We use “slice” as the axis name here, but any name is valid.

+

NIfTI 1 and 2 can encode some slice acquisition times using a somewhat +complicated scheme, but they cannot - for example - encode multi-slice +acquisitions, and NIfTI slice time encoding is rarely set. According to the +C-struct primacy principle, if the slice timing is set, it overrides this +acquisition_times field. Slice timing is set in the C-struct if the +slice_code +in the C-struct is other than 0 (=unknown). The specific slice times from the +C-struct also depend on C-struct fields slice_start and slice_end.

+
+
+

acquisition_times applying to volumes

+

When acquisition_times` applies to a volume axis, it is a list of times of +acquisition of each volume in milliseconds relative to the beginning of the +acquisition of the run.

+

These values can be useful for recording runs with missing or otherwise +not-continuous time data.

+

We use “time” as the axis name, but any name is valid.

+
>>> element = dict(applies_to=['time'],
+...                acquisition_times=[0, 120, 240, 480, 600])
+
+
+

The NIfTI C-struct can encode a non-zero start point for volumes, using the +toffset +field. If this is not-zero, and not equal to the first value in +acquisition_times, JSON acquisition times applying to volumes are ignored. +The C-struct slice_code field (see above) is not relevant to volume times, +and can have any value.

+
+
+

acquisition_times applying to slices and volumes

+

When acquisition_times` applies to both a slice and a volume axis, it is a +list of times of acquisition of each slice in each volume in milliseconds +relative to the beginning of the acquisition of the run.

+
>>> element = dict(applies_to=['slice', 'time'],
+...                acquisition_times = [[0, 100, 200],
+...                                     [10, 110, 210],
+...                                     [20, 120, 220],
+...                                     [30, 130, 230],
+...                                     [40, 140, 240]]
+...           )
+
+
+

This meaning becomes invalid with non-zero and conflicting values for +slice_code or toffset in the C-struct. Conflicting values are values +different from those implied from a strict per-volume repetition of the +acquisition times from slice_code, slice_start, slice_end, starting at +toffset.

+
+
+

axis_meanings field

+

So far we are allowing any axis to be a slice or volume axis, but it might be +nice to check. One way of doing this is:

+
>>> element = dict(applies_to=['mytime'],
+...                axis_meanings=["volume", "time"],
+...                acquisition_times=[0, 120, 240, 480, 600])
+>>> element = dict(applies_to=['myslice'],
+...                axis_meanings=["slice"],
+...                acquisition_times=[0, 20, 40, 60, 80, 100])
+
+
+

In this case we can assert that acquisition_times applies to an axis with +meanings that include “slice” or that it applies to an axis with meaning +“volume”. For example:

+
>>> # Should raise an error on reading full JSON
+>>> element = dict(applies_to=['myslice'],
+...                axis_meanings=["frequency"],
+...                acquisition_times=[0, 20, 40, 60, 80, 100])
+
+
+

Being able to specify meanings that apply to more than one axis might also +help for the situation where there is more than one frequency axis:

+
>>> hdr = dict(nipy_header_version='1.0',
+...            axis_names = ["frequency1", "frequency2", "slice", "time"],
+...            axis_metadata = [
+...                dict(applies_to = ["frequency1"],
+...                     axis_meanings = ["frequency"]),
+...                dict(applies_to = ["frequency2"],
+...                     axis_meanings = ["frequency"]),
+...                dict(applies_to = ['slice'],
+...                     axis_meanings = ["slice"]),
+...                dict(applies_to = ['time'],
+...                     axis_meanings = ["time", "volume"]),
+...            ])
+
+
+

We can also check that space axes really are space axes:

+
>>> hdr = dict(nipy_header_version='1.0',
+...            axis_names = ["frequency", "phase", "slice", "time"],
+...            axis_metadata = [
+...                dict(applies_to = ["frequency"],
+...                     axis_meanings = ["frequency", "space"]),
+...                dict(applies_to = ["phase"],
+...                     axis_meanings = ["phase", "space"]),
+...                dict(applies_to = ["slice"],
+...                     axis_meanings = ["slice", "space"]),
+...                dict(applies_to = ["time"],
+...                     axis_meanings = ["time", "volume"]),
+...                dict(applies_to=["time"],
+...                     q_vector = dict(
+...                        spatial_axes = ["frequency", "phase", "slice"],
+...                        array = [[0, 0, 0],
+...                                 [1000, 0, 0]]))
+...                ])
+
+
+

For the q_vector field, we can check that all of the spatial_axes axes +(“frequency”, “phase”, “slice”) do in fact have meaning “space”.

+

For this check to pass, either of these must be true:

+
    +
  • no axes are labeled with the meaning “space” OR

  • +
  • the only three axes with label “space” are those named in spatial_axes.

  • +
+
+

multi_affine field

+
+
Use case
+

When doing motion correction on a 4D image, we calculate the required affine +transformation from, say, the second image to the first image; the +third image to the first image; etc. If there are N volumes in the 4D image, +we would need to store N-1 affine transformations. If we have registered to +the mean volume of the volume series instead of one of the volumes in the +volume series, then we need to store all N transforms.

+

We often want to store this set of required transformations with the image, +but NIfTI does not allow us to do that. SPM therefore stores these transforms +in a separate MATLAB-format .mat file. We currently don’t read these +transformations because we have no API in nibabel to present or store multiple +affines.

+
+
+
Implementation
+

Assume the 4D volume has T time points (volumes).

+

There are two ways we could implement the multi-affines. The first would be to +have (T x 3 x 4) array of affines, with one for each volume / time point, +and a spatial_axes field specifying the input axes for the affine. This +is the same general idea as the q_vector field:

+
>>> element = dict(applies_to=['time'],
+...                multi_affine = dict(
+...                    spatial_axes = ['frequency', 'phase', 'slice'],
+...                    array = [[[   2.86,   -0.7 ,    0.83,  -80.01],
+...                               [   0.71,    2.91,    0.01, -114.59],
+...                               [  -0.54,    0.13,    4.42,  -54.34]],
+...                              [[   2.87,   -0.38,    1.19,  -92.77],
+...                               [   0.31,    2.97,    0.45, -110.87],
+...                               [  -0.82,   -0.2 ,    4.32,  -33.89]],
+...                              [[   2.97,   -0.39,    0.31,  -78.95],
+...                               [   0.33,    2.9 ,    1.06, -116.99],
+...                               [  -0.29,   -0.68,    4.36,  -36.41]],
+...                              [[   2.93,   -0.5 ,    0.61,  -78.02],
+...                               [   0.4 ,    2.9 ,    0.99, -118.9 ],
+...                               [  -0.5 ,   -0.59,    4.35,  -33.61]],
+...                              [[   2.95,   -0.44,    0.49,  -77.86],
+...                               [   0.3 ,    2.78,    1.62, -125.83],
+...                               [  -0.46,   -1.03,    4.17,  -21.66]]]))
+>>> np.array(element['multi_affine']['array']).shape
+(5, 3, 4)
+
+
+

This obeys the axis transpose principle, because the spatial axes are +specified. If the user transposes the image, the order of axis names in +axis_names changes, but the correspondence between axis names and affine +columns is still correctly encoded in the spatial_axes.

+

Another option would be to partially follow the NRRD format in giving the column vectors +from the affine to the axis to which they apply, and split the translation +into a separate offset vector:

+
>>> hdr = dict(nipy_header_version='1.0',
+...            axis_names = ["time"],
+...            axis_metadata = [
+...                dict(applies_to=['time'],
+...                     output_vector=dict(
+...                        spatial_axis = ['frequency'],
+...                        array = [
+...                                 [ 2.86, 0.71, -0.54],
+...                                 [ 2.87, 0.31, -0.82],
+...                                 [ 2.97, 0.33, -0.29],
+...                                 [ 2.93, 0.4 , -0.5 ],
+...                                 [ 2.95, 0.3 , -0.46],
+...                                 ])),
+...                dict(applies_to=['time'],
+...                     output_vector=dict(
+...                        spatial_axis = ['phase'],
+...                        array = [
+...                                 [ -0.7 , 2.91,  0.13],
+...                                 [ -0.38, 2.97, -0.2 ],
+...                                 [ -0.39, 2.9 , -0.68],
+...                                 [ -0.5 , 2.9 , -0.59],
+...                                 [ -0.44, 2.78, -1.03],
+...                                 ])),
+...                dict(applies_to=['time'],
+...                     output_vector = dict(
+...                        spatial_axis = ['slice'],
+...                        array = [
+...                                 [ 0.83, 0.01, 4.42],
+...                                 [ 1.19, 0.45, 4.32],
+...                                 [ 0.31, 1.06, 4.36],
+...                                 [ 0.61, 0.99, 4.35],
+...                                 [ 0.49, 1.62, 4.17],
+...                                 ])),
+...                dict(applies_to=['time'],
+...                     output_offset = [
+...                              [ -80.01, -114.59, -54.34],
+...                              [ -92.77, -110.87, -33.89],
+...                              [ -78.95, -116.99, -36.41],
+...                              [ -78.02, -118.9,  -33.61],
+...                              [ -77.86, -125.83, -21.66],
+...                              ])],
+...           )
+>>> np.array(hdr['axis_metadata'][0]['output_vector']['array']).shape
+(5, 3)
+>>> np.array(hdr['axis_metadata'][1]['output_vector']['array']).shape
+(5, 3)
+>>> np.array(hdr['axis_metadata'][2]['output_vector']['array']).shape
+(5, 3)
+>>> np.array(hdr['axis_metadata'][3]['output_offset']).shape
+(5, 3)
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/devel/biaps/biap_0004.html b/devel/biaps/biap_0004.html new file mode 100644 index 0000000000..ad27ee2ded --- /dev/null +++ b/devel/biaps/biap_0004.html @@ -0,0 +1,342 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
+
+ +
+
+

NiBabel

+

Access a cacophony of neuro-imaging file formats

+
+
+ + + + +
+
+
+
+ +
+

BIAP4 - Merging nibabel and dcmstack

+
+
Author:
+

Brendan Moloney, Matthew Brett

+
+
Status:
+

Draft

+
+
Type:
+

Standards

+
+
Created:
+

2012-11-21

+
+
+

In which we set out what dcmstack does and how it might integrate with the +nibabel objects and functions.

+
+

Motivation

+

It is very common to convert source DICOM images to another format, typically +Nifti, before doing any image processing. The Nifti format is significantly +easier to work with and has wide spread compatibility. However, the vast +amount of meta data stored in the source DICOM files will be lost.

+

After implementing this proposal, users will be able to preserve all of the +meta data from the DICOM files during conversion, including meta data from +private elements. The meta data will then be easily accessible through the +SpatialImage API:

+
>>> nii = nb.load('input.nii')
+>>> data = nii.get_data()
+>>> print data.shape
+(256, 256, 24, 8)
+>>> print nii.get_meta('RepetitionTime')
+3500.0
+>>> echo_times = [nii.get_meta('EchoTime', (0, 0, 0, idx))
+                  for idx in xrange(data.shape[-1])]
+>>> print echo_times
+[16.4, 32.8, 49.2, 65.6, 82.0, 98.4, 114.8, 131.2]
+>>> print nii.get_meta('AcquisitionTime', (0, 0, 1, 0))
+110455.370000
+>>> print nii.get_meta('AcquisitionTime', (0, 0, 2, 0))
+110457.272500
+>>> print nii.get_meta('AcquisitionTime', (0, 0, 1, 1))
+110455.387500
+
+
+
+
+

Overview

+

dcmstack reads a series of DICOM images, works out their relationship in terms +of slices and volumes, and compiles them into multidimensional volumes. It can +produce the corresponding data volume and affine, or a Nifti image (with any +additional header information set appropriately).

+

In the course of the read, dcmstack creates a DcmMeta object for +each input file. This object is an ordered mapping that can contain a copy +of all the meta data in the DICOM header. By default some filtering is +applied to reduce the chance of including PHI. The set of DcmMeta objects are +then merged together in the same order as the image data to create a single +DcmMeta object that summarizes all of the meta data for the series.

+

To summarize the meta data, each element is classified based on how the values +repeat (e.g. const, per_slice, per_volume, etc.). Each element has a name (the +keyword from the DICOM standard) and one or more values (the number of values +depends on the classification and the shape of the image). Each classification’s +meta data is stored stored in a separate nested dictionary.

+

While creating the Nifti image output, the DcmMeta is stored in a +DcmMetaExtension which can be added as a header extension. This extension +simply does a JSON encoding directly on the DcmMeta object.

+

When working with these images, it’s possible to keep track of the +meta-information in the DcmMetaExtension. For example, when taking slice out +of a 3D volume, we keep track of the information specific to the chosen +slice, and remove information for other slices. Or when merging 3D volumes to +a 4D time series, we want to merge together the meta data too.

+

At the moment, dcmstack only creates Nifti images. There’s no reason that this +should be so, and the relationship of dcmstack to other spatial images should be +more flexible.

+
+
+

Issues

+
+

DcmMetaExtension tied to NiftiExtension

+

At the moment, DcmMetaExtension inherits from the NiftiExtension, allowing +the data to be dumped out to JSON when writing into the extension part of a +Nifti header.

+

There’s no reason that the DcmMetaExtension should be tied to the Nifti +format.

+
+

Plan

+

Refactor DcmMetaExtension to inherit from object. Maybe rename DcmMeta or +something. Make a NiftiExtension object when needed with a new object +wrapping the DcmMeta in the Extension API?

+
+
+

Status

+

Resolved. We now have a separate DcmMeta object which inherits from +OrderedDict and contains all of the functionality previously in +DcmMetaExtension except those related to acting as a Nifti1Extension. +The DcmMetaExtension now provides just the functionality for being +a Nifti1Extension.

+
+
+
+

Keeping track of metadata when manipulating images

+

When slicing images, it is good to be able to keep track of the relevant DICOM +metadata for the particular slice. Or when merging images, it is good to be +able to compile the metadata across slices into the (e.g) volume metadata. Or, +say, when coregistering an image, it is good to be able to know that the +metadata that is per-slice no longer directly corresponds to a slice of the +data array.

+

At the moment, dcmstack deals with this by wrapping the image with DICOM meta +information in NiftiWrapper object : see +https://github.com/moloney/dcmstack/blob/d157741/src/dcmstack/dcmmeta.py#L1232. +This object accepts a Nifti image as input, that usually contains a +DcmMetaExtension, and has methods get_meta (to get metadata from extension), +split (for taking slice specific metadata into the split parts), meta_valid +to check the metadata against the Nifti information, and methods to remove / +replace the extension, save to a filename, and create the object with various +alternative classmethod constructors.

+

In particular, the meta_valid method needs to know about both the enclosed +image, and the enclosed meta data.

+

Can we put this stuff into the SpatialImage image object of nibabel, so we +don’t need this wrapper object?

+
+

Plan

+

Put the DcmMeta data into the extra object that is input to the +SpatialImage and all other nibabel image types.

+

Add a get_meta method to SpatialImage that uses the to-be-defined API of the +extra object. Maybe, by default, this would just get keys out of the mapping.

+

Define an API for the extra object to give back metadata that is potentially +varying (per slice or volume). We also need a way to populate the extra object +when loading an image that has an associated DcmMeta object.

+

Use this API to get metadata. Try and make this work with functions outside the +SpatialImage such as four_to_three and three_to_four in nibabel.funcs. +These functions could use the extra API to get varying meta-information.

+

** TODO : specific proposal for SpatialImage and extra API changes **

+
+
+
+

Detecting slice or volume-specific data difficult for 3D and 4D DICOMS

+

The DcmMeta object needs to be able to identify slice and volume specific +information when reading the DICOM, so that it can correctly split the resulting +metadata, or merge it.

+

This is easy for slice-by-slice DICOM files because anything that differs +between the slices is by definition slice-specific. For 3D and 4D data, such as +Siemens Mosaic, some of the fields in the private headers contains +slice-by-slice information for the volume contained. There’s not automatic way +of detecting slice-by-slice information in this case, so we have to specify +which fields are slice-by-slice when reading. That is, we need to specialize +the DICOM read for each type of volume-containing DICOM - such as Mosaic or the +Philips multi-frame format.

+
+

Plan

+

Add create_dcmmeta method to the nibabel DICOM wrapper objects, that can be +specialized for each known DICOM format variation. Put the rules for slice +information etc into each class.

+

For the Siemens files, we will need to make a list of elements from the private +CSA headers that are known to be slice specific. For the multiframe DICOM files +we should be able to do this in a programmatic manner, since the varying data +should live in the PerFrameFunctionalSequence DICOM element. Each element that +is reclassified should be simplified with the DcmMeta.simplify method so that +it can be classified appropriately.

+
+
+
+

Meta data in nested DICOM sequences can not be independently classified

+

The code for summarizing meta data only works on the top level of key/value +pairs. Any value that is a nested dataset is treated as a single entity, +which prevents us from classifying its individual elements differently.

+

In a DICOM data set, any element that is a sequence contains one or more +nested DICOM data sets. For most MRI images this is not an issue since +they rarely contain many sequences, and the ones they do are usually small +and relatively unimportant. However in multiframe DICOM files make heavy +use of nested sequences to store data.

+
+

Plan

+

This same issue was solved for the translated Siemens CSA sub headers by +unpacking each nested dataset by joining the keys from each level with a +dotted notation. For example, in the CsaSeries subheader there is a nested +MrPhoenixProtocol dataset which has an element ulVersion so the key we +use after unpacking is CsaSeries.MrPhoenixProtocol.ulVersion.

+

We can take the same approach for DICOM sequence elements. One additional +consideration is that each of these element is actually a list of data sets, +so we would need to add an index number to the key somehow.

+

The alternative is to handle nested data sets recursively in the meta data +summarizing code. This would be fairly complex and you would no longer be +able to refer to each element with a single string, at least not without +some mini-language for traversing the nested datasets.

+
+
+
+

Improving access to varying meta data through the Nifti

+

Currently, when accessing varying meta data through the get_meta method +you can only get one value at a time:

+
>>> echo_times = [nii.get_meta('EchoTime', (0, 0, 0, idx))
+                  for idx in xrange(data.shape[-1])]
+
+
+

You can easily get multiple values from the DcmMeta object itself, but +then you lose the capability to automatically check if the meta data is +valid in relation to the current image.

+
+
+
+ + +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/devel/biaps/biap_0005.html b/devel/biaps/biap_0005.html new file mode 100644 index 0000000000..9a4c96ad83 --- /dev/null +++ b/devel/biaps/biap_0005.html @@ -0,0 +1,271 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
+
+ +
+
+

NiBabel

+

Access a cacophony of neuro-imaging file formats

+
+
+ + + + +
+
+
+
+ +
+

BIAP5 - A streamlines converter

+
+
Author:
+

Marc-Alexandre Côté

+
+
Status:
+

Draft

+
+
Type:
+

Standards

+
+
Created:
+

2013-09-03

+
+
+

The first objective of this proposal is to add support to other streamlines +format. The second objective is to be able to easily convert from one file +format to another.

+
+

Motivation

+

There are a couple of different formats for saving streamlines to a file. +Currently, NiBabel only support one of them: TRK from Trackvis. NiBabel could greatly benefit from supporting +other formats: +TCK +(MRtrix), +VTK +(Camino, MITK) +and more. Moreover, being able to move from one format to another would be +convenient. To ease the conversion process, a generic format from which to +inherit and some common header fields would be necessary. This is similar to +what NiBabel already has for neuroimages.

+

After implementing this proposal, users could load and use streamlines file like this:

+
>>> import nibabel as nib
+>>> f = nib.streamlines.load('my_trk.trk', lazy_load=False)
+>>> type(f)
+nibabel.streamlines.base_format.Streamlines
+>>> f.points
+[array([ [1, 1, 1],
+         [2, 2, 2],
+         [3, 3, 3] ]),
+ array([ [4, 4, 4],
+         [5, 5, 5] ])]
+>>> nib.streamlines.convert('my_trk.trk', 'my_tck.tck')
+>>> f2 = nib.streamlines.load('my_trk.tck', lazy_load=False)
+>>> type(f2)
+nibabel.streamlines.base_format.Streamlines
+>>> f2.points
+[array([ [1, 1, 1],
+         [2, 2, 2],
+         [3, 3, 3] ]),
+ array([ [4, 4, 4],
+         [5, 5, 5] ])]
+
+
+

Of course, similar functions will be available for ‘scalars’ (per point) and ‘properties’ (per streamline) as defined in the TrackVis format. A simple example to save three streamlines with no scalars nor properties would look like this:

+
>>> import nibabel as nib
+>>> points = [np.arange(1*3).reshape((1,3)),
+              np.arange(2*3).reshape((2,3)),
+              np.arange(5*3).reshape((5,3))]
+>>> streamlines = nib.streamlines.Streamlines(points)
+>>> nib.streamlines.save(streamlines, 'data1.trk')  # Default TRK header is used but updated with streamlines information.
+
+>>> FA = nib.load('FA.nii')
+>>> streamlines.header = nib.streamlines.header.from_nifti(FA)  # Uses information of the FA to create an header.
+>>> nib.streamlines.save(streamlines, 'data2.trk')  # Streamlines' header is used but also updated with streamlines information.
+
+>>> from nib.streamlines.header import VOXEL_ORDER, VOXEL_SIZES
+>>> hdr = nib.streamlines.TrkFile.get_empty_header()  # Default TRK header
+>>> hdr[VOXEL_ORDER] = "LAS"
+>>> hdr[VOXEL_SIZES] = (2, 2, 2)
+>>> streamlines.header = hdr
+>>> nib.streamlines.save(streamlines, 'data3.trk')  # Uses hdr to create a TRK header.
+
+
+
+
+

Overview

+

All code related to managing streamlines should be kept in a separate folder: +nibabel.streamlines. A first file, base_format.py, would contain base +classes acting as general interfaces from which new streamlines file format +will inherit.

+

Streamlines would be represented by its own class Streamlines which will +have three main properties: points, scalars and properties. +Streamlines objects can be iterate over producing tuple of points, +scalars and properties for each streamline.

+

The generic class StreamlinesFile would look like this:

+
class StreamlinesFile:
+    @classmethod
+    def get_magic_number(cls):
+        raise NotImplementedError()
+
+    @classmethod
+    def is_correct_format(cls, fileobj):
+        raise NotImplementedError()
+
+    @classmethod
+    def get_empty_header(cls):
+        raise NotImplementedError()
+
+    @classmethod
+    def load(cls, fileobj, lazy_load=True):
+        raise NotImplementedError()
+
+    @classmethod
+    def save(cls, streamlines, fileobj):
+        raise NotImplementedError()
+
+    @staticmethod
+    def pretty_print(streamlines):
+        raise NotImplementedError()
+
+
+

When inheriting from a base class, a specific streamline format class should know how to do its i/o, in particular how to iterate through the streamlines without loading the whole file into memory.

+

Once, the right interface is in place, the conversion part should be quite easy. Moreover, the conversion could be done without loading the input file entirely into memory thanks to generators. Actually, the convert function should looks like this:

+
def convert(in_fileobj, out_filename):
+    # Loading part
+    streamlines_file = detect_format(in_fileobj)
+    streamlines = streamlines_file.load(in_fileobj, lazy_load=True)
+
+    # Saving part
+    streamlines_file = detect_format(out_filename)
+    streamlines_file.save(streamlines, out_filename)
+
+
+

Of course, this implies some sort of general header compatibility between every format.

+
+
+

Issues

+ +
+
+

Future Work

+

A first interesting subclass would be the DynamicStreamlineFile offering +a way to append streamlines to an existing file when format permits it.

+
+
+ + +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/devel/biaps/biap_0006.html b/devel/biaps/biap_0006.html new file mode 100644 index 0000000000..63d0775a28 --- /dev/null +++ b/devel/biaps/biap_0006.html @@ -0,0 +1,362 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
+
+ +
+
+

NiBabel

+

Access a cacophony of neuro-imaging file formats

+
+
+ + + + +
+
+
+
+ +
+

BIAP6 - Identifying image axes

+
+
Author:
+

Matthew Brett

+
+
Status:
+

Draft

+
+
Type:
+

Standards

+
+
Created:
+

2015-07-11

+
+
+
+

Background

+

Image axes can have meaningful labels.

+

For example in a typical 4D NIfTI file, as we move along the 4th dimension in +the image array, we are also moving in time. For example, this would be the +first volume (in time):

+
img = nibabel.load('my_4d.nii')
+data = img.get_data()
+vol0 = data[..., 0]
+
+
+

and this would be second volume in time:

+
vol1 = data[..., 1]
+
+
+

It would therefore be reasonable to label the 4th axis of this image as ‘time’ +or ‘t’.

+

We need to know which axis is the “time” axis for many reasons, including +being able to select whole image volumes to align during motion correction, +and doing spatial smoothing, where we want to avoid smoothing along the time +dimension.

+

It is common to acquire MRI images one slice at a time. In a 3D or 4D NIfTI, +the 3rd axis often contains these slices. So this these would be the first +and second slices of data collected by the scanner:

+
slice0 = vol0[:, :, 0]
+slice1 = vol0[:, :, 1]
+
+
+

In this case we might refer to the 3rd axis as the “slice” axis. We might +care about knowing the “slice” axis, because we do processing specific to the +slice axis, such as slice-timing correction.

+

For an individual 2D slice, MRI physicists distinguish between the image axis +encoded during a single continual readout of the signal (frequency encoding +direction) and the image axis encoded in a series of stepwise changes in the +phase encode gradient (phase encoding direction). We care about the phase +encoding direction because we usually correct for image distortion only along +this direction.

+

Let us say that the first axis is the frequency encoding axis, and the second +is the phase encoding axis. Now we can label all four of our axes:

+
    +
  • “frequency”;

  • +
  • “phase”;

  • +
  • “slice”;

  • +
  • “time”.

  • +
+

In fact the NIfTI format can store this information. NIfTI specifies that the +fourth image dimension should have units in terms of time (seconds), frequency +(Hertz, radians per second) or concentration (parts per million), where the +value difference between elements on the fourth axis is in +img.header['pixdim'][4], and the units of this difference are available in +img.header['xyzt_units']. The field img.header['dim_info'] can +identify the frequency, phase and slice-encoding axes.

+
+

Time axis as the fourth axis

+

In the NIfTI standard, time must be the fourth dimension.

+

In fact, the NIfTI standard specifies that the fourth axis must be time. If +we want to store more than one volume that do not differ across time, then we +have to set the 4th dimension to be length 1, and have 5th dimension have +length > 1. Quoting from the standard:

+
In NIFTI-1 files, dimensions 1,2,3 are for space, dimension 4 is for time,
+and dimension 5 is for storing multiple values at each spatiotemporal
+voxel.
+
+
+

This arrangement happens in practice. For example, SPM deformation fields +have three values for each voxel (x, y, z displacement), and have shape (I, J, +K, 1, 3):

+
In [7]: img = nib.load('y_highres001.nii')
+In [8]: img.shape
+Out[8]: (121, 145, 121, 1, 3)
+
+
+

So, for correctly written NIfTI images, we can identify time by the fact that +it is the fourth axis.

+

MGH format also appears to use the fourth dimension for time. The dimensions +are listed in order width, height, depth, nframes and “frames” is always +the slowest changing dimension in the image data buffer. Of course, in numpy, +this does not tell us which axis this must be in the returned array, but at +least the load_mgh.m MATLAB function (see MGH format) returns the frame +axis as the last axis, as does nibabel.

+

The ECAT and PAR / REC formats seem to be primarily based on and stored as +slices (2D arrays) which can then be concatenated to form volumes, implying a +slowest-changing axis of volume. Nibabel currently arranges PAR images with +volume as the 4th and last axis.

+

On the other hand, the MINC format:

+
    +
  1. gives specific names to the image data axes so we can directly find the +time axis

  2. +
  3. expects (given the common ordering of these names in MINC files) that the +time axis will be first:

    +
    In [31]: mnc2 = h5py.File('nibabel/tests/data/minc2_4d.mnc', 'r')['minc-2.0']
    +In [32]: mnc2['dimensions'].values()
    +Out[32]:
    +[<HDF5 dataset "time": shape (2,), type "<f8">,
    +<HDF5 dataset "xspace": shape (), type "<i4">,
    +<HDF5 dataset "yspace": shape (), type "<i4">,
    +<HDF5 dataset "zspace": shape (), type "<i4">]
    +
    +
    +
  4. +
+

This reflects MINC’s lineage as C-library, where the C convention is for the +first axis in an array is the slowest changing. arr[0] in a C-convention +4D array would be the first volume, where time (volume) is the slowest +changing axis.

+

MINC2 uses HDF5 storage, and HDF5 uses C storage order for standard contiguous +arrays on disk - see “7.3.2.5. C versus Fortran Dataspaces” in chapter 7 of +the HDF5 user guide.

+

BrainVoyager STC files store data in (fastest to +slowest changing) order: columns (of slice); rows (of slice); time; slice. The +VTC stores the data in the (fast to slow) order: +time; Anterior->Posterior; Superior->Inferior; Left->Right.

+
+
+

Images can have more than four axes

+

We’ve already seen the example of NIfTI images where the 4th axis is length 1 +and the 5th axis is length 3, encoding a deformation field.

+

This is a trick NIfTI uses to allow us to identify the “time” axis.

+

We can also have (rarely) images of 5D, where the time axis has length > 1. +For example, some MR acquisitions take two echoes per time point, so we might +have an image of shape (64, 64, 32, 200, 2), where the fourth axis is time and +the fifth axis is echo number.

+
+
+

The current nibabel convention

+

The nibabel rule of thumb has been that, when we return an image array, it +should be in the order described in the format’s user documentation.

+

So, for NIfTI format images, the image dimension sizes are listed in fastest +to slowest changing order, implying that the expected array to be returned +will have that same axis order. Time is always the fourth (rather than the +first) dimension of a 4D NIfTI. Nibabel NIfTI images return the array in that +order, and the time / volume axis is the last in a 4D nibabel NIfTI image +array.

+

On the other hand MINC clearly expects that the axes will be returned in the +order the axes are listed in the MINC file. This is also (usually) the +slowest-to-fastest changing order in the underlying file, and by convention, +the first axis is the time axis. Nibabel MINC images return the array in this +same order with the time / volume axis first, but in general it returns the +array with the axes in the order listed in the MINC file.

+

We don’t currently have BrainVoyager support, so this will be a decision we +have to make before finalizing the API.

+
+
+

Distinguishing time and volume

+

A volume is a complete set of slices making up one brain image.

+

In NIfTI:

+
    +
  • 3D image: volume == image array i.e. arr[:, :, :];

  • +
  • > 3D image: volume == a single slice over the final dim > 3 dimensions +e.g.: arr[:, :, :, 2] (4D); arr[:, :, :, 0, 3] (5D).

  • +
+

We saw above that the MGH format refers to a volume (in our sense) as a +frame. ECAT has the same usage - a frame is a 3D volume. The fmristat +software uses frame in the same sense, e.g., line 32 of example.m.

+

Unfortunately DICOM appears to use “frame” to mean a 2D slice. For example, +here is the definition of a “multi-frame image”:

+
3.8.9 Multi-frame image:
+    Image that contains multiple two-dimensional pixel planes.
+
+
+

From PS 3.3 of the 2011 DICOM standard.

+
+
+
+

Possible solutions to finding axes

+

A general solution for finding axes would be to attach axis labels to the +returned image data array, or to the image object.

+

A less general solution would be to identify the time axis by convention - say +- by being the fourth axis in a 4D array.

+

Finding the time axis is an urgent problem, because we are currently +considering utility routines for (spatial) smoothing, and viewing images, that +need to know which axis is time.

+
+

General solution: associating axes and labels

+

Possible options:

+
    +
  • Add a property time_axis_index to the image class. This always returns 3 +(4th axis) for images other than MINC. For MINC, it returns the index of +the image dimension labeled time;

  • +
  • Add a property axis_labels to the image class. By default, most image +types return ‘i’, ‘j’, ‘k’, ‘time’. MINC returns the image dimension +labels;

  • +
  • Copy or depend on datarray (no other dependencies) or xray (depends on +Pandas). Use these to attach labels directly to the image data array axes. +These labels could then be preserved through operations like slicing.

  • +
+
+
+

Using convention : enforcing time as 4th axis

+

This solution could be implemented as well as the solution using labels.

+

At the moment, we can always identify the time axis in the NIfTI file, because +it is the 4th axis in the returned image.

+

This is probably so for:

+
    +
  • PAR/REC

  • +
  • ECAT

  • +
  • MGH

  • +
+

but not so for MINC1 or MINC2, where time is typically (?always) the first +axis.

+

One option would be to make a new MINC1, MINC2 image class that reorders the +MINC axes to have time last. Call these new classes NiMINC1, NiMINC2.

+

In order to avoid surprise, we continue to return MINC1, MINC2 class images +from nibabel.load, but give a DeprecationWarning when doing this, saying +that the default load will change in future versions of nibabel, and +suggesting the as_niminc=True keyword-only argument to load, defaulting to +as_niminc=False (giving the current nibabel behavior).

+

In Nibabel 3.0, we require the as_niminc keyword argument.

+

In Nibabel 4.0, we default to as_niminc=True.

+

We would still have to deal with MINC1, MINC2 images in memory - and therefore +cannot in general assume that the fourth dimension of any image data array is +time. In order to deal with this, routines that need to know the time +dimension would have to check whether they were dealing with MINC1, MINC2, +which ends up being similar to the time_axis_index option above.

+
+
+
+ + +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/devel/biaps/biap_0007.html b/devel/biaps/biap_0007.html new file mode 100644 index 0000000000..9c4aa18d00 --- /dev/null +++ b/devel/biaps/biap_0007.html @@ -0,0 +1,217 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
+
+ +
+
+

NiBabel

+

Access a cacophony of neuro-imaging file formats

+
+
+ + + + +
+
+
+
+ +
+

BIAP7 - Loading multiple images

+
+
Author:
+

Matthew Brett

+
+
Status:
+

Draft

+
+
Type:
+

Standards

+
+
Created:
+

2015-07-18

+
+
+
+

Background

+
+

Some formats store images with different shapes in the same file

+

The ECAT file format can contain more than one type of image in a single image +file.

+

ECAT can store many frames in a single image file. Each frame has its own +subheader. The subheader specifies the 3D image size; each frame can +therefore have a different image size.

+

We currently raise an error if you try and load an ECAT file where the frames +do not have the same 3D dimensions.

+

It would be better if we could allow loading multiple images with different +image dimensions, from a single ECAT file.

+

Vista data format and Lipsia format are other formats that allow saving +multiple images with different image dimensions in the same file. We don’t +currently support Lipsia or Vista formats and it is not clear how we would do +that with the current load API.

+

We have had some discussion about saving multiple images into a +single HDF5 file - see https://github.com/nipy/nibabel/pull/215#issuecomment-122357444

+
+
+

It can be useful to load 4D images as multiple 3D images

+

We sometimes want to load a 4D image as multiple 3D images.

+

When we are doing motion correction, we often want to split up a 4D image into +separate 3D images.

+

Motion estimation results in different affines for each volume in the 4D time +series. At the moment we have no API for returning these affines with a 4D +image. One way of doing that is to load the 4D image and affines as a +sequence of 3D images, each with their own affine.

+

We currently have a proposal open for a JSON header extension that can store +these 4D affines for a 4D NIfTI file.

+

SPM saves the affines in an associated .mat file, with one affine per +volume in the 4D image.

+
+
+
+

Options

+
+
+

Return an image sequence from load for some file formats

+

We don’t currently load ECAT files from the top-level nibabel.load +function.

+

We do have nibabel.ecat.load, which raises an error for an ECAT file +having frames with different image dimensions.

+

We could therefore choose to return a sequence of images from nibabel.load +on an ECAT file, with one element per frame in the ECAT file.

+

Most ECAT images are 4D images, in the sense that the frames in the file do +all have the same image dimensions and data type, so this might be cumbersome +as a default.

+

We would have to work out how to deal with nibabel.ecat.load.

+

The same principles apply to the Lipsia / Vista formats, except we have no +backward-compatibility problems, and it seems to be more common for these +formats to mix image types in a single file.

+
+
+

Add a load_multi top-level function

+

nibabel.load_multi always returns an image sequence.

+

nibabel.load always returns a single image.

+

nibabel.load on ECAT (etc) files could first do load_multi, then check +the resulting image dimensions, raising an error if incompatible, +concatenating otherwise.

+

load_multi on current formats like NIfTI could return one image per +volume, where each volume might have its own affine, as loaded from the JSON +header extension or the SPM .mat file.

+
+
+

Next steps:

+
    +
  • Make sure there are use-cases where you could wish to call load vs. load_multi on the same image (perhaps a Nifti image with different affines for each volume)

  • +
  • Investigate AFNI file formats as a use-case for this.

  • +
  • Check the nilearn codebase, see if iter_img and slice_img functions might offer a post-load alternative. Also check if those functions could be deprecated in favor of slicing / iterating on dataobj

  • +
  • Create a new issue to implement getting an iterator on dataobj?

  • +
+
+
+ + +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/devel/biaps/biap_0008.html b/devel/biaps/biap_0008.html new file mode 100644 index 0000000000..bb5617339f --- /dev/null +++ b/devel/biaps/biap_0008.html @@ -0,0 +1,268 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
+
+ +
+
+

NiBabel

+

Access a cacophony of neuro-imaging file formats

+
+
+ + + + +
+
+
+
+ +
+

BIAP8 - Always load image data as floating point

+
+
Author:
+

Matthew Brett

+
+
Status:
+

Accepted

+
+
Type:
+

Standards

+
+
Created:
+

2018-04-18

+
+
+

get_fdata shipped as of nibabel 2.2.0.

+

See this mailing list thread for discussion on an earlier version of this proposal.

+
+

Background

+
+

Summary

+

The problem with our current get_data method is that the returned data +type is difficult to predict, and can switch between integer and floating +point types depending on values in the image header.

+

The underlying problem is that the author and the user of a given NIfTI image +would be unlikely to expect that the scalefactors of the NIfTI header (which +the user will probably not be aware of) will affect the calculations done on +the image data after loading into memory.

+
+
+

In detail

+

At the moment, if you do this:

+
img = nib.load('my_image.nii')
+data = img.get_data()
+
+
+

then the data type (dtype) of the returned data array depends on the values in +the header of my_image.nii. Specifically, if the raw on-disk data type +is np.int16 (it often is) and the header scalefactor values are default (1 +for slope, 0 for intercept) then you will get back an array of the on-disk +data type - here np.int16.

+

This is very efficient in terms of memory, but it can be a real trap unless +you are careful.

+

For example, let’s say you had a pipeline where you did this:

+
sum = img.get_data().sum()
+
+
+

That would work fine most of the time, when the data on disk is +floating point, or the scalefactors are not default (1, 0). Then one +day, you get an image with int16 data type on disk and (1, 0) +scalefactors, and your sum calculation is now being done in int16, and +silently overflows. I (MB) ran into this when teaching - I had to cast some +image arrays to floating point to get sensible answers.

+
+
+

Current implementation

+

get_data has the following implementation, at time of writing:

+
def get_data(self):
+    """ Return image data from image with any necessary scalng applied
+
+    If the image data is a array proxy (data not yet read from disk) then
+    read the data, and store in an internal cache.  Future calls to
+    ``get_data`` will return the cached copy.
+
+    Returns
+    -------
+    data : array
+        array of image data
+    """
+    if self._data_cache is None:
+        self._data_cache = np.asanyarray(self._dataobj)
+    return self._data_cache
+
+
+

Note that:

+
    +
  • self._dataobj may well be an array proxy object;

  • +
  • np.asanyarray forces the read of an array proxy object into a numpy +array;

  • +
  • the read also fills an internal cache.

  • +
+
+
+
+

Proposal - add, prefer get_fdata method

+

The future default behavior of nibabel should be to do the thing least likely +to trip you up by accident. But - we do not want the result of get_data +to change silently between nibabel versions.

+
    +
  • step 1: now - add get_fdata method:

    +
    def get_fdata(self, dtype=np.float64):
    +    """ Return floating point image data with necessary scalng applied.
    +
    +    If the image data is an array proxy (data not yet read from disk) then
    +    read the data from file, and retain the result in an internal cache.
    +    Future calls to ``get_fdata`` on the same image instance will return
    +    the cached copy.
    +
    +    Parameters
    +    ----------
    +    dtype : numpy dtype specifier
    +        A numpy dtype specifier specifying a floating point type.  Data is
    +        returned as this floating point type.  Default is ``np.float64``.
    +
    +    Returns
    +    -------
    +    fdata : array
    +        Array of image data of data type `dtype`.
    +    """
    +    dtype = np.dtype(dtype)
    +    if not issubclass(dtype, np.inexact):
    +        raise ValueError('{} should be floating point type'.format(dtype))
    +    if self._fdata_cache is None:
    +        self._fdata_cache = np.asanyarray(self._dataobj).astype(dtype)
    +    return self._fdata_cache
    +
    +
    +

    Change all instances of get_data in documentation to get_fdata.

    +

    Add warning about pending deprecation in get_data method, with +suggestion to use get_fdata or np.asanyarray(img.dataobj) if you +want the previous behavior, on the lines of:

    +
    We recommend you use the ``get_fdata`` method instead of the ``get_data``
    +method, because it is easier to predict the return data type.  We will
    +deprecate the ``get_data`` method around April 2018, and remove it around
    +April 2020.
    +
    +If you don't care about the predictability of the return data type, and
    +you want the minimum possible data size in memory, you can replicate the
    +array that would be returned by ``img.get_data()`` by using
    +``np.asanyarray(img.dataobj)``.
    +
    +
    +

    Add floating point cache self._fdata_cache to cache cleared by +uncache method.

    +
  • +
  • step 2: around one year from now - deprecate get_data method;

  • +
  • step 3: around three years from now - make get_data method raise an +error such as NotImplementedError with a helpful message, and remove +associated self._data_cache attribute. Leave this error in place for +a long time, to help people porting older code.

  • +
+
+
+ + +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/devel/biaps/biap_0009.html b/devel/biaps/biap_0009.html new file mode 100644 index 0000000000..0b3fd826dd --- /dev/null +++ b/devel/biaps/biap_0009.html @@ -0,0 +1,478 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + + +
+
+ +
+
+

NiBabel

+

Access a cacophony of neuro-imaging file formats

+
+
+ + + + +
+
+
+
+ +
+

BIAP9 - The Coordinate Image API

+
+
Author:
+

Chris Markiewicz

+
+
Status:
+

Draft

+
+
Type:
+

Standards

+
+
Created:
+

2021-09-16

+
+
+
+

Background

+
+

Surface data is generally kept separate from geometric metadata

+

In contrast to volumetric data, whose geometry can be fully encoded in the +shape of a data array and a 4x4 affine matrix, data sampled to a surface +require the location of each sample to be explicitly represented by a +coordinate. In practice, the most common approach is to have a geometry file +and a data file.

+

A geometry file consists of a vertex coordinate array and a triangle array +describing the adjacency of vertices, while a data file is an n-dimensional +array with one axis corresponding to vertex.

+

Keeping these files separate is a pragmatic optimization to avoid costly +reproductions of geometric data, but presents an administrative burden to +direct consumers of the data.

+
+
+

Terminology

+

For the purposes of this BIAP, the following terms are used:

+
    +
  • Coordinate - a triplet of floating point values in RAS+ space

  • +
  • Vertex - an index into a table of coordinates

  • +
  • Triangle (or face) - a triplet of adjacent vertices (A-B-C); +the normal vector for the face is (\(\overline{AB}\times\overline{AC}\))

  • +
  • Topology - vertex adjacency data, independent of vertex coordinates, +typically in the form of a list of triangles

  • +
  • Geometry - topology + a specific set of coordinates for a surface

  • +
  • Parcel - a subset of vertices; can be the full topology. Special cases include: +* Patch - a connected parcel +* Decimated mesh - a parcel that has a desired density of vertices

  • +
  • Parcel sequence - an ordered set of parcels

  • +
  • Data array - an n-dimensional array with one axis corresponding to the +vertices (typical) OR faces (more rare) in a patch sequence

  • +
+
+
+

Currently supported surface formats

+
    +
  • +
    FreeSurfer
    +
    +
    +
  • +
  • +
    GIFTI: GiftiImage
      +
    • Every image contains a collection of data arrays, which may be +coordinates, topology, or data (further subdivided by type and intent)

    • +
    +
    +
    +
  • +
  • +
    CIFTI-2: Cifti2Image
      +
    • Pure data array, with image header containing flexible axes

    • +
    • The BrainModelAxis is a subspace sequence including patches for +each hemisphere (cortex without the medial wall) and subcortical +structures defined by indices into three-dimensional array and an +affine matrix

    • +
    • Geometry referred to by an associated wb.spec file +(no current implementation in NiBabel)

    • +
    • Possible to have one with no geometric information, e.g., parcels x time

    • +
    +
    +
    +
  • +
+
+
+

Other relevant formats

+
    +
  • +
    MNE’s STC (source time course) format. Contains:
      +
    • Subject name (resolvable with a FreeSurfer SUBJECTS_DIR)

    • +
    • Index arrays into left and right hemisphere surfaces (subspace sequence)

    • +
    • Data, one of: +* ndarray of shape (n_verts, n_times) +* tuple of ndarrays of shapes (n_verts, n_sensors) and (n_sensors, n_times)

    • +
    • Time start

    • +
    • Time step

    • +
    +
    +
    +
  • +
+
+
+
+

Desiderata for an API supporting surfaces

+

The following are provisional guiding principles:

+
    +
  1. A surface image (data array) should carry a reference to geometric metadata +that is easily transferred to a new image.

  2. +
  3. Partial images (data only or geometry only) should be possible. Absence of +components should have a well-defined signature, such as a property that is +None or a specific Exception is raised.

  4. +
  5. All arrays (coordinates, triangles, data arrays) should be proxied to +avoid excess memory consumption

  6. +
  7. Selecting among coordinates (e.g., gray/white boundary, inflated surface) +for a single topology should be possible.

  8. +
  9. Combining multiple brain structures (canonically, left and right hemispheres) +in memory should be easy; serializing to file may be format-specific.

  10. +
  11. Splitting a data array into independent patches that can be separately +operated on and serialized should be possible.

  12. +
+
+

Prominent use cases

+

We consider the following use cases for working with surface data. +A good API will make retrieving the components needed for each use case +straightforward, as well as storing the results in new images.

+
    +
  • Arithmetic/modeling - per-vertex mathematical operations

  • +
  • Smoothing - topology/geometry-respecting smoothing

  • +
  • Plotting - paint the data array as a texture on a surface

  • +
  • Decimation - subsampling a topology (possibly a subset, possibly with +interpolated vertex locations)

  • +
  • Resampling to a geometrically-aligned surface +* Downsampling by decimating, smoothing, resampling +* Inter-subject resampling by using ?h.sphere.reg

  • +
  • Interpolation of per-vertex and per-face data arrays

  • +
+

When possible, we prefer to expose NumPy ndarrays and +allow use of numpy, scipy, scikit-learn. In some cases, it may +make sense for NiBabel to provide methods.

+
+
+
+

Proposal

+

A CoordinateImage is an N-dimensional array, where one axis corresponds +to a sequence of points in one or more parcels.

+
class CoordinateImage:
+    """
+    Attributes
+    ----------
+    header : a file-specific header
+    coordaxis : ``CoordinateAxis``
+    dataobj : array-like
+    """
+
+class CoordinateAxis:
+    """
+    Attributes
+    ----------
+    parcels : list of ``Parcel`` objects
+    """
+
+    def load_structures(self, mapping):
+        """
+        Associate parcels to ``Pointset`` structures
+        """
+
+    def __getitem__(self, slicer):
+        """
+        Return a sub-sampled CoordinateAxis containing structures
+        matching the indices provided.
+        """
+
+    def get_indices(self, parcel, indices=None):
+        """
+        Return the indices in the full axis that correspond to the
+        requested parcel. If indices are provided, further subsample
+        the requested parcel.
+        """
+
+class Parcel:
+    """
+    Attributes
+    ----------
+    name : str
+    structure : ``Pointset``
+    indices : object that selects a subset of coordinates in structure
+    """
+
+
+

To describe coordinate geometry, the following structures are proposed:

+
class Pointset:
+    @property
+    def n_coords(self):
+        """ Number of coordinates """
+
+    def get_coords(self, name=None):
+        """ Nx3 array of coordinates in RAS+ space """
+
+
+class TriangularMesh(Pointset):
+    @property
+    def n_triangles(self):
+        """ Number of faces """
+
+    def get_triangles(self, name=None):
+        """ Mx3 array of indices into coordinate table """
+
+    def get_mesh(self, name=None):
+        return self.get_coords(name=name), self.get_triangles(name=name)
+
+    def get_names(self):
+        """ List of surface names that can be passed to
+        ``get_{coords,triangles,mesh}``
+        """
+
+    def decimate(self, *, n_coords=None, ratio=None):
+        """ Return a TriangularMesh with a smaller number of vertices that
+        preserves the geometry of the original """
+        # To be overridden when a format provides optimization opportunities
+
+
+class NdGrid(Pointset):
+    """
+    Attributes
+    ----------
+    shape : 3-tuple
+        number of coordinates in each dimension of grid
+    """
+    def get_affine(self, name=None):
+        """ 4x4 array """
+
+
+

The NdGrid class allows raveled volumetric data to be treated the same as +triangular mesh or other coordinate data.

+

Finally, a structure for containing a collection of related geometric files is +defined:

+
class GeometryCollection:
+    """
+    Attributes
+    ----------
+    structures : dict
+        Mapping from structure names to ``Pointset``
+    """
+
+    @classmethod
+    def from_spec(klass, pathlike):
+        """ Load a collection of geometries from a specification. """
+
+
+

The canonical example of a geometry collection is a left hemisphere mesh, +right hemisphere mesh.

+

Here we present common use cases:

+
+

Modeling

+
from nilearn.glm.first_level import make_first_level_design_matrix, run_glm
+
+bold = CoordinateImage.from_filename("/data/func/hemi-L_bold.func.gii")
+dm = make_first_level_design_matrix(...)
+labels, results = run_glm(bold.get_fdata(), dm)
+betas = CoordinateImage(results["betas"], bold.coordaxis, bold.header)
+betas.to_filename("/data/stats/hemi-L_betas.mgz")
+
+
+

In this case, no reference to the surface structure is needed, as the operations +occur on a per-vertex basis. +The coordinate axis and header are preserved to ensure that any metadata is +not lost.

+

Here we assume that CoordinateImage is able to make the appropriate +translations between formats (GIFTI, MGH). This is not guaranteed in the final +API.

+
+
+

Smoothing

+
bold = CoordinateImage.from_filename("/data/func/hemi-L_bold.func.gii")
+bold.coordaxis.load_structures({"lh": "/data/anat/hemi-L_midthickness.surf.gii"})
+# Not implementing networkx weighted graph here, so assume we have a function
+# that retrieves a graph for each structure
+graphs = get_graphs(bold.coordaxis)
+distances = distance_matrix(graphs['lh'])  # n_coords x n_coords matrix
+weights = normalize(gaussian(distances, sigma))
+# Wildly inefficient smoothing algorithm
+smoothed = CoordinateImage(weights @ bold.get_fdata(), bold.coordaxis, bold.header)
+smoothed.to_filename(f"/data/func/hemi-L_smooth-{sigma}_bold.func.gii")
+
+
+
+
+

Plotting

+

Nilearn currently provides a +plot_surf function. +With the proposed API, we could interface as follows:

+
def plot_surf_img(img, surface="inflated"):
+    from nilearn.plotting import plot_surf
+    coords, triangles = img.coordaxis.parcels[0].get_mesh(name=surface)
+
+    data = img.get_fdata()
+
+    return plot_surf((triangles, coords), data)
+
+tstats = CoordinateImage.from_filename("/data/stats/hemi-L_contrast-taskVsBase_tstat.mgz")
+# Assume a GeometryCollection that reads a FreeSurfer subject directory
+fs_subject = FreeSurferSubject.from_spec("/data/subjects/fsaverage5")
+tstats.coordaxis.load_structures(fs_subject.get_structure("lh"))
+plot_surf_img(tstats)
+
+
+
+
+

Subsampling CIFTI-2

+
img = nb.load("sub-01_task-rest_bold.dtseries.nii")  # Assume CIFTI CoordinateImage
+parcel = nb.load("sub-fsLR_hemi-L_label-DLPFC_mask.label.gii") # GiftiImage
+structure = parcel.meta.metadata['AnatomicalStructurePrimary'] # "CortexLeft"
+vtx_idcs = np.where(parcel.agg_data())[0]
+dlpfc_idcs = img.coordaxis.get_indices(parcel=structure, indices=vtx_idcs)
+
+# Subsampled coordinate axes will override any duplicate information from header
+dlpfc_img = CoordinateImage(img.dataobj[dlpfc_idcs], img.coordaxis[dlpfc_idcs], img.header)
+
+# Now load geometry so we can plot
+wbspec = CaretSpec("fsLR.wb.spec")
+dlpfc_img.coordaxis.load_structures(wbspec)
+...
+
+
+
+
+
+ + +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/devel/biaps/biap_template.html b/devel/biaps/biap_template.html new file mode 100644 index 0000000000..0cf965bfac --- /dev/null +++ b/devel/biaps/biap_template.html @@ -0,0 +1,211 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
+
+ +
+
+

NiBabel

+

Access a cacophony of neuro-imaging file formats

+
+
+ + + + +
+
+
+
+ +
+

BIAP X — Template and Instructions

+
+
Author:
+

<list of authors’ real names and optionally, email addresses>

+
+
Status:
+

<Draft | Active | Accepted | Deferred | Rejected | Withdrawn | Final | Superseded>

+
+
Type:
+

<Standards Track | Process>

+
+
Created:
+

<date created on, in yyyy-mm-dd format>

+
+
Resolution:
+

<url> (required for Accepted | Rejected | Withdrawn)

+
+
+
+

Abstract

+

The abstract should be a short description of what the BIAP will achieve.

+

Note that the — in the title is an elongated dash, not -.

+
+
+

Motivation and Scope

+

This section describes the need for the proposed change. It should describe +the existing problem, who it affects, what it is trying to solve, and why. +This section should explicitly address the scope of and key requirements for +the proposed change.

+
+
+

Usage and Impact

+

This section describes how users of Nibabel will use features described in this +BIAP. It should be comprised mainly of code examples that wouldn’t be possible +without acceptance and implementation of this BIAP, as well as the impact the +proposed changes would have on the ecosystem. This section should be written +from the perspective of the users of Nibabel, and the benefits it will provide +them; and as such, it should include implementation details only if +necessary to explain the functionality.

+
+
+

Backward compatibility

+

This section describes the ways in which the BIAP breaks backward compatibility.

+

The mailing list post will contain the BIAP up to and including this section. +Its purpose is to provide a high-level summary to users who are not interested +in detailed technical discussion, but may have opinions around, e.g., usage and +impact.

+
+
+

Detailed description

+

This section should provide a detailed description of the proposed change. +It should include examples of how the new functionality would be used, +intended use-cases and pseudo-code illustrating its use.

+
+ +
+

Implementation

+

This section lists the major steps required to implement the BIAP. Where +possible, it should be noted where one step is dependent on another, and which +steps may be optionally omitted. Where it makes sense, each step should +include a link to related pull requests as the implementation progresses.

+

Any pull requests or development branches containing work on this BIAP should +be linked to from here. (A BIAP does not need to be implemented in a single +pull request if it makes sense to implement it in discrete phases).

+
+
+

Alternatives

+

If there were any alternative solutions to solving the same problem, they should +be discussed here, along with a justification for the chosen approach.

+
+
+

Discussion

+

This section may just be a bullet list including links to any discussions +regarding the BIAP:

+
    +
  • This includes links to mailing list threads or relevant GitHub issues.

  • +
+
+
+ + +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/devel/biaps/index.html b/devel/biaps/index.html new file mode 100644 index 0000000000..7ef5f15f0e --- /dev/null +++ b/devel/biaps/index.html @@ -0,0 +1,129 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + + +
+
+ +
+
+

NiBabel

+

Access a cacophony of neuro-imaging file formats

+
+
+ + + + + + + + \ No newline at end of file diff --git a/devel/bv_formats.html b/devel/bv_formats.html new file mode 100644 index 0000000000..97fc90b38d --- /dev/null +++ b/devel/bv_formats.html @@ -0,0 +1,197 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
+
+ +
+
+

NiBabel

+

Access a cacophony of neuro-imaging file formats

+
+
+ + + + +
+
+
+
+ +
+

BrainVoyager file formats

+

With notes on nibabel support.

+

PR for some BrainVoyager support at https://github.com/nipy/nibabel/pull/216.

+
+

Overview

+

See :

+
    +
  • All files are little-endian byte order regardless of byte-order on the +machine writing the data;

  • +
  • BV apparently provides a “BVQXtools” library for reading writing BV files in +MATLAB;

  • +
+
+
+

BV internal format axes

+

BV files have a internal format that has axes named X, Y and Z. Quoting +from the VMR format definition:

+
BV X front -> back = Y in Tal space
+BV Y top -> bottom = Z in Tal space
+BV Z left -> right = X in Tal space
+
+
+

Put another way — the correspondence of BV XYZ to Talairach axes is:

+
    +
  • BV X -> Anterior to Posterior;

  • +
  • BV Y -> Superior to Inferior;

  • +
  • BV Z -> Left to Right.

  • +
+

or:

+
    +
  • BV X -> Talairach -Y;

  • +
  • BV Y -> Talairach -Z;

  • +
  • BV Z -> Talairach X;

  • +
+

Nice!

+
+
+

Types of BV files

+

There appear to be 38 BV file types at the time of writing of which 18 appear +to have a page of description on the BV file format index page.

+

Here are some examples of BV formats:

+
    +
  • FMR — “FMR project files are simple text files containing the information +defining a functional project created from raw MRI data”. This text file +contains meta-data about the functional time course data, stored in one or +more STC files. See the FMR format definition.

  • +
  • STC — “A STC file (STC = “slice time course”) contains the functional +data (time series) of a FMR project.” The time-course data of a 4D +(“single-slice”) format STC file are stored on disk in +fastest-to-slowest-changing order: columns, rows, time, slice. STC files +can also contain the data for one single slice (“multi-slice format”), in +which case the data are in fast-to-slow order: columns, rows, time. This is +a raw data file where the relevant meta-data such as image size come from an +associated FMR format file. See STC format definition;

  • +
  • VTC — “A VTC file contains the functional data (time series) of one +experimental run (one functional scan) in the space of a 3D anatomical data +set (VMR), e.g. in Talairach space.”. See VTC format definition; +This is a different format to the STC (raw data in native-space) format. +The file is a header followed by ints or floats in +fastest-to-slowest-changing order of: time; BV X; BV Y; BV Z; where BV X, BV +Y, BV Z refer to the BV internal format axes, and therefore Talairach -Y, +-Z, X.

  • +
  • NR-VMP — “A native resolution volume map (NR-VMP) file contains +statistical results in 3D format.”. See NR-VMP format definition

  • +
  • AR-VMP — “An anatomical-resolution VMP (volume map) file contains +statistical results in 3D format” at anatomical scan resolution. See +AR-VMP format definition;

  • +
  • VMR — ‘high-resolution anatomical MR’ - see VMR format definition.

  • +
  • MSK — mask file. Only documentation appears to be +http://www.brainvoyager.com/ubb/Forum8/HTML/000087.html

  • +
  • SMP — ‘surface map’. See SMP format definition. Contains one or more +“maps”, where a map is a NrOfVertices (number of vertices) length vector +of float64 values.

  • +
+
+
+ + +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/devel/core_developer.html b/devel/core_developer.html new file mode 100644 index 0000000000..2a5fb12c8c --- /dev/null +++ b/devel/core_developer.html @@ -0,0 +1,256 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
+
+ +
+
+

NiBabel

+

Access a cacophony of neuro-imaging file formats

+
+
+ + + + +
+
+
+
+ +
+

Core Developer Guide

+

As a core developer, you should continue making pull requests +in accordance with the NiBabel Developer Guidelines. +You are responsible for shepherding other contributors through the review process. +You also have the ability to merge or approve other contributors’ pull requests.

+
+

Reviewing

+
+

How to Conduct A Good Review

+

Always be kind to contributors. Nearly all of Nibabel is +volunteer work, for which we are tremendously grateful. Provide +constructive criticism on ideas and implementations, and remind +yourself of how it felt when your own work was being evaluated as a +novice.

+

Nibabel strongly values mentorship in code review. New users +often need more handholding, having little to no git +experience. Repeat yourself liberally, and, if you don’t recognize a +contributor, point them to our development guide, or other GitHub +workflow tutorials around the web. Do not assume that they know how +GitHub works (e.g., many don’t realize that adding a commit +automatically updates a pull request). Gentle, polite, kind +encouragement can make the difference between a new core developer and +an abandoned pull request.

+

When reviewing, focus on the following:

+
    +
  1. API: The API is what users see when they first use +Nibabel. APIs are difficult to change once released, so +should be simple, consistent with other parts of the library, and +should avoid side-effects such as changing global state or modifying +input variables.

  2. +
  3. Documentation: Any new feature should have a tutorial +example that not only illustrates but explains it.

  4. +
  5. The algorithm: You should understand the code being modified or +added before approving it. (See Merge Only Changes You +Understand below.) Implementations should do what they claim, +and be simple, readable, and efficient.

  6. +
  7. Tests: All contributions to the library must be tested, and +each added line of code should be covered by at least one test. Good +tests not only execute the code, but explore corner cases. It is tempting +not to review tests, but please do so.

  8. +
+

Other changes may be nitpicky: spelling mistakes, formatting, +etc. Do not ask contributors to make these changes, and instead +make the changes by pushing to their branch, +or using GitHub’s suggestion +feature. +(The latter is preferred because it gives the contributor a choice in +whether to accept the changes.)

+

Please add a note to a pull request after you push new changes; GitHub +may not send out notifications for these.

+
+
+

Merge Only Changes You Understand

+

Long-term maintainability is an important concern. Code doesn’t +merely have to work, but should be understood by multiple core +developers. Changes will have to be made in the future, and the +original contributor may have moved on.

+

Therefore, do not merge a code change unless you understand it. Ask +for help freely: we have a long history of consulting community +members, or even external developers, for added insight where needed, +and see this as a great learning opportunity.

+

While we collectively “own” any patches (and bugs!) that become part +of the code base, you are vouching for changes you merge. Please take +that responsibility seriously.

+
+
+
+

Closing issues and pull requests

+

Sometimes, an issue must be closed that was not fully resolved. This can be +for a number of reasons:

+
    +
  • the person behind the original post has not responded to calls for +clarification, and none of the core developers have been able to reproduce +their issue;

  • +
  • fixing the issue is difficult, and it is deemed too niche a use case to +devote sustained effort or prioritize over other issues; or

  • +
  • the use case or feature request is something that core developers feel +does not belong in Nibabel,

  • +
+

among others. Similarly, pull requests sometimes need to be closed without +merging, because:

+
    +
  • the pull request implements a niche feature that we consider not worth the +added maintenance burden;

  • +
  • the pull request implements a useful feature, but requires significant +effort to bring up to Nibabel’s standards, and the original +contributor has moved on, and no other developer can be found to make the +necessary changes; or

  • +
  • the pull request makes changes that make maintenance harder, such as +increasing the code complexity of a function significantly to implement a +marginal speedup,

  • +
+

among others.

+

All these may be valid reasons for closing, but we must be wary not to alienate +contributors by closing an issue or pull request without an explanation. When +closing, your message should:

+
    +
  • explain clearly how the decision was made to close. This is particularly +important when the decision was made in a community meeting, which does not +have as visible a record as the comments thread on the issue itself;

  • +
  • thank the contributor(s) for their work; and

  • +
  • provide a clear path for the contributor or anyone else to appeal the +decision.

  • +
+

These points help ensure that all contributors feel welcome and empowered to +keep contributing, regardless of the outcome of past contributions.

+
+
+

Further resources

+

As a core member, you should be familiar with community and developer +resources such as:

+ +

Please do monitor any of the resources above that you find helpful.

+
+
+
+

Acknowledgments

+

This document is based on the NetworkX Core Developer guide.

+
+ + +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/devel/data_pkg_discuss.html b/devel/data_pkg_discuss.html new file mode 100644 index 0000000000..c84998490a --- /dev/null +++ b/devel/data_pkg_discuss.html @@ -0,0 +1,466 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + +
+
+ +
+
+

NiBabel

+

Access a cacophony of neuro-imaging file formats

+
+
+ + + + +
+
+
+
+ +
+

Principles of data package

+
+

Summary

+

This is a discussion of data packages, as they are currently implemented in +nibabel / nipy.

+

This API proved to be very uncomfortable, and we intend to replace it fairly +soon. See data_packages.rst in the nibabel wiki for our current +thinking, not yet implemented.

+
+
+

Motivation

+

When developing or using nipy, many data files can be useful.

+
    +
  1. small test data - very small data files required for routine code testing. +By small we mean less than 100K, and probably much less. They have to be +small because we keep them in the main code repository, and you therefore +always get them with any download.

  2. +
  3. large test data. These files can be much larger, and don’t come in the +standard repository. We use them for tests, but we skip the tests if the +data are not present.

  4. +
  5. template data - data files required for some algorithms to function, +such as templates or atlases

  6. +
  7. example data - data files for running examples.

  8. +
+

We need some standard way to provide the larger data sets. To do this, we are +here defining the idea of a data package. This document is a draft +specification of what a data package looks like and how to use it.

+
+
+

Separation of ideas

+

This section needs some healthy beating to make the ideas clearer. However, in +the interests of the 0SAGA software model, here are some ideas that may be +separable.

+
+

Package

+

This idea is rather difficult to define, but is a bit like a data project, that +is a set of information that the packager believed had something in common. The +package then is an abstract idea, and what is in the package could change +completely over course of the life of the package. The package then is a little +bit like a namespace, having itself no content other than a string (the package +name) and the data it contains.

+
+
+

Package name

+

This is a string that gives a name to the package.

+
+
+

Package instantiation

+

By instantiation we mean some particular actual set of data for a particular +package. By actual, we mean stuff that can be read as bytes. As we add and +remove data from the package, the instantiation changes. In version control, +the instantiation would be the particular state of the working tree at any +moment, whether this has been committed or not.

+

It might not be enjoyable, but we’ll call a package instantiation a pinstance.

+
+
+

Pinstance revision

+

A revision is an instantiation of the working tree that has a unique label - the +revision id.

+
+
+

Pinstance revision id

+

The revision id is a string that identifies a particular pinstance. This is +the equivalent of the revision number in subversion, or the commit hash in +systems like git or mercurial. There is only one pinstance for any given +revision id, but there can be more than one revision id for a pinstance. For +example, you might have a revision of id ‘200’, delete a file, restore the file, +call this revision id ‘201’, but they might both refer to the same instantiation +of the package. Or they might not, that’s up to you, the author of the package.

+
+
+

Pinstance tag

+

A tag is a memorable string that refers to a particular pinstance. It differs +from a revision id only in that there is not likely to be a tag for every +revision. It’s possible to imagine pinstances without a revision id but with a +tag, but perhaps it’s reasonable to restrict tags to refer to revisions. A +tag is equivalent to a tag name in git or mercurial - a memorable string that +refers to a static state of the data. An example might be a numbered version. +So, a package may have a revision uniquely identified by a revision id +af5bd6. We might decide to label this revision release-0.3 (the +equivalent of applying a git tag). release-0.3 is the tag and af5bd6 is +the revision id. Different sources of the same package might possibly produce +different tags [1]

+
+
+

Pinstance version

+

A pinstance might also have a version. A version is just a tag that can be +compared using some algorithm.

+
+
+

Package provider bundle

+

Maybe we could call this a “prundle”.

+

The provider bundle is something that can deliver the bytes of a particular +pinstance. For example, if you have a package named “interesting-images”, you +might have a revision of that package identified by revision id “f745dc2” and +tagged with “version-0.2”. There might be a provider bundle of that +instantiation that is a zipfile interesting-images-version-0.2.zip. There +might also be a directory on an http server with the same contents +http://my.server.org/packages/interesting-images/version-9.2. The zipfile +and the http directory would both be provider bundles of the particular +instantiation. When I unpack the zipfile onto my hard disk, I might have a +directory /my/home/packages/interesting-images/version-0.2. Now this path +is a provider bundle.

+
+
+

Provider bundle format

+

In the example above, the zipfile, the http directory and the local path are +three different provider bundle formats delivering the same package +instantiation. Let’s call those formats:

+
    +
  • zipfile format

  • +
  • url-path format

  • +
  • local-path format

  • +
+
+
+

Pinstance release

+

A release might be a package instantiation that one person has:

+
    +
  1. tagged

  2. +
  3. made available as one or more provider bundles

  4. +
+
+
+

Prundle discovery

+

We discover a package bundle when we ask a system (local or remote) whether +they have a package bundle at a given revision, tag, or bundle format. That +implies two discoveries - local discovery (is the package bundle on my local +system, if so where is it?); and remote discovery (is the package bundle on +your expensive server and if so, how do I get it?). For the Debian +distributions, the sources.list file identifies sources from which we can +query for software packages. Those would be sources for remote discovery in +our language.

+
+
+

Prundle discovery source

+

A prundle discovery source is somewhere that can answer prundle discovery +queries.

+

One such thing might be a prundle registry, where an element in the registry +contains information about a particular prundle. At a first pass this might +contain:

+
    +
  • package name

  • +
  • bundle format

  • +
  • revision id (optional)

  • +
  • tag (optional)

  • +
+

Maybe it should also contain information about where the information came from.

+
+
+

Pinstance metadata query

+

We query a pinstance when we know that a particular system (local or remote) has +a package bundle of the pinstance we want. Then we get some information about +that pinstance.

+

By definition, different prundles relating to the same pinstance have the same +metadata.

+
+
+

Pinstance metadata query source

+

A pinstance metadata query source is somewhere that can answer pinstance +metadata queries.

+

Obviously a source may well be both a prundle discovery source and a +pinstance metadata query source.

+
+
+

Pinstance installation

+

We install a pinstance when we get some prundle containing the pinstance and +place it on local storage, such that we can discover the prundle on our own +(local) system. That is we take some prundle and convert it to a local-path +format bundle and we register this local-path format bundle to a discovery +source.

+
+
+

Data and metadata

+
+
Pinstance data

is the bytes as they are arranged in a particular pinstance.

+
+
Pinstance metadata

is data about the pinstance. It might include information about what data +is in the package.

+
+
Prundle metadata

Information about the particular prundle format.

+
+
+
+
+
+

Comparative terminology

+

In which we compare the package terminology above to the terminology of Debian +packaging.

+
+

Compared to Debian packaging

+
    +
  • A Debian distribution is a label - such as ‘unstable’ or ‘lenny’ - that refers to a +set of package revisions that go together. We have no equivalent.

  • +
  • A Debian repository is a set of packages within a distribution that go +together - e.g. ‘main’ or ‘contrib’. We probably don’t have an equivalent +(unless we consider Debian’s repository as being like a very large package +in our language).

  • +
  • A Debian source is a URI giving a location from which you can collect one or +more repositories. For example, the line: “http://www.example.com/packages +stable main contrib” in a “sources.list” file refers to the source +“http://www.example.com/packages” providing distribution “stable” and +repositories (within stable) of “main” and “contrib”. In our language the +combination of URI, distribution and repository would refer to a prundle +discovery source - that is - something that will answer queries about +bundles.

  • +
  • package probably means the same for us as for Debian - a name - like +“python-numpy” - that refers to a set of files that go together and should be +installed together.

  • +
  • Debian packages have versions to reflect the different byte contents. For +example there might be a .deb file (see below) “some-package-0.11_3-i386.deb” +for one distribution, and another (with different contents) for another +distribution - say “some-package-0.12_9-i386.deb”. The “0.11_3” and “0.12_9” +parts of the deb filename are what we would call package instantiation tags.

  • +
  • A Debian deb file is an archive in a particular format that unpacks to provide +the files for a particular package version. We’d call the deb file a package +bundle, that is in bundle format “deb-format”.

  • +
+
+
+
+

Desiderata

+

We want to build a package system that is very simple (‘S’ in 0SAGA). For the +moment, the main problems we want to solve are: creation of a package +instantiation, installation of package instantiations, local discovery of +package instantiations. For now we are not going to try and solve queries.

+

At least local discovery should be so simple that it can be implemented in any +language, and should not require a particular tool to be installed. We hope we +can write a spec that makes all of (creation, installation, local discovery) +clearly defined, so that it would be simple to write an implementation. +Obviously we’re going to end up writing our own implementation, or adapting +someone else’s. datapkg looks like the best candidate at the moment.

+
+
+

Issues

+

From a brief scan of the debian package management documentation.

+
+

Dependency management

+

(no plan at the moment)

+
+
+

Authentication and validation

+
    +
  • Authentication - using signatures to confirm that you made this package.

  • +
  • Verification - verify that the contents have not been corrupted or changed +since the original instantiation.

  • +
+

For dependency and validation, see the Debian secure apt page. One related +proposal would be:

+
    +
  • Each package instantiation would carry a table of checksums for the files +within. Someone using this instantiation would check the checksums to confirm +that they had the intended content.

  • +
  • Authentication would involve some kind of signing of the table of checksums, +as in the Release.gpg file in Debian distributions (Debian secure apt +again). This involves taking a checksum of the table of checksums, then using +our trusted private key to encrypt this checksum, generating a digital +signature. The signature is the thing we provide to the user. The user then +gets our public key or has it already; they use the key to decrypt the +signature to get the checksum, and they check the resulting checksum against +the actual checksum of the checksum table. The property of the public/private +key pair is that it is very hard to go backwards. To explain, here’s an +example. Imagine someone we don’t like has made a version of the package +instantiation, but wants to persuade the world that we made it. Their +contents will have different checksums, and therefore a different checksum for +the checksum table. Let’s say the checksum of the new checksum table is X. +They know that you, the user, will use your own copy of our public key, and +they can’t get at that. Their job then, is to make a new encrypted checksum +(the signature) that will decrypt with our real public key, to equal X. +That’s going backwards from the desired result X to the signature, and that +is very hard, if they don’t have our private key.

  • +
+
+
+
+

Differences from code packages

+

The obvious differences are:

+
    +
  1. Data packages can be very large

  2. +
  3. We have less need for full history tracking (probably)

  4. +
+

The size of data packages probably mean that using git itself will not work +well. git stores (effectively) all previous versions of the files in the +repository, as zlib compressed blobs. The working tree is an uncompressed +instantiation of the current state. Thus, if we have, over time, had 4 +different versions of a large file with little standard diff relationship to one +another, the repository will have four zlib compressed versions of the file in +the .git/objects database, and one uncompressed version in the working tree. +The files in data packages may or may not compress well.

+

In contrast to the full git model, we may want to avoid duplicates of the data. +We probably won’t by default want to keep all previous versions of the data +together at least locally.

+

We probably do want to be able to keep track of which files are the same across +different instantiations of the package, in the case where we already have one +instantiation on local disk, and we are asking for another, with some shared +files. We might well want to avoid downloading duplicate data in that case.

+

Maybe the way to think of it is of the different costs that become important as +files get larger. So the cost for holding a full history becomes very large, +whereas the benefit decreases a little bit (compared to code).

+
+
+

Some usecases

+
+

Discovery

+
from ourpkg import default_registry
+
+my_pkg_path = default_registry.pathfor('mypkg', '0.3')
+if mypkg_path is None:
+    raise RuntimeError('It looks like mypkg version 0.3 is not installed')
+
+
+

Footnotes

+ +
+
+
+ + +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/devel/devdiscuss.html b/devel/devdiscuss.html new file mode 100644 index 0000000000..495fe4a416 --- /dev/null +++ b/devel/devdiscuss.html @@ -0,0 +1,133 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
+
+ +
+
+

NiBabel

+

Access a cacophony of neuro-imaging file formats

+
+
+ + + + +
+
+
+ +
+
+
+
+ + + \ No newline at end of file diff --git a/devel/devguide.html b/devel/devguide.html new file mode 100644 index 0000000000..e9dc4c7a53 --- /dev/null +++ b/devel/devguide.html @@ -0,0 +1,312 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
+
+ +
+
+

NiBabel

+

Access a cacophony of neuro-imaging file formats

+
+
+ + + + +
+
+
+
+ +
+

NiBabel Developer Guidelines

+

Also see Developer documentation page

+
+

NiBabel source code

+ +
+
+

Documentation

+
+

Code Documentation

+

Please write documentation using Numpy documentation conventions:

+
+
+
+
+
+

Git Repository

+
+

Layout

+

The main release branch is called master. This is a merge-only branch. +Features finished or updated by some developer are merged from the +corresponding branch into master. At a certain point the current state of +master is tagged – a release is done.

+

Only usable feature should end-up in master. Ideally master should be +releasable at all times.

+

Additionally, there are distribution branches. They are prefixed dist/ +and labeled after the packaging target (e.g. debian for a Debian package). +If necessary, there can be multiple branches for each distribution target.

+
+
dist/debian/proper

Official Debian packaging

+
+
dist/debian/dev

Debian packaging of unofficial development snapshots. They do not go into the +main Debian archive, but might be distributed through other channels (e.g. +NeuroDebian).

+
+
+

Releases are merged into the packaging branches, packaging is updated if +necessary and the branch gets tagged when a package version is released. +Maintenance (as well as backport) releases or branches off from the respective +packaging tag.

+

There might be additional branches for each developer, prefixed with initials. +Alternatively, several GitHub (or elsewhere) clones might be used.

+
+
+

Commits

+

Please prefix all commit summaries with one (or more) of the following labels. +This should help others to easily classify the commits into meaningful +categories:

+
+
    +
  • BF : bug fix

  • +
  • RF : refactoring

  • +
  • NF : new feature

  • +
  • BW : addresses backward-compatibility

  • +
  • OPT : optimization

  • +
  • BK : breaks something and/or tests fail

  • +
  • PL : making pylint happier

  • +
  • DOC: for all kinds of documentation related commits

  • +
  • TEST: for adding or changing tests

  • +
+
+
+
+

Merges

+

For easy tracking of what changes were absorbed during merge, we +advise that you enable merge summaries within git:

+
+

git-config merge.summary true

+
+

See Configure git for more detail.

+
+
+
+

Testing

+

NiBabel uses tox to organize our testing and development workflows. +tox runs tests in isolated environments that we specify, +ensuring that we are able to test across many different environments, +and those environments do not depend on our local configurations.

+

If you have the pipx tool installed, then you may simply:

+
pipx run tox
+
+
+

Alternatively, you can install tox and run it:

+
python -m pip install tox
+tox
+
+
+

This will run the tests in several configurations, with multiple sets of +optional dependencies. +If you have multiple versions of Python installed in your path, it will +repeat the process for each version of Python iin our supported range. +It may be useful to pick a particular version for rapid development:

+
tox -e py311-full-x64
+
+
+

This will run the environment using the Python 3.11 interpreter, with the +full set of optional dependencies that are available for 64-bit +interpreters. If you are using 32-bit Python, replace -x64 with -x86.

+
+
+

Style guide

+

To ensure code consistency and readability, NiBabel has adopted the following +tools:

+
    +
  • blue - An auto-formatter that aims to reduce diffs to relevant lines

  • +
  • isort_ - An import sorter that groups stdlib, third-party and local imports.

  • +
  • flake8 - A style checker that can catch (but generally not fix) common +errors in code.

  • +
  • codespell - A spell checker targeted at source code.

  • +
  • pre-commit_ - A pre-commit hook manager that runs the above and various +other checks/fixes.

  • +
+

While some amount of personal preference is involved in selecting and +configuring auto-formatters, their value lies in largely eliminating the +need to think or argue about style. +With pre-commit turned on, you can write in the style that works for you, +and the NiBabel style will be adopted prior to the commit.

+

To apply our style checks uniformly, simply run:

+
tox -e style,spellcheck
+
+
+

To fix any issues found:

+
tox -e style-fix
+tox -e spellcheck -- -w
+
+
+

Occasionally, codespell has a false positive. To ignore the suggestion, add +the intended word to tool.codespell.ignore-words-list in pyproject.toml. +However, the ignore list is a blunt instrument and could cause a legitimate +misspelling to be missed. Consider choosing a word that does not trigger +codespell before adding it to the ignore list.

+
+

Pre-commit hooks

+

NiBabel uses pre-commit_ to help committers validate their changes +before committing. To enable these, you can use pipx:

+
pipx run pre-commit install
+
+
+

Or install and run:

+
python -m pip install pre-commit
+pre-commit install
+
+
+
+
+
+

Changelog

+

The changelog is located in the toplevel directory of the source tree in the +Changelog file. The content of this file should be formatted as restructured +text to make it easy to put it into manual appendix and on the website.

+

This changelog should neither replicate the VCS commit log nor the +distribution packaging changelogs (e.g. debian/changelog). It should be +focused on the user perspective and is intended to list rather macroscopic +and/or important changes to the module, like feature additions or bugfixes in +the algorithms with implications to the performance or validity of results.

+

It may list references to 3rd party bugtrackers, in case the reported bugs +match the criteria listed above.

+
+
+

Community guidelines

+

Please see our community guidelines. +Other projects call these guidelines the “code of conduct”.

+
+
+ + +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/devel/governance.html b/devel/governance.html new file mode 100644 index 0000000000..cbca05b070 --- /dev/null +++ b/devel/governance.html @@ -0,0 +1,298 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
+
+ +
+
+

NiBabel

+

Access a cacophony of neuro-imaging file formats

+
+
+ + + + +
+
+
+
+ +
+

Governance and Decision Making

+
+

Abstract

+

Nibabel is a consensus-based community project. Anyone with an interest in the +project can join the community, contribute to the project design, and +participate in the decision making process. This document describes how that +participation takes place, how to find consensus, and how deadlocks are +resolved.

+
+
+

Roles And Responsibilities

+
+

The Community

+

The Nibabel community consists of anyone using or working with the project +in any way.

+
+
+

Contributors

+

Any community member can become a contributor by interacting directly with the +project in concrete ways, such as:

+
    +
  • proposing a change to the code or documentation via a GitHub pull request;

  • +
  • reporting issues on our +GitHub issues page;

  • +
  • discussing the design of the library, website, or tutorials on the +mailing list, +or in existing issues and pull requests; or

  • +
  • reviewing +open pull requests,

  • +
+

among other possibilities. By contributing to the project, community members +can directly help to shape its future.

+

Contributors should read the NiBabel Developer Guidelines and our Community guidelines.

+
+
+

Core Developers

+

Core developers are community members that have demonstrated continued +commitment to the project through ongoing contributions. They +have shown they can be trusted to maintain Nibabel with care. Becoming a +core developer allows contributors to merge approved pull requests, cast votes +for and against merging a pull request, and be involved in deciding major +changes to the API, and thereby more easily carry on with their project related +activities.

+

Core developers:

+ + + + + + + + + + + + + + + + + +

Name

GitHub user

Chris Markiewicz

effigies

Matthew Brett

matthew-brett

Oscar Esteban

oesteban

+

Core developers also appear as team members on the Nibabel Core Team page and can +be messaged @nipy/nibabel-core-developers. We expect core developers to +review code contributions while adhering to the Core Developer Guide.

+

New core developers can be nominated by any existing core developer. Discussion +about new core developer nominations is one of the few activities that takes +place on the project’s private management list. The decision to invite a new +core developer must be made by “lazy consensus”, meaning unanimous agreement by +all responding existing core developers. Invitation must take place at least +one week after initial nomination, to allow existing members time to voice any +objections.

+
+
+

Steering Council

+

The Steering Council (SC) members are current or former core developers who +have additional responsibilities to ensure the smooth running of the project. +SC members are expected to participate in strategic planning, approve changes +to the governance model, and make decisions about funding granted to the +project itself. (Funding to community members is theirs to pursue and manage.) +The purpose of the SC is to ensure smooth progress from the big-picture +perspective. Changes that impact the full project require analysis informed by +long experience with both the project and the larger ecosystem. When the core +developer community (including the SC members) fails to reach such a consensus +in a reasonable timeframe, the SC is the entity that resolves the issue.

+

The steering council is:

+ + + + + + + + + + + + + + + + + + + + +

Name

GitHub user

Chris Markiewicz

effigies

Matthew Brett

matthew-brett

Michael Hanke

mih

Yaroslav Halchenko

yarikoptic

+

Steering Council members also appear as team members on the Nibabel Steering +Council Team page and +can be messaged @nipy/nibabel-steering-council.

+
+
+
+

Decision Making Process

+

Decisions about the future of the project are made through discussion with all +members of the community. All non-sensitive project management discussion takes +place on the project +mailing list +and the issue tracker. +Occasionally, sensitive discussion may occur on a private list.

+

Decisions should be made in accordance with our Community guidelines.

+

Nibabel uses a consensus seeking process for making decisions. The group +tries to find a resolution that has no open objections among core developers. +Core developers are expected to distinguish between fundamental objections to a +proposal and minor perceived flaws that they can live with, and not hold up the +decision making process for the latter. If no option can be found without +an objection, the decision is escalated to the SC, which will itself use +consensus seeking to come to a resolution. In the unlikely event that there is +still a deadlock, the proposal will move forward if it has the support of a +simple majority of the SC. Any proposal must be described by a Nibabel Enhancement Proposals (BIAPs).

+

Decisions (in addition to adding core developers and SC membership as above) +are made according to the following rules:

+
    +
  • Minor documentation changes, such as typo fixes, or addition / correction +of a sentence (but no change of the Nibabel landing page or the “about” +page), require approval by a core developer and no disagreement or +requested changes by a core developer on the issue or pull request page (lazy +consensus). We expect core developers to give “reasonable time” to others to +give their opinion on the pull request if they’re not confident others would +agree.

  • +
  • Code changes and major documentation changes require agreement by one +core developer and no disagreement or requested changes by a core developer +on the issue or pull-request page (lazy consensus).

  • +
  • Changes to the API principles require a Enhancement Proposals (BIAPs) and follow the +decision-making process outlined above.

  • +
  • Changes to this governance model or our mission and values require +a Enhancement Proposals (BIAPs) and follow the decision-making process outlined above, unless +there is unanimous agreement from core developers on the change.

  • +
+

If an objection is raised on a lazy consensus, the proposer can appeal to the +community and core developers and the change can be approved or rejected by +escalating to the SC, and if necessary, a BIAP (see below).

+
+
+

Enhancement Proposals (BIAPs)

+

Any proposals for enhancements of Nibabel should be written as a formal BIAP +following the template BIAP X — Template and Instructions. The BIAP must be made public and +discussed before any vote is taken. The discussion must be summarized by a key +advocate of the proposal in the appropriate section of the BIAP. Once this +summary is made public and after sufficient time to allow the core team to +understand it, they vote.

+

The workflow of a BIAP is detailed in BIAP 0 - Purpose and process.

+

A list of all existing BIAPs is available here.

+
+
+

Acknowledgments

+

Many thanks to Jarrod Millman, Dan Schult and the Scikit-Image team for the +draft on which we based this document.

+
+
+ + +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/devel/image_design.html b/devel/image_design.html new file mode 100644 index 0000000000..be0384231e --- /dev/null +++ b/devel/image_design.html @@ -0,0 +1,92 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + +
+
+ +
+
+

NiBabel

+

Access a cacophony of neuro-imaging file formats

+
+
+ + + + +
+
+
+
+ +
+

The nibabel image object

+

The latest version of this page is now at Nibabel images.

+
+ + +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/devel/index.html b/devel/index.html new file mode 100644 index 0000000000..cfca72e621 --- /dev/null +++ b/devel/index.html @@ -0,0 +1,188 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + + +
+
+ +
+
+

NiBabel

+

Access a cacophony of neuro-imaging file formats

+
+
+ + + + + + + + \ No newline at end of file diff --git a/devel/make_release.html b/devel/make_release.html new file mode 100644 index 0000000000..e225393a51 --- /dev/null +++ b/devel/make_release.html @@ -0,0 +1,425 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
+
+ +
+
+

NiBabel

+

Access a cacophony of neuro-imaging file formats

+
+
+ + + + +
+
+
+
+ +
+

A guide to making a nibabel release

+

This is a guide for developers who are doing a nibabel release.

+

The general idea of these instructions is to go through the following steps:

+
    +
  • Make sure that the code is in the right state for release;

  • +
  • update release-related docs such as the Changelog;

  • +
  • update various documents giving dependencies, dates and so on;

  • +
  • check all standard and release-specific tests pass;

  • +
  • make the release commit and release tag;

  • +
  • check Windows binary builds and slow / big memory tests;

  • +
  • push source and windows builds to pypi;

  • +
  • push docs;

  • +
  • push release commit and tag to github;

  • +
  • announce.

  • +
+

We leave pushing the tag to the last possible moment, because it’s very bad +practice to change a git tag once it has reached the public servers (in our +case, github). So we want to make sure of the contents of the release before +pushing the tag.

+
+

Release checklist

+
    +
  • Review the open list of nibabel issues. Check whether there are +outstanding issues that can be closed, and whether there are any issues that +should delay the release. Label them !

  • +
  • Review and update the release notes. Review and update the Changelog +file. Get a partial list of contributors with something like:

    +
    git log 2.0.0.. | grep '^Author' | cut -d' ' -f 2- | sort | uniq
    +
    +
    +

    where 2.0.0 was the last release tag name.

    +

    Then manually go over git shortlog 2.0.0.. to make sure the release +notes are as complete as possible and that every contributor was recognized.

    +
  • +
  • Look at doc/source/index.rst and add any authors not yet acknowledged. +You might want to use the following to list authors by the date of their +contributions:

    +
    git log --format="%aN <%aE>" --reverse | perl -e 'my %dedupe; while (<STDIN>) { print unless $dedupe{$_}++}'
    +
    +
    +

    (From: +http://stackoverflow.com/questions/6482436/list-of-authors-in-git-since-a-given-commit#6482473)

    +

    Consider any updates to the AUTHOR file.

    +
  • +
  • Use the opportunity to update the .mailmap file if there are any +duplicate authors listed from git shortlog -nse.

  • +
  • Check the copyright year in doc/source/conf.py

  • +
  • Refresh the README.rst text from the LONG_DESCRIPTION in info.py +by running make refresh-readme.

    +

    Check the output of:

    +
    rst2html.py README.rst > ~/tmp/readme.html
    +
    +
    +

    because this will be the output used by pypi

    +
  • +
  • Check the dependencies listed in nibabel/info.py (e.g. +NUMPY_MIN_VERSION) and in doc/source/installation.rst and in +requirements.txt and .travis.yml. They should at least match. Do +they still hold? Make sure nibabel on travis is testing the minimum +dependencies specifically.

  • +
  • Do a final check on the nipy buildbot. Use the try_branch.py +scheduler available in nibotmi to test particular schedulers.

  • +
  • Make sure all tests pass (from the nibabel root directory):

    +
    pytest --doctest-modules nibabel
    +
    +
    +
  • +
  • Make sure you are set up to use the try_branch.py - see +https://github.com/nipy/nibotmi/blob/master/install.rst#trying-a-set-of-changes-on-the-buildbots

  • +
  • Make sure all your changes are committed or removed, because +try_branch.py pushes up the changes in the working tree;

  • +
  • The following checks get run with the nibabel-release-checks, as in:

    +
    try_branch.py nibabel-release-checks
    +
    +
    +

    Beware: this build does not usually error, even if the steps do not give the +expected output. You need to check the output manually by going to +https://nipy.bic.berkeley.edu/builders/nibabel-release-checks after the +build has finished.

    +
      +
    • Make sure all tests pass from sdist:

      +
      make sdist-tests
      +
      +
      +

      and the three ways of installing (from tarball, repo, local in repo):

      +
      make check-version-info
      +
      +
      +

      The last may not raise any errors, but you should detect in the output +lines of this form:

      +
      {'sys_version': '2.6.6 (r266:84374, Aug 31 2010, 11:00:51) \n[GCC 4.0.1 (Apple Inc. build 5493)]', 'commit_source': 'archive substitution', 'np_version': '1.5.0', 'commit_hash': '25b4125', 'pkg_path': '/var/folders/jg/jgfZ12ZXHwGSFKD85xLpLk+++TI/-Tmp-/tmpGPiD3E/pylib/nibabel', 'sys_executable': '/Library/Frameworks/Python.framework/Versions/2.6/Resources/Python.app/Contents/MacOS/Python', 'sys_platform': 'darwin'}
      +/var/folders/jg/jgfZ12ZXHwGSFKD85xLpLk+++TI/-Tmp-/tmpGPiD3E/pylib/nibabel/__init__.pyc
      +{'sys_version': '2.6.6 (r266:84374, Aug 31 2010, 11:00:51) \n[GCC 4.0.1 (Apple Inc. build 5493)]', 'commit_source': 'installation', 'np_version': '1.5.0', 'commit_hash': '25b4125', 'pkg_path': '/var/folders/jg/jgfZ12ZXHwGSFKD85xLpLk+++TI/-Tmp-/tmpGPiD3E/pylib/nibabel', 'sys_executable': '/Library/Frameworks/Python.framework/Versions/2.6/Resources/Python.app/Contents/MacOS/Python', 'sys_platform': 'darwin'}
      +/Users/mb312/dev_trees/nibabel/nibabel/__init__.pyc
      +{'sys_version': '2.6.6 (r266:84374, Aug 31 2010, 11:00:51) \n[GCC 4.0.1 (Apple Inc. build 5493)]', 'commit_source': 'repository', 'np_version': '1.5.0', 'commit_hash': '25b4125', 'pkg_path': '/Users/mb312/dev_trees/nibabel/nibabel', 'sys_executable': '/Library/Frameworks/Python.framework/Versions/2.6/Resources/Python.app/Contents/MacOS/Python', 'sys_platform': 'darwin'}
      +
      +
      +
    • +
    • Check the setup.py file is picking up all the library code and scripts, +with:

      +
      make check-files
      +
      +
      +

      Look for output at the end about missed files, such as:

      +
      Missed script files:  /Users/mb312/dev_trees/nibabel/bin/nib-dicomfs, /Users/mb312/dev_trees/nibabel/bin/nifti1_diagnose.py
      +
      +
      +

      Fix setup.py to carry across any files that should be in the +distribution.

      +
    • +
    • Check the documentation doctests:

      +
      make -C doc doctest
      +
      +
      +

      This should also be tested by nibabel on travis.

      +
    • +
    • Check everything compiles without syntax errors:

      +
      python -m compileall .
      +
      +
      +
    • +
    • Check that nibabel correctly generates a source distribution:

      +
      make source-release
      +
      +
      +
    • +
    +
  • +
  • Edit nibabel/info.py to set _version_extra to ''; commit;

  • +
  • You may have virtualenvs for different Python versions. Check the tests +pass for different configurations. The long-hand way looks like this:

    +
    workon python26
    +make distclean
    +make sdist-tests
    +deactivate
    +
    +
    +

    etc for the different virtualenvs;

    +
  • +
  • Check on different platforms, particularly windows and PPC. Look at the +nipy buildbot automated test runs for this;

  • +
  • Force build of your release candidate branch with the slow and big-memory +tests on the zibi buildslave:

    +
    try_branch.py nibabel-py2.7-osx-10.10
    +
    +
    +

    Check the build web-page for errors:

    + +
  • +
  • Force builds of your local branch on the win32 and amd64 binaries on +buildbot:

    +
    try_branch.py nibabel-bdist32-27
    +try_branch.py nibabel-bdist32-33
    +try_branch.py nibabel-bdist32-34
    +try_branch.py nibabel-bdist32-35
    +try_branch.py nibabel-bdist64-27
    +
    +
    +

    Check the builds completed without error on their respective web-pages:

    + +
  • +
  • Make sure you have travis-ci building set up for your own repo. Make a new +release-check (or similar) branch, and push the code in its current +state to a branch that will build, e.g:

    +
    git branch -D release-check # in case branch already exists
    +git co -b release-check
    +# You might need the --force flag here
    +git push your-github-user release-check -u
    +
    +
    +
  • +
  • Once everything looks good, you are ready to upload the source release to +PyPi. See setuptools intro. Make sure you have a file +\$HOME/.pypirc, of form:

    +
    [distutils]
    +index-servers =
    +    pypi
    +    warehouse
    +
    +[pypi]
    +username:your.pypi.username
    +password:your-password
    +
    +[warehouse]
    +repository: https://upload.pypi.io/legacy/
    +username:your.pypi.username
    +password:your-password
    +
    +
    +
  • +
  • Clean:

    +
    make distclean
    +# Check no files outside version control that you want to keep
    +git status
    +# Nuke
    +git clean -fxd
    +
    +
    +
  • +
  • When ready:

    +
    python setup.py register
    +python setup.py sdist --formats=gztar,zip
    +# -s flag to sign the release
    +twine upload -r warehouse -s dist/nibabel*
    +
    +
    +
  • +
  • Tag the release with signed tag of form 2.0.0:

    +
    git tag -s 2.0.0
    +
    +
    +
  • +
  • Push the tag and any other changes to trunk with:

    +
    git push origin 2.0.0
    +git push
    +
    +
    +
  • +
  • Now the version number is OK, push the docs to github pages with:

    +
    make upload-html
    +
    +
    +
  • +
  • Finally (for the release uploads) upload the Windows binaries you built with +try_branch.py above;

  • +
  • Set up maintenance / development branches

    +

    If this is this is a full release you need to set up two branches, one for +further substantial development (often called ‘trunk’) and another for +maintenance releases.

    +
      +
    • Branch to maintenance:

      +
      git co -b maint/2.0.x
      +
      +
      +

      Set _version_extra back to .dev and bump _version_micro by 1. +Thus the maintenance series will have version numbers like - say - +‘2.0.1.dev’ until the next maintenance release - say ‘2.0.1’. Commit. +Don’t forget to push upstream with something like:

      +
      git push upstream-remote maint/2.0.x --set-upstream
      +
      +
      +
    • +
    • Start next development series:

      +
      git co main-master
      +
      +
      +

      then restore .dev to _version_extra, and bump _version_minor +by 1. Thus the development series (‘trunk’) will have a version number +here of ‘2.1.0.dev’ and the next full release will be ‘2.1.0’.

      +

      Next merge the maintenance branch with the “ours” strategy. This just +labels the maintenance info.py edits as seen but discarded, so we can +merge from maintenance in future without getting spurious merge conflicts:

      +
      git merge -s ours maint/2.0.x
      +
      +
      +
    • +
    +

    If this is just a maintenance release from maint/2.0.x or similar, just +tag and set the version number to - say - 2.0.2.dev.

    +
  • +
  • Push the main branch:

    +
    git push upstream-remote main-master
    +
    +
    +
  • +
  • Make next development release tag

    +
    +

    After each release the master branch should be tagged +with an annotated (or/and signed) tag, naming the intended +next version, plus an ‘upstream/’ prefix and ‘dev’ suffix. +For example ‘upstream/1.0.0.dev’ means “development start +for upcoming version 1.0.0.

    +

    This tag is used in the Makefile rules to create development snapshot +releases to create proper versions for those. The version derives its name +from the last available annotated tag, the number of commits since that, +and an abbreviated SHA1. See the docs of git describe for more info.

    +

    Please take a look at the Makefile rules devel-src, devel-dsc and +orig-src.

    +
    +
  • +
  • Go to: https://github.com/nipy/nibabel/tags and select the new tag, to fill +in the release notes. Copy the relevant part of the Changelog into the +release notes. Click on “Publish release”. This will cause Zenodo to +generate a new release “upload”, including a DOI. After a few minutes, go +to https://zenodo.org/deposit and click on the new release upload. Click on +the “View” button and click on the DOI badge at the right to display the +text for adding a DOI badge in various formats. Copy the DOI Markdown text. +The markdown will look something like this:

    +
    [![DOI](https://zenodo.org/badge/doi/10.5281/zenodo.60847.svg)](https://doi.org/10.5281/zenodo.60847)
    +
    +
    +

    Go back to the Github release page for this release, click “Edit release”. +and copy the DOI into the release notes. Click “Update release”.

    +

    See: https://guides.github.com/activities/citable-code

    +
  • +
  • Announce to the mailing lists.

  • +
+
+
+ + +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/devel/modified_images.html b/devel/modified_images.html new file mode 100644 index 0000000000..7081c39b66 --- /dev/null +++ b/devel/modified_images.html @@ -0,0 +1,224 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
+
+ +
+
+

NiBabel

+

Access a cacophony of neuro-imaging file formats

+
+
+ + + + +
+
+
+
+ +
+

Keeping track of whether images have been modified since load

+
+

Summary

+

This is a discussion of a missing feature in nibabel: the ability to keep +track of whether an image object in memory still corresponds to an image file +(or files) on disk.

+
+
+

Motivation

+

We may need to know whether the image in memory corresponds to the image file +on disk.

+

For example, we often need to get filenames for images when passing +images to external programs. Imagine a realignment, in this case, in nipy +(the package):

+
import nipy
+img1 = nibabel.load('meanfunctional.nii')
+img2 = nibabel.load('anatomical.nii')
+realigner = nipy.interfaces.fsl.flirt()
+params = realigner.run(source=img1, target=img2)
+
+
+

In nipy.interfaces.fsl.flirt.run there may at some point be calls like:

+
source_filename = nipy.as_filename(source_img)
+target_filename = nipy.as_filename(target_img)
+
+
+

As the authors of the flirt.run method, we need to make sure that the +source_filename corresponds to the source_img.

+

Of course, in the general case, if source_img has no corresponding +filename (from source_img.get_filename(), then we will have to save a copy +to disk, maybe with a temporary filename, and return that temporary name as +source_filename.

+

In our particular case, source_img does have a filename +(meanfunctional.nii). We would like to return that as +source_filename. The question is, how can we be sure that the user has +done nothing to source_img to make it diverge from its original state? +Could source_img have diverged, in memory, from the state recorded in +meantunctional.nii?

+

If the image and file have not diverged, we return meanfunctional.nii as +the source_filename, otherwise we will have to do something like:

+
import tempfile
+fname = tempfile.mkstemp('.nii')
+img = source_img.to_filename(fname)
+
+
+

and return fname as source_filename.

+

Another situation where we might like to pass around image objects that are +known to correspond to images on disk is when working in parallel. A set of +nodes may have fast common access to a filesystem on which the images are +stored. If a master is farming out images to nodes, a master node +distribution jobs to workers might want to check if the image was identical to +something on file and pass around a lightweight (proxied) image (with the data +not loaded into memory), relying on the node pulling the image from disk when +it uses it.

+
+
+

Possible implementation

+

One implementation is to have dirty flag, which, if set, would tell +you that the image might not correspond to the disk file. We set this +flag when anyone asks for the data, on the basis that the user may then +do something to the data and you can’t know if they have:

+
img = nibabel.load('some_image.nii')
+data = img.get_fdata()
+data[:] = 0
+img2 = nibabel.load('some_image.nii')
+assert not np.all(img2.get_fdata() == img.get_fdata())
+
+
+

The image consists of the data, the affine and a header. In order to +keep track of the header and affine, we could cache them when loading +the image:

+
img = nibabel.load('some_image.nii')
+hdr = img.header
+assert img._cache['header'] == img.header
+hdr.set_data_dtype(np.complex64)
+assert img._cache['header'] != img.header
+
+
+

When we need to know whether the image object and image file correspond, we +could check the current header and current affine (the header may be separate +from the affine for an SPM Analyze image) against their cached copies, if they +are the same and the ‘dirty’ flag has not been set by a previous call to +get_fdata(), we know that the image file does correspond to the image +object.

+

This may be OK for small bits of memory like the affine and the header, +but would quickly become prohibitive for larger image metadata such as +large nifti header extensions. We could just always assume that images +with large header extensions are not the same as for on disk.

+

The user might be able to override the result of these checks directly:

+
img = nibabel.load('some_image.nii')
+assert img.is_dirty == False
+hdr = img.header
+hdr.set_data_dtype(np.complex64)
+assert img.is_dirty == True
+img.is_dirty == False
+
+
+

The checks are magic behind the scenes stuff that do some safe optimization +(in the sense that we are not re-saving the data if that is not necessary), +but drops back to the default (re-saving the data) if there is any +uncertainty, or the cost is too high to be able to check.

+
+
+ + +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/devel/roadmap.html b/devel/roadmap.html new file mode 100644 index 0000000000..4dcd50fb60 --- /dev/null +++ b/devel/roadmap.html @@ -0,0 +1,208 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
+
+ +
+
+

NiBabel

+

Access a cacophony of neuro-imaging file formats

+
+
+ + + + +
+
+
+
+ +
+

Roadmap

+

The roadmap is intended for larger, fundamental changes to the project that are +likely to take months or years of developer time. Smaller-scoped items will +continue to be tracked on our issue tracker.

+

The scope of these improvements means that these changes may be controversial, +are likely to involve significant discussion among the core development team, +and may require the creation of one or more BIAPs (niBabel Increased +Awesomeness Proposals).

+
+

Background

+

Nibabel is a workbench that provides a Python API for working with images in +many formats. It is also a base library for tools implementing higher level +processing.

+

Nibabel’s success depends on:

+
    +
  • How easy it is to express common imaging tasks in the API.

  • +
  • The range of tasks it can perform.

  • +
+

An expressive, broad API will increase adoption and make it easier to teach.

+
+

Expressive API

+
+

Axis and tick labels

+

Brain images typically have three or four axes, whose meanings depend on the +way the image was acquired. Axes have natural labels, expressing meaning, +such as “time” or “slice”, and they may have tick labels such as acquisition +time. The scanner captures this information, but typical image formats cannot +store it, so it is easy to lose metadata and make analysis errors; see +BIAP6 - Identifying image axes.

+

We plan to expand Nibabel’s API to encode axis and tick labels by integrating +the Xarray package. Xarray simplifies HDF5 +serialization, and visualization.

+

An API for labels is not useful if we cannot read labels from the scanner +data, or save them with the image. We plan to:

+
    +
  • Develop HDF5 equivalents of standard image formats, for serialization of +data with labels.

  • +
  • Expand the current standard image format, NIfTI, to store labels in a JSON +addition to image metadata: BIAP3 - A JSON nifti header extension.

  • +
  • Read image metadata from DICOM, the standard scanner format.

  • +
+

Reading and attaching DICOM data will start with code integrated from +Dcmstack, by Brendan Moloney; see: +BIAP4 - Merging nibabel and dcmstack.

+

DICOM metadata is often hidden inside “private” DICOM elements that need +specialized parsers. We want to expand these parsers to preserve full metadata +and build a normalization layer to abstract vendor-specific storage locations +for metadata elements that describe the same thing.

+
+
+

API for surface data

+

Neuroimaging data often refers to locations on the brain surface. There are +three common formats for such data: GIFTI, CIFTI and Freesurfer. Nibabel can +read these formats, but lacks a standard API for reading and storing surface +data with metadata; see +nipy/nibabel#936, +nilearn/nilearn#2171. +We plan to develop a standard API, apply it to the standard formats, +and design an efficient general HDF5 storage container for serializing surface +data and metadata.

+
+
+
+

Range

+
+

Spatial transforms

+

Neuroimaging toolboxes include spatial registration methods to align the +objects and features present in two or more images. Registration methods +estimate and store spatial transforms. There is no standard or compatible +format to store and reuse these transforms, across packages.

+

Because Nibabel is a workbench, we want to extend its support to read +transforms calculated with AFNI, FreeSurfer, FSL, ITK/ANTs, NiftyReg, and SPM.

+

We have developed the NiTransforms project for this task; we plan to complete +and integrate NiTransforms into Nibabel. This will make transforms more +accessible to researchers, and therefore easier to work with, and reason about.

+
+
+
+
+ + +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/devel/scaling.html b/devel/scaling.html new file mode 100644 index 0000000000..0341b9ef0e --- /dev/null +++ b/devel/scaling.html @@ -0,0 +1,171 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
+
+ +
+
+

NiBabel

+

Access a cacophony of neuro-imaging file formats

+
+
+ + + + +
+
+
+
+ +
+

Scalefactors and intercepts

+

SPM Analyze and nifti1 images have scalefactors. nifti1 images also have +intercepts. If A is an array in memory, and S is the array that will +be written to disk, then:

+
R = (A - intercept) / scalefactor
+
+
+

and R == S if R is already the data dtype we need to write.

+

If we load the image from disk, we exactly recover S (and R). To get +something approximating A (say Aprime) we apply the intercept and +scalefactor:

+
Aprime = (S * scalefactor) + intercept
+
+
+

In a perfect world A would be exactly the same as Aprime. However +scalefactor and intercept are floating point values. With floating +point, if r = (a - b) / c; p = (r * c) + b it is not necessarily true that +p == a. For example:

+
>>> import numpy as np
+>>> a = 10
+>>> b = np.e
+>>> c = np.pi
+>>> r = (a - b) / c
+>>> p = (r * c) + b
+>>> p == a
+False
+
+
+

So there will be some error in this reconstruction, even when R is the same +type as S.

+

More common is the situation where R is a different type from S. If +R is of type r_dtype, S is of type s_dtype and +cast_function(R, dtype) is some function that casts R to the desired +type dtype, then:

+
R = (A - intercept) / scalefactor
+S = cast_function(R, s_dtype)
+R_prime = cast_function(S, r_dtype)
+A_prime = (R_prime * scalefactor) + intercept
+
+
+

The type of R will depend on what numpy did for upcasting A, intercept, +scalefactor.

+

In order that cast_function(S, r_dtype) can best reverse cast_function(R, +s_dtype), the second needs to know the type of R, which is not stored. The +type of R depends on the types of A and of intercept, scalefactor. +We don’t know the type of A because it is not stored.

+

R is likely to be a floating point type because of the application of +scalefactor and intercept. If (intercept, scalefactor) are not the identity +(0, 1), then we can ensure that R is at minimum the type of the intercept, +scalefactor by making these be at least 1D arrays, so that floating point +types will upcast in R = (A - intercept) / scalefactor.

+

The cast of R to S and back to R_prime can lose resolution if the +types of R and S have different resolution.

+

Our job is to select:

+
    +
  • scalefactor

  • +
  • intercept

  • +
  • cast_function

  • +
+

such that we minimize some measure of difference between A and +A_prime.

+
+ + +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/devel/spm_use.html b/devel/spm_use.html new file mode 100644 index 0000000000..4c7c134694 --- /dev/null +++ b/devel/spm_use.html @@ -0,0 +1,391 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
+
+ +
+
+

NiBabel

+

Access a cacophony of neuro-imaging file formats

+
+
+ + + + +
+
+
+
+ +
+

Image use-cases in SPM

+

SPM uses a vol struct as a structure characterizing an object. This +is a Matlab struct. A struct is like a Python dictionary, where +field names (strings) are associated with values. There are various +functions operating on vol structs, so the vol struct is rather like an +object, where the methods are implemented as functions. Actually, the +distinction between methods and functions in Matlab is fairly subtle - +their call syntax is the same for example.

+
>> fname = 'some_image.nii';
+>> vol = spm_vol(fname) % the vol struct
+
+vol =
+
+      fname: 'some_image.nii'
+        mat: [4x4 double]
+        dim: [91 109 91]
+         dt: [2 0]
+      pinfo: [3x1 double]
+          n: [1 1]
+    descrip: 'NIFTI-1 Image'
+    private: [1x1 nifti]
+
+>> vol.mat % the 'affine'
+
+ans =
+
+    -2     0     0    92
+     0     2     0  -128
+     0     0     2   -74
+     0     0     0     1
+
+>> help spm_vol
+  Get header information etc for images.
+  FORMAT V = spm_vol(P)
+  P - a matrix of filenames.
+  V - a vector of structures containing image volume information.
+  The elements of the structures are:
+        V.fname - the filename of the image.
+        V.dim   - the x, y and z dimensions of the volume
+        V.dt    - A 1x2 array.  First element is datatype (see spm_type).
+                  The second is 1 or 0 depending on the endian-ness.
+        V.mat   - a 4x4 affine transformation matrix mapping from
+                  voxel coordinates to real world coordinates.
+        V.pinfo - plane info for each plane of the volume.
+               V.pinfo(1,:) - scale for each plane
+               V.pinfo(2,:) - offset for each plane
+                  The true voxel intensities of the jth image are given
+                  by: val*V.pinfo(1,j) + V.pinfo(2,j)
+               V.pinfo(3,:) - offset into image (in bytes).
+                  If the size of pinfo is 3x1, then the volume is assumed
+                  to be contiguous and each plane has the same scalefactor
+                  and offset.
+ ____________________________________________________________________________
+
+  The fields listed above are essential for the mex routines, but other
+  fields can also be incorporated into the structure.
+
+  The images are not memory mapped at this step, but are mapped when
+  the mex routines using the volume information are called.
+
+  Note that spm_vol can also be applied to the filename(s) of 4-dim
+  volumes. In that case, the elements of V will point to a series of 3-dim
+  images.
+
+  This is a replacement for the spm_map_vol and spm_unmap_vol stuff of
+  MatLab4 SPMs (SPM94-97), which is now obsolete.
+ _______________________________________________________________________
+  Copyright (C) 2005 Wellcome Department of Imaging Neuroscience
+
+
+>> spm_type(vol.dt(1))
+
+ans =
+
+uint8
+
+>> vol.private
+
+ans =
+
+NIFTI object: 1-by-1
+            dat: [91x109x91 file_array]
+            mat: [4x4 double]
+     mat_intent: 'MNI152'
+           mat0: [4x4 double]
+    mat0_intent: 'MNI152'
+        descrip: 'NIFTI-1 Image'
+
+
+

So, in our (provisional) terms:

+
    +
  • vol.mat == img.affine

  • +
  • vol.dim == img.shape

  • +
  • vol.dt(1) (vol.dt[0] in Python) is equivalent to +img.get_data_dtype()

  • +
  • vol.fname == img.get_filename()

  • +
+

SPM abstracts the implementation of the image to the vol.private +member, that is not in fact required by the image interface.

+

Images in SPM are always 3D. Note this behavior:

+
>> fname = 'functional_01.nii';
+>> vol = spm_vol(fname)
+
+vol =
+
+191x1 struct array with fields:
+    fname
+    mat
+    dim
+    dt
+    pinfo
+    n
+    descrip
+    private
+
+
+

That is, one vol struct per 3D volume in a 4D dataset.

+
+

SPM image methods / functions

+

Some simple ones:

+
>> fname = 'some_image.nii';
+>> vol = spm_vol(fname);
+>> img_arr = spm_read_vols(vol);
+>> size(img_arr) % just loads in scaled data array
+
+ans =
+
+    91   109    91
+
+>> spm_type(vol.dt(1)) % the disk-level (IO) type is uint8
+
+ans =
+
+uint8
+
+>> class(img_arr) % always double regardless of IO type
+
+ans =
+
+double
+
+>> new_fname = 'another_image.nii';
+>> new_vol = vol;  % matlab always copies
+>> new_vol.fname = new_fname;
+>> spm_write_vol(new_vol, img_arr)
+
+ans =
+
+      fname: 'another_image.nii'
+        mat: [4x4 double]
+        dim: [91 109 91]
+         dt: [2 0]
+      pinfo: [3x1 double]
+          n: [1 1]
+    descrip: 'NIFTI-1 Image'
+    private: [1x1 nifti]
+
+
+

Creating an image from scratch, and writing plane by plane (slice by slice):

+
>> new_vol = struct();
+>> new_vol.fname = 'yet_another_image.nii';
+>> new_vol.dim = [91 109 91];
+>> new_vol.dt = [spm_type('float32') 0]; % little endian (0)
+>> new_vol.mat = vol.mat;
+>> new_vol.pinfo = [1 0 0]';
+>> new_vol = spm_create_vol(new_vol);
+>> for vox_z = 1:new_vol.dim(3)
+new_vol = spm_write_plane(new_vol, img_arr(:,:,vox_z), vox_z);
+end
+
+
+

I think it’s true that writing the plane does not change the image +scalefactors, so it’s only practical to use spm_write_plane for data +for which you already know the dynamic range across the volume.

+

Simple resampling from an image:

+
>> fname = 'some_image.nii';
+>> vol = spm_vol(fname);
+>> % for voxel coordinate 10,15,20 (1-based)
+>> hold_val = 3; % third order spline resampling
+>> val = spm_sample_vol(vol, 10, 15, 20, hold_val)
+
+val =
+
+    0.0510
+
+>> img_arr = spm_read_vols(vol);
+>> img_arr(10, 15, 20)  % same as simple indexing for integer coordinates
+
+ans =
+
+    0.0510
+
+>> % more than one point
+>> x = [10, 10.5]; y = [15, 15.5]; z = [20, 20.5];
+>> vals = spm_sample_vol(vol, x, y, z, hold_val)
+
+vals =
+
+    0.0510    0.0531
+
+>> % you can also get the derivatives, by asking for more output args
+>> [vals, dx, dy, dz] = spm_sample_vol(vol, x, y, z, hold_val)
+
+vals =
+
+    0.0510    0.0531
+
+
+dx =
+
+    0.0033    0.0012
+
+
+dy =
+
+    0.0033    0.0012
+
+
+dz =
+
+    0.0020   -0.0017
+
+
+

This is to speed up optimization in registration - where the optimizer +needs the derivatives.

+

spm_sample_vol always works in voxel coordinates. If you want some +other coordinates, you would transform them yourself. For example, +world coordinates according to the affine looks like:

+
>> wc = [-5, -12, 32];
+>> vc = inv(vol.mat) * [wc 1]'
+
+vc =
+
+   48.5000
+   58.0000
+   53.0000
+    1.0000
+
+>> vals = spm_sample_vol(vol, vc(1), vc(2), vc(3), hold_val)
+
+vals =
+
+    0.6792
+
+
+

Odder sampling, often used, can be difficult to understand:

+
>> slice_mat = eye(4);
+>> out_size = vol.dim(1:2);
+>> slice_no = 4; % slice we want to fetch
+>> slice_mat(3,4) = slice_no;
+>> arr_slice = spm_slice_vol(vol, slice_mat, out_size, hold_val);
+>> img_slice_4 = img_arr(:,:,slice_no);
+>> all(arr_slice(:) == img_slice_4(:))
+
+ans =
+
+     1
+
+
+

This is the simplest use - but in general any affine transform can go in +slice_mat above, giving optimized (for speed) sampling of slices +from volumes, as long as the transform is an affine.

+

Miscellaneous functions operating on vol structs:

+
    +
  • spm_conv_vol - convolves volume with separable functions in x, y, z

  • +
  • spm_render_vol - does a projection of a volume onto a surface

  • +
  • spm_vol_check - takes array of vol structs and checks for sameness of +image dimensions and mat (affines) across the list.

  • +
+

And then, many SPM functions accept vol structs as arguments.

+
+
+ + +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/dicom/dcm2nii_algorithms.html b/dicom/dcm2nii_algorithms.html new file mode 100644 index 0000000000..17f9935b49 --- /dev/null +++ b/dicom/dcm2nii_algorithms.html @@ -0,0 +1,202 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
+
+ +
+
+

NiBabel

+

Access a cacophony of neuro-imaging file formats

+
+
+ + + + +
+
+
+
+ +
+

dcm2nii algorithms

+

dcm2nii is an open source DICOM to nifti conversion program, written +by Chris Rorden, in Delphi (object orientated pascal). It’s part of +Chris’ popular mricron collection of programs. The source appears to +be best found on the mricron NITRC site. It’s BSD licensed.

+

These are working notes looking at Chris’ algorithms for working with +DICOM.

+
+

Compiling dcm2nii

+

Follow the download / install instructions at the +http://www.lazarus.freepascal.org/ site. I was on a Mac, and followed the +instructions here: +http://wiki.lazarus.freepascal.org/Installing_Lazarus_on_MacOS_X . Default +build with version 0.9.28.2 gave an error linking against Carbon, so I needed to +download a snapshot of fixed Lazarus 0.9.28.3 from +http://www.hu.freepascal.org/lazarus . Open <mricron>/dcm2nii/dcm2nii.lpi +using the Lazarus GUI. Follow instructions for compiler setup in the mricron +Readme.txt; in particular I set other compiler options to:

+
-k-macosx_version_min -k10.5
+-XR/Developer/SDKs/MacOSX10.5.sdk/
+
+
+

Further inspiration for building also came from the debian/rules file in +Michael Hanke’s mricron debian package: +http://neuro.debian.net/debian/pool/main/m/mricron/

+
+
+

Some tag modifications

+

Note - Chris tells me that dicomfastread.pas was an attempt to do a fast +dicom read that is not yet fully compatible, and that the algorithm used is in +fact dicomcompat.pas.

+

Looking in the source file <mricron>/dcm2nii/dicomfastread.pas.

+

Named fields here are as from DICOM fields

+
    +
  • If ‘MOSAIC’ is the last string in ‘ImageType’, this is a mosaic

  • +
  • ‘DateTime’ field is combination of ‘StudyDate’ and ‘StudyTime’; fixes +in file dicomtypes.pas for different scanner date / time formats.

  • +
  • AcquisitionNumber read as normal, but then set to 1, if this a mosaic +image, as set above.

  • +
  • If ‘EchoNumbers’ > 0 and < 16, add ‘EchoNumber’ * 100 to the +‘AcquisitionNumber’ - presumably to identify different echos from the +same series as being different series.

  • +
  • If ‘ScanningSequence’ sequence contains ‘RM’, add 100 to the +‘SeriesNumber’ - maybe to differentiate research and not-research +scans with the same acquisition number.

  • +
  • is_4D flag labeling DICOM file as a 4D file:

    +
    +
      +
    • There’s a Philips private tag (2001, 1018) - labeled ‘Number of +Slices MR’ by pydicom call this NS

    • +
    • If NS>0 and ‘NumberofTemporalPositions’ > 0, and +‘NumberOfFrames’ is > 1

    • +
    +
    +
  • +
+
+
+

Sorting slices into volumes

+

Looking in the source file <mricron>/dcm2nii/sortdicom.pas.

+

In function ShellSortDCM:

+

Sort compares two dicom images, call them dcm1 and dcm2. Tests are:

+
    +
  1. Are the two images ‘repeats’ - defined by same ‘InstanceNumber’ +(0020, 0013), and ‘AcquisitionNumber’ (0020, 0012) and ‘SeriesNumber’ +(0020, 0011) and a combination of ‘StudyDate’ and ‘StudyTime’)? Then +report an error about files having the same index, flag repeated values.

  2. +
  3. Is dcm1 less than dcm2, defined with comparisons in the +following order:

    +
      +
    1. StudyDate/Time

    2. +
    3. SeriesNumber

    4. +
    5. AcquisitionNumber

    6. +
    7. InstanceNumber

    8. +
    +

    This should obviously only ever be > or <, not ==, because of the +first check.

    +
  4. +
+

Next remove repeated values as found in the first step above.

+
+
+ + +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/dicom/dicom.html b/dicom/dicom.html new file mode 100644 index 0000000000..9aed6edad5 --- /dev/null +++ b/dicom/dicom.html @@ -0,0 +1,161 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + + +
+
+ +
+
+

NiBabel

+

Access a cacophony of neuro-imaging file formats

+
+
+ + + + + + + + \ No newline at end of file diff --git a/dicom/dicom_fields.html b/dicom/dicom_fields.html new file mode 100644 index 0000000000..eac0438fbd --- /dev/null +++ b/dicom/dicom_fields.html @@ -0,0 +1,180 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
+
+ +
+
+

NiBabel

+

Access a cacophony of neuro-imaging file formats

+
+
+ + + + +
+
+
+
+ +
+

DICOM fields

+

In which we pick out some interesting fields in the DICOM header.

+

We’re getting the information mainly from the standard DICOM object +definitions

+

We won’t talk about the orientation, patient position-type fields here +because we’ve covered those somewhat in DICOM voxel to patient coordinate system mapping.

+
+

Fields for ordering DICOM files into images

+

You’ll see some discussion of this in SPM DICOM conversion.

+

Section 7.3.1: general series module

+
    +
  • Modality (0008,0060) - Type of equipment that originally acquired the +data used to create the images in this Series. See C.7.3.1.1.1 for +Defined Terms.

  • +
  • Series Instance UID (0020,000E) - Unique identifier of the Series.

  • +
  • Series Number (0020,0011) - A number that identifies this Series.

  • +
  • Series Time (0008,0031) - Time the Series started.

  • +
+

Section C.7.6.1:

+
    +
  • Instance Number (0020,0013) - A number that identifies this image.

  • +
  • Acquisition Number (0020,0012) - A number identifying the single +continuous gathering of data over a period of time that resulted in +this image.

  • +
  • Acquisition Time (0008,0032) - The time the acquisition of data that +resulted in this image started

  • +
+

Section C.7.6.2.1.2:

+

Slice Location (0020,1041) is defined as the relative position of the +image plane expressed in mm. This information is relative to an +unspecified implementation specific reference point.

+

Section C.8.3.1 MR Image Module

+
    +
  • Slice Thickness (0018,0050) - Nominal reconstructed slice thickness, +in mm.

  • +
+

Section C.8.3.1 MR Image Module

+
    +
  • Spacing Between Slices (0018,0088) - Spacing between slices, in +mm. The spacing is measured from the center-tocenter of each slice.

  • +
  • Temporal Position Identifier (0020,0100) - Temporal order of a dynamic +or functional set of Images.

  • +
  • Number of Temporal Positions (0020,0105) - Total number of temporal +positions prescribed.

  • +
  • Temporal Resolution (0020,0110) - Time delta between Images in a +dynamic or functional set of images

  • +
+
+
+

Multi-frame images

+

An image for which the pixel data is a continuous stream of sequential frames.

+

Section C.7.6.6: Multi-Frame Module

+
    +
  • Number of Frames (0028,0008) - Number of frames in a Multi-frame +Image.

  • +
  • Frame Increment Pointer (0028,0009) - Contains the Data Element Tag of +the attribute that is used as the frame increment in Multi-frame pixel +data.

  • +
+
+
+ + +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/dicom/dicom_info.html b/dicom/dicom_info.html new file mode 100644 index 0000000000..89aee46998 --- /dev/null +++ b/dicom/dicom_info.html @@ -0,0 +1,160 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
+
+ +
+
+

NiBabel

+

Access a cacophony of neuro-imaging file formats

+
+
+ + + + +
+
+
+
+ +
+

DICOM information

+

DICOM is a large and sometimes confusing imaging data format.

+

In the other pages in this series we try and document our understanding of +various aspects of DICOM relevant to converting to formats such as NIfTI .

+

There are a large number of DICOM image conversion programs already, +partly because it is a complicated format with features that vary from +manufacturer to manufacturer.

+

We use the excellent PyDICOM as our back-end for reading DICOM.

+

Here is a selected list of other tools and relevant resources:

+
    +
  • Grassroots DICOM : GDCM . It is C++ code wrapped with swig and so +callable from Python. ITK apparently uses it for DICOM conversion. +BSD license.

  • +
  • dcm2nii - a BSD licensed converter by Chris Rorden. As usual, Chris +has done an excellent job of documentation, and it is well +battle-tested. There’s a nice set of example data to test against and +a list of other DICOM software. The MRIcron install page points to +the source code. Chris has also put effort into extracting diffusion +parameters from the DICOM images.

  • +
  • SPM8 - SPM has a stable and robust general DICOM conversion tool +implemented in the spm_dicom_convert.m and spm_dicom_headers.m +scripts. The conversions don’t try to get the diffusion parameters. +The code is particularly useful because it has been well-tested and is +written in Matlab - and so is relatively easy to read. GPL license. +We’ve described some of the algorithms that SPM uses for DICOM +conversion in SPM DICOM conversion.

  • +
  • DICOM2Nrrd: a command line converter to convert DICOM images to Nrrd +format. You can call the command from within the Slicer GUI. It +does have algorithms for getting diffusion information from the DICOM +headers, and has been tested with Philips, GE and Siemens data. It’s +not clear whether it yet supports the Siemens mosaic format. BSD style +license.

  • +
  • The famous Philips cookbook: https://www.archive.org/details/DicomCookbook

  • +
  • http://dicom.online.fr/fr/dicomlinks.htm

  • +
+
+
+

Sample images

+ +
+ + +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/dicom/dicom_intro.html b/dicom/dicom_intro.html new file mode 100644 index 0000000000..deaf1beb82 --- /dev/null +++ b/dicom/dicom_intro.html @@ -0,0 +1,1009 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
+
+ +
+
+

NiBabel

+

Access a cacophony of neuro-imaging file formats

+
+
+ + + + +
+
+
+
+ +
+

Introduction to DICOM

+

DICOM defines standards for storing data in memory and on disk, and for +communicating this data between machines over a network.

+

We are interested here in DICOM data. Specifically we are interested in DICOM +files.

+

DICOM files are binary dumps of the objects in memory that DICOM sends across +the network.

+

We need to understand the format that DICOM uses to send messages across the +network to understand the terms the DICOM uses when storing data in files.

+

For example, I hope, by the time you reach the end of this document, you will +understand the following complicated and confusing statement from section 7 of +the DICOM standards document PS 3.10:

+
+

7 DICOM File Format

+

The DICOM File Format provides a means to encapsulate in a file the Data Set +representing a SOP Instance related to a DICOM IOD. As shown in Figure 7-1, +the byte stream of the Data Set is placed into the file after the DICOM File +Meta Information. Each file contains a single SOP Instance.

+
+
+

DICOM is messages

+

The fundamental task of DICOM is to allow different computers to send messages +to one another. These messages can contain data, and the data is very often +medical images.

+

The messages are in the form of requests for an operation, or responses to those requests.

+

Let’s call the requests and the responses - services.

+

Every DICOM message starts with a stream of bytes containing information about +the service. This part of the message is called the DICOM Message Service +Element or DIMSE. Depending on what the DIMSE was, there may follow some data +related to the request.

+

For example, there is a DICOM service called “C-ECHO”. This asks for a response +from another computer to confirm it has seen the echo request. There is no +associated data following the “C-ECHO” DIMSE part. So, the full message is the +DIMSE “C-ECHO”.

+

There is another DICOM service called “C-STORE”. This is a request for the +other computer to store some data, such as an image. The data to be stored +follows the “C-STORE” DIMSE part.

+

We go into more detail on this later in the page.

+

Both the DIMSE and the subsequent data have a particular binary format - +consisting of DICOM elements (see below).

+

Here we will cover:

+
    +
  • what DICOM elements are;

  • +
  • how DICOM elements are arranged to form complicated data structures such as images;

  • +
  • how the service part and the data part go together to form whole messages

  • +
  • how these parts relate to DICOM files.

  • +
+
+
+

The DICOM standard

+

The documents defining the standard are:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Number

Name

PS 3.1

Introduction and Overview

PS 3.2

Conformance

PS 3.3

Information Object Definitions

PS 3.4

Service Class Specifications

PS 3.5

Data Structure and Encoding

PS 3.6

Data Dictionary

PS 3.7

Message Exchange

PS 3.8

Network Communication Support for Message Exchange

PS 3.9

Retired

PS 3.10

Media Storage / File Format for Media Interchange

PS 3.11

Media Storage Application Profiles

PS 3.12

Media Formats / Physical Media for Media Interchange

PS 3.13

Retired

PS 3.14

Grayscale Standard Display Function

PS 3.15

Security and System Management Profiles

PS 3.16

Content Mapping Resource

PS 3.17

Explanatory Information

PS 3.18

Web Access to DICOM Persistent Objects (WADO)

PS 3.19

Application Hosting

PS 3.20

Transformation of DICOM to and from HL7 Standards

+
+
+

DICOM data format

+

DICOM data is stored in memory and on disk as a sequence of DICOM elements +(section 7 of PS 3.5).

+
+

DICOM elements

+

A DICOM element is made up of three or four fields. These are (Attribute Tag, +[Value Representation, ], Value Length, Value Field), where Value +Representation may be present or absent, depending on the type of “Value +Representation Encoding” (see below)

+
+

Attribute Tag

+

The attribute tag is a pair of 16-bit unsigned integers of form (Group number, +Element number). The tag uniquely identifies the element.

+

The Element number is badly named, because the element number does not give a +unique number for the element, but only for the element within the group (given +by the Group number).

+

The (Group number, Element number) are nearly always written as hexadecimal +numbers in the following format: (0010, 0010). The decimal representation +of hexadecimal 0010 is 16, so this tag refers to group number 16, element number +16. If you look this tag up in the DICOM data dictionary (PS 3.6) you’ll see +this must be the element called “PatientName”.

+

These tag groups have special meanings:

+ + + + + + + + + + + + + + + + + + + + +

Tag group

Meaning

0000

Command elements

0002

File meta elements

0004

Directory structuring elements

0006

(not used)

+

See Annex E (command dictionary) of PS 3.7 for details on group 0000. See +sections 7 and 8 of PS 3.6 for details of groups 2 and 4 respectively.

+

Tags in groups 0000, 0002, 0004 are therefore not data elements, but Command +elements; File meta elements; directory structuring elements.

+

Tags with groups from 0008 are data element tags.

+
+
Standard attribute tags
+

Standard tags are tags with an even group number (see below). There is a full +list of all standard data element tags in the DICOM data dictionary in section +6 of DICOM standard PS 3.6.

+

Even numbered groups are defined in the DICOM standard data dictionary. Odd +numbered groups are “private”, are not defined in the standard data dictionary +and can be used by manufacturers as they wish (see below).

+

Quoting from section 7.1 of PS 3.5:

+
+

Two types of Data Elements are defined:

+

—Standard Data Elements have an even Group Number that is not (0000,eeee), +(0002,eeee), (0004,eeee), or (0006,eeee).

+
+
+

Note: Usage of these groups is reserved for DIMSE Commands (see PS 3.7) and +DICOM File Formats.

+

—Private Data Elements have an odd Group Number that is not (0001,eeee), +(0003,eeee), (0005,eeee), (0007,eeee), or (FFFF,eeee). Private Data Elements +are discussed further in Section 7.8.

+
+
+
+
Private attribute tags
+

Private attribute tags are tags with an odd group number. A private element is +an element with a private tag.

+

Private elements still use the (Tag, [Value Representation, ] Value Length, +Value Field) DICOM data format.

+

The same odd group may be used by different manufacturers in different ways.

+

To try and avoid collisions of private tags from different manufacturers, there +is a mechanism by which a manufacturer can tell other users of a DICOM dataset +that it has reserved a block in the (Group number, Element number) space for +their own use. To do this they write a “Private Creator” element where the tag +is of the form (gggg, 00xx), the Value Representation (see below) is “LO” +(Long String) and the Value Field is a string identifying what the space is +reserved for. Here gggg is the odd group we are reserving a portion of and +the xx is the block of elements we are reserving. A tag of (gggg, 00xx) +reserves the 256 elements in the range (gggg, xx00) to (gggg, xxFF).

+

For example, here is a real data element from a Siemens DICOM dataset:

+
(0019, 0010) Private Creator                     LO: 'SIEMENS MR HEADER'
+
+
+

This reserves the tags from (0019, 1000) to (0019, 10FF) for information +on the “SIEMENS MR HEADER”

+

The odd group gggg must be greater than 0008 and the block reservation +xx must be greater than or equal to 0010 and less than 0100.

+

Here is the start of the relevant section from PS 3.5:

+
+

7.8.1 PRIVATE DATA ELEMENT TAGS

+

It is possible that multiple implementers may define Private Elements with the +same (odd) group number. To avoid conflicts, Private Elements shall be +assigned Private Data Element Tags according to the following rules.

+

a) Private Creator Data Elements numbered (gggg,0010-00FF) (gggg is odd) shall +be used to reserve a block of Elements with Group Number gggg for use by an +individual implementer. The implementer shall insert an identification code +in the first unused (unassigned) Element in this series to reserve a block of +Private Elements. The VR of the private identification code shall be LO (Long +String) and the VM shall be equal to 1.

+

b) Private Creator Data Element (gggg,0010), is a Type 1 Data Element that +identifies the implementer reserving element (gggg,1000-10FF), Private Creator +Data Element (gggg,0011) identifies the implementer reserving elements +(gggg,1100-11FF), and so on, until Private Creator Data Element (gggg,00FF) +identifies the implementer reserving elements (gggg,FF00- FFFF).

+

c) Encoders of Private Data Elements shall be able to dynamically assign +private data to any available (unreserved) block(s) within the Private group, +and specify this assignment through the blocks corresponding Private Creator +Data Element(s). Decoders of Private Data shall be able to accept reserved +blocks with a given Private Creator identification code at any position within +the Private group specified by the blocks corresponding Private Creator Data +Element.

+
+
+
+
+

Value Representation

+

Value Representation is often abbreviated to VR.

+

The VR is a two byte character string giving the code for the encoding of the +subsequent data in the Value Field (see below).

+

The VR appears in DICOM data that has “Explicit Value Representation”, and is +absent for data with “Implicit Value Representation”. “Implicit Value +Representation” uses the fact that the DICOM data dictionary gives VR values for +each tag in the standard DICOM data dictionary, so the VR value is implied by +the tag value, given the data dictionary.

+

Most DICOM data uses “Explicit Value Representation” because the DICOM data +dictionary only gives VRs for standard (even group number, not private) data +elements. Each manufacturer writes their own private data elements, and the VR +of these elements is not defined in the standard, and therefore may not be known +to software not from that manufacturer.

+

The VR codes have to be one of the values from this table (section 6.2 of DICOM +standard PS 3.5):

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Value Representation

Description

AE

Application Entity

AS

Age String

AT

Attribute Tag

CS

Code String

DA

Date

DS

Decimal String

DT

Date/Time

FL

Floating Point Single (4 bytes)

FD

Floating Point Double (8 bytes)

IS

Integer String

LO

Long String

LT

Long Text

OB

Other Byte

OF

Other Float

OW

Other Word

PN

Person Name

SH

Short String

SL

Signed Long

SQ

Sequence of Items

SS

Signed Short

ST

Short Text

TM

Time

UI

Unique Identifier

UL

Unsigned Long

UN

Unknown

US

Unsigned Short

UT

Unlimited Text

+
+
+

Value length

+

Value length gives the length of the data contained in the Value Field tag, or +is a flag specifying the Value Field is of undefined length, and thus must be +terminated later in the data stream with a special Item or Sequence Delimitation +tag.

+

Quoting from section 7.1.1 of PS 3.5:

+
+

Value Length: Either:

+

a 16 or 32-bit (dependent on VR and whether VR is explicit or implicit) +unsigned integer containing the Explicit Length of the Value Field as the +number of bytes (even) that make up the Value. It does not include the +length of the Data Element Tag, Value Representation, and Value Length +Fields.

+

a 32-bit Length Field set to Undefined Length (FFFFFFFFH). Undefined +Lengths may be used for Data Elements having the Value Representation +(VR) Sequence of Items (SQ) and Unknown (UN). For Data Elements with +Value Representation OW or OB Undefined Length may be used depending +on the negotiated Transfer Syntax (see Section 10 and Annex A).

+
+
+
+

Value field

+

An even number of bytes storing the value(s) of the data element. The exact +format of this data depends on the Value Representation (see above) and the +Value Multiplicity (see next section).

+
+
+
+

Data element tags and data dictionaries

+

We can look up data element tags in a data dictionary.

+

As we’ve seen, data element tags with even group numbers are standard data +element tags. We can look these up in the standard data dictionary in section 6 +of PS 3.6.

+

Data element tags with odd group numbers are private data element tags. These +can be used by manufacturers for information that may be specific to the +manufacturer. To look up these tags, we need the private data dictionary of the +manufacturer.

+

A data dictionary lists (Attribute tag, Attribute name, Attribute Keyword, Value +Representation, Value Multiplicity) for all tags.

+

For example, here is an excerpt from the table in PS 3.6 section 6:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Tag

Name

Keyword

VR

VM

(0010,0010)

Patient’s Name

PatientName

PN

1

(0010,0020)

Patient ID

PatientID

LO

1

(0010,0021)

Issuer of Patient ID

IssuerOfPatientID

LO

1

(0010,0022)

Type of Patient ID

TypeOfPatientID

CS

1

(0010,0024)

Issuer of Patient ID Qualifiers Sequence

IssuerOfPatientIDQualifiersSequence

SQ

1

(0010,0030)

Patient’s Birth Date

PatientBirthDate

DA

1

(0010,0032)

Patient’s Birth Time

PatientBirthTime

TM

1

+

The “Name” column gives a standard name for the tag. “Keyword” gives a shorter +equivalent to the name without spaces that can be used as a variable or +attribute name in code.

+
+

Value Representation in the data dictionary

+

The “VR” column in the data dictionary gives the Value Representation. There is +usually only one possible VR for each tag [1].

+

If a particular stream of data elements is using “Implicit Value Representation +Encoding” then the data elements consist of (tag, Value Length, Value Field) and +the Value Representation is implicit. In this case we have to get the Value +Representation from the data dictionary. If a stream is using “Explicit Value +Representation Encoding”, the elements consist of (tag, Value Representation, +Value Length, Value Field) and the Value Representation is therefore already +specified along with the data.

+
+
+

Value Multiplicity in the data dictionary

+

The “VM” column in the dictionary gives the Value Multiplicity for this tag. +Quoting from PS 3.5 section 6.4:

+
+

The Value Multiplicity of a Data Element specifies the number of Values that +can be encoded in the Value Field of that Data Element. The VM of each Data +Element is specified explicitly in PS 3.6. If the number of Values that may +be encoded in an element is variable, it shall be represented by two numbers +separated by a dash; e.g., “1-10” means that there may be 1 to 10 Values in +the element.

+
+

The most common values for Value Multiplicity in the standard data dictionary +are (in decreasing frequency) ‘1’, ‘1-n’, ‘3’, ‘2’, ‘1-2’, ‘4’ with other values +being less common.

+

The data dictionary is the only way to know the Value Multiplicity of a +particular tag. This means that we need the manufacturer’s private data +dictionary to know the Value Multiplicity of private attribute tags.

+
+
+
+

DICOM data structures

+
+

A data set

+

A DICOM data set is a ordered list of data elements. The order of the list is +the order of the tags of the data elements. Here is the definition from section +3.10 of PS 3.5:

+
+

DATA SET: Exchanged information consisting of a structured set of Attribute +values directly or indirectly related to Information Objects. The value of +each Attribute in a Data Set is expressed as a Data Element. A collection +of Data Elements ordered by increasing Data Element Tag number that is an +encoding of the values of Attributes of a real world object.

+
+
+
+

Background - the DICOM world

+

DICOM has abstract definitions of a set of entities (objects) in the “Real +World”. These real world objects have relationships between them. Section 7 of +PS 3.3 has the title “DICOM model of the real world”. Examples of Real World +entities are Patient, Study, Series.

+

Here is a selected list of real world entities compiled from section 7 of PS +3.3:

+
    +
  • Patient

  • +
  • Visit

  • +
  • Study

  • +
  • Modality Performed Procedure Steps

  • +
  • Frame of Reference

  • +
  • Equipment

  • +
  • Series

  • +
  • Registration

  • +
  • Fiducials

  • +
  • Image

  • +
  • Presentation State

  • +
  • SR Document

  • +
  • Waveform

  • +
  • MR Spectroscopy

  • +
  • Raw Data

  • +
  • Encapsulated Document

  • +
  • Real World Value Mapping

  • +
  • Stereometric Relationship

  • +
  • Surface

  • +
  • Measurements

  • +
+

DICOM refers to its model of the entities and their relationships in the real +world as the DICOM Application Model. PS 3.3:

+
+

3.8.5 DICOM application model: an Entity-Relationship diagram used to model +the relationships between Real-World Objects which are within the area of +interest of the DICOM Standard.

+
+
+
+

DICOM Entities and Information Object Definitions

+

This is rather confusing.

+

PS 3.3 gives definitions of fundamental DICOM objects called Information Object +Definitions (IODs). Here is the definition of an IOD from section 3.8.7 of PS +3.3:

+
+

3.8.7 Information object definition (IOD): a data abstraction of a class of +similar Real-World Objects which defines the nature and Attributes relevant +to the class of Real-World Objects represented.

+
+

IODs give lists of attributes (data elements) that refer to one or more objects +in the DICOM Real World.

+

A single IOD is the usual atom of data sent in a single DICOM message.

+

An IOD that contains attributes (data elements) for only one object in the DICOM +Real World is a Normalized IOD. From PS 3.3:

+
+

3.8.10 Normalized IOD: an Information Object Definition which represents a +single entity in the DICOM Application Model. Such an IOD includes +Attributes which are only inherent in the Real-World Object that the IOD +represents.

+
+

Annex B of PS 3.3 defines the normalized IODs.

+

Many DICOM Real World objects do not have corresponding normalized IODs, +presumably because there is no common need to send data only corresponding to - +say - a patient - without also sending related information like - say - an +image. If you do want to send information relating to a patient with +information relating to an image, you need a composite IOD.

+

An IOD that contains attributes from more than one object in the DICOM Real +World is a Composite IOD. PS 3.3 again:

+
+

3.8.2 Composite IOD: an Information Object Definition which represents parts +of several entities in the DICOM Application Model. Such an IOD includes +Attributes which are not inherent in the Real-World Object that the IOD +represents but rather are inherent in related Real-World Objects

+
+

Annex A of PS 3.3 defines the composite IODs.

+

DICOM MR or CT image IODs are classic examples of composite IODs, because they +contain information not just about the image itself, but also information about +the patient, the study, the series, the frame of reference and the equipment.

+

The term Information Entity (IE) refers to a part of a composite IOD that +relates to a single DICOM Real World object. PS 3.3:

+
+

3.8.6 Information entity: that portion of information defined by a Composite +IOD which is related to one specific class of Real-World Object. There is a +one-to-one correspondence between Information Entities and entities in the +DICOM Application Model.

+
+

IEs are names of DICOM Real World objects that label parts of a composite IOD. +IEs have no intrinsic content, but serve as meaningful labels for a group of +modules (see below) that refer to the same Real World object.

+

Annex A 1.2, PS 3.3 lists all the IEs used in composite IODs.

+

For example, section A.4 in PD 3.3 defines the composite IOD for an MR Image - +the Magnetic Resonance Image Object Definition. The definition looks like this +(table A.4-1 of PS 3.3)

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

IE

Module

Reference

Usage

Patient

Patient

C.7.1.1

M

Clinical Trial Subject

C.7.1.3

U

Study

General Study

C.7.2.1

M

Patient Study

C.7.2.2

U

Clinical Trial Study

C.7.2.3

U

Series

General Series

C.7.3.1

M

Clinical Trial Series

C.7.3.2

U

Frame of Reference

Frame of Reference

C.7.4.1

M

Equipment

General Equipment

C.7.5.1

M

Image

General Image

C.7.6.1

M

Image Plane

C.7.6.2

M

Image Pixel

C.7.6.3

M

Contrast/bolus

C.7.6.4

C - Required if contrast media was used in this image

Device

C.7.6.12

U

Specimen

C.7.6.22

U

MR Image

C.8.3.1

M

Overlay Plane

C.9.2

U

VOI LUT

C.11.2

U

SOP Common

C.12.1

M

+

As you can see, the MR Image IOD is composite and composed of Patient, Study, +Series, Frame of Reference, Equipment and Image IEs.

+

The module heading defines which modules make up the information relevant to +the IE.

+

A module is a named and defined grouping of attributes (data elements) with +related meaning. PS 3.3:

+
+

3.8.8 Module: A set of Attributes within an Information Entity or Normalized +IOD which are logically related to each other.

+
+

Grouping attributes into modules simplifies the definition of multiple composite +IODs. For example, the composite IODs for a CT image and an MR Image both have +modules for Patient, Clinical Trial Subject, etc.

+

Annex C of PS 3.3 defines all the modules used for the IOD definitions. For +example, from the table above, we see that the “Patient” module is at section +C.7.1.1 of PS 3.3. This section gives a table of all the attributes (data +elements) in this module.

+

The last column in the table above records whether the particular module is +Mandatory, Conditional or User Option (defined in section A 1.3 of PS 3.3)

+

Lastly module definitions may make use of Attribute macros. Attribute macros +are very much like modules, in that they are a named group of attributes that +often occur together in module definitions, or definitions of other macros. +From PS 3.3:

+
+

3.11.1 Attribute Macro: a set of Attributes that are described in a single +table that is referenced by multiple Modules or other tables.

+
+

For example, here is the Patient Orientation Macro definition table from section +10.12 in PS 3.3:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Attribute Name

Tag

Type

Attribute Description

Patient Orientation Code Sequence

(0054,0410)

1

Sequence that describes the orientation of the patient with respect to gravity. See C.8.11.5.1.2 for further explanation. Only a single Item shall be included in this Sequence.

>Include ‘Code Sequence Macro’ Table 8.8-1.

Baseline Context ID 19

>Patient Orientation Modifier Code Sequence

(0054,0412)

1C

Patient orientation modifier. Required if needed to fully specify the orientation of the patient with respect to gravity. Only a single Item shall be included in this Sequence.

>>Include ‘Code Sequence Macro’ Table 8.8-1.

Baseline Context ID 20

Patient Gantry Relationship Code Sequence

(0054,0414)

3

Sequence that describes the orientation of the patient with respect to the head of the table. See Section C.8.4.6.1.3 for further explanation. Only a single Item is permitted in this Sequence.

>Include ‘Code Sequence Macro’ Table 8.8-1.

Baseline Context ID 21

+

As you can see, this macro specifies some tags that should appear when this +macro is “Included” - and also includes other macros.

+
+
+
+

DICOM services (DIMSE)

+

We now go back to messages.

+

The DICOM application sending the message is called the Service Class User +(SCU). We might also call this the client.

+

The DICOM application receiving the message is called the Service Class Provider +(SCP). We might also call this the server - for this particular message.

+

Quoting from PS 3.7 section 6.3:

+
+

A Message is composed of a Command Set followed by a conditional Data Set +(see PS 3.5 for the definition of a Data Set). The Command Set is used to +indicate the operations/notifications to be performed on or with the Data +Set.

+
+

The command set consists of command elements (elements with group number 0000).

+

Valid sequences of command elements in the command set form valid DICOM Message +Service Elements (DIMSEs). Sections 9 and 10 of PS 3.7 define the valid DIMSEs.

+

For example, there is a DIMSE service called “C-ECHO” that requests confirmation +from the responding application that the echo message arrived.

+

The definition of the DIMSE services specifies, for a particular DIMSE service, +whether the DIMSE command set should be followed by a data set.

+

In particular, the data set will be a full Information Object Definition’s worth +of data.

+

Of most interest to us, the “C-STORE” service command set should always be +followed by a data set conforming to an image data IOD.

+
+
+

DICOM service object pairs (SOPs)

+

As we’ve seen, some DIMSE services should be followed by particular types of +data.

+

For example, the “C-STORE” DIMSE command set should be followed by an IOD of +data to store, but the “C-ECHO” has no data object following.

+

The association of a particular type of DIMSE (command set) with the associated +IOD’s-worth of data is a Service Object Pair. The DIMSE is the “Service” and the +data IOD is the “Object”. Thus the combination of a “C-STORE” DIMSE and an “MR +Image” IOD would be a SOP. Services that do not have data following are a +particular type of SOP where the Object is null. For example, the “C-ECHO” +service is the entire contents of a Verification SOP (PS 3.4, section A.4).

+

DICOM defines which pairings are possible, by listing them all as Service Object +Pair classes (SOP classes).

+

Usually a SOP class describes the pairing of exactly one DIMSE service with one +defined IOD. For example, the “MR Image storage” SOP class pairs the “C-STORE” +DIMSE with the “MR Image” IOD.

+

Sometimes a SOP class describes the pairings of one of several possible DIMSEs +with a particular IOP. For example, the “Modality Performed Procedure Step” SOP +class describes the pairing of either (“N-CREATE”, Modality Performed +Procedure Step IOD) or (“N-SET”, Modality Performed Procedure Step IOD) (see +PS 3.4 F.7.1). For this reason a SOP class is best described as the pairing of +a DIMSE service group with an IOD, where the DIMSE service group usually +contains just one DIMSE service, but sometimes has more. For example, the “MR +Image Storage” SOP class has a DIMSE service group of one element [“C-STORE”]. +The “Modality Performed Procedure Step” SOP class has a DIMSE service group with +two elements: [“N-CREATE”, “N-SET”].

+

From PS 3.4:

+
+

6.4 DIMSE SERVICE GROUP

+

DIMSE Service Group specifies one or more operations/notifications defined +in PS 3.7 which are applicable to an IOD.

+

DIMSE Service Groups are defined in this Part of the DICOM Standard, in the +specification of a Service - Object Pair Class.

+

6.5 SERVICE-OBJECT PAIR (SOP) CLASS

+

A Service-Object Pair (SOP) Class is defined by the union of an IOD and a +DIMSE Service Group. The SOP Class definition contains the rules and +semantics which may restrict the use of the services in the DIMSE Service +Group and/or the Attributes of the IOD.

+
+

The Annexes of PS 3.4 define the SOP classes.

+

A pairing of actual data of form (DIMSE group, IOD) that conforms to the SOP +class definition, is a SOP class instance. That is, the instance comprises the +actual values of the service and data elements being transmitted.

+

For example, there is a SOP class called “MR Image Storage”. This is the +association of the “C-STORE” DIMSE command with the “MR Image” IOD. A +particular “C-STORE” request command set along with the particular “MR Image” +IOD data set would be an instance of the MR Image SOP class.

+
+
+

DICOM files

+

Now let us return to the confusing definition of the DICOM file format from +section 7 of PS 3.10:

+
+

7 DICOM File Format

+

The DICOM File Format provides a means to encapsulate in a file the Data Set +representing a SOP Instance related to a DICOM IOD. As shown in Figure 7-1, +the byte stream of the Data Set is placed into the file after the DICOM File +Meta Information. Each file contains a single SOP Instance.

+
+

The DICOM file Meta Information is:

+
    +
  • File preamble - 128 bytes, content unspecified

  • +
  • DICOM prefix - 4 bytes “DICM” character string

  • +
  • 5 meta information elements (group 0002) as defined in table 7.1 of PS 3.10

  • +
+

There follows the IOD dataset part of the SOP instance. In the case of a file +storing an MR Image, this dataset will be of IOD type “MR Image”

+

Footnotes

+ +
+
+
+ + +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/dicom/dicom_mosaic.html b/dicom/dicom_mosaic.html new file mode 100644 index 0000000000..b89b12ac02 --- /dev/null +++ b/dicom/dicom_mosaic.html @@ -0,0 +1,232 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + + +
+
+ +
+
+

NiBabel

+

Access a cacophony of neuro-imaging file formats

+
+
+ + + + +
+
+
+
+ +
+

Siemens mosaic format

+

Siemens mosaic format is a way of storing a 3D image in a DICOM image +file. The simplest DICOM images only knows how to store 2D files. For +example, a 3D image in DICOM is usually stored as a series of 2D slices, +each slices as a separate DICOM image. . Mosaic format stores the 3D +image slices as a 2D grid - or mosaic.

+

For example here are the pixel data as loaded directly from a DICOM image +with something like:

+
import matplotlib.pylab as plt
+import dicom
+dcm_data = dicom.read_file('my_file.dcm')
+plt.imshow(dcm_data.pixel_array)
+
+
+../_images/mosaic_grid.png +
+

Getting the slices from the mosaic

+

The apparent image in the DICOM file is a 2D array that consists of blocks, +that are the output 2D slices. Let’s call the original array the slab, and +the contained slices slices. The slices are of pixel dimension +n_slice_rows x n_slice_cols. The slab is of pixel dimension +n_slab_rows x n_slab_cols. Because the arrangement of blocks in the +slab is defined as being square, the number of blocks per slab row and slab +column is the same. Let n_blocks be the number of blocks contained in the +slab. There is also n_slices - the number of slices actually collected, +some number <= n_blocks. We have the value n_slices from the +‘NumberOfImagesInMosaic’ field of the Siemens private (CSA) header. +n_row_blocks and n_col_blocks are therefore given by +ceil(sqrt(n_slices)), and n_blocks is n_row_blocks ** 2. Also +n_slice_rows == n_slab_rows / n_row_blocks, etc. Using these numbers we +can therefore reconstruct the slices from the 2D DICOM pixel array.

+
+
+

DICOM orientation for mosaic

+

See DICOM patient coordinate system and DICOM voxel to patient coordinate system mapping. We want a 4 x 4 +affine \(A\) that will take us from (transposed) voxel coordinates in the +DICOM image to mm in the DICOM patient coordinate system. See (i, j), columns, rows in DICOM for +what we mean by transposed voxel coordinates.

+

We can think of the affine \(A\) as the (3,3) component, \(RS\), and a (3,1) +translation vector \(\mathbf{t}\). \(RS\) can in turn be thought of as the +dot product of a (3,3) rotation matrix \(R\) and a scaling matrix \(S\), +where S = diag(s) and \(\mathbf{s}\) is a (3,) vector of voxel sizes. +\(\mathbf{t}\) is a (3,1) translation vector, defining the coordinate in +millimeters of the first voxel in the voxel volume (the voxel given by +voxel_array[0,0,0]).

+

In the case of the mosaic, we have the first two columns of \(R\) from the +\(F\) - the left/right flipped version of the ImageOrientationPatient +DICOM field described in DICOM affines again. To make a full +rotation matrix, we can generate the last column from the cross product +of the first two. However, Siemens defines, in its private +CSA header, a SliceNormalVector which gives the third column, +but possibly with a z flip, so that \(R\) is orthogonal, but not a +rotation matrix (it has a determinant of < 0).

+

The first two values of \(\mathbf{s}\) (\(s_1, s_2\)) are given by the +PixelSpacing field. We get \(s_3\) (the slice scaling +value) from SpacingBetweenSlices.

+

The SPM DICOM conversion code has a comment saying that mosaic DICOM images +have an incorrect ImagePositionPatient field. The +ImagePositionPatient field usually gives the \(\mathbf{t}\) vector. +The comments imply that Siemens has derived ImagePositionPatient +from the (correct) position of the center of the first slice (once the +mosaic has been unpacked), but has then adjusted the vector to point to +the top left voxel, where the slice size used for this adjustment is the +size of the mosaic, before it has been unpacked. Let’s call the correct +position in millimeters of the center of the first slice \(\mathbf{c} = +[c_x, c_y, c_z]\). We have the derived \(RS\) matrix from the calculations +above. The unpacked (eventual, real) slice dimensions are \((rd_{rows}, +rd_{cols})\) and the mosaic dimensions are \((md_{rows}, md_{cols})\). The +ImagePositionPatient vector \(\mathbf{i}\) resulted from:

+
+\[\begin{split}\mathbf{i} = \mathbf{c} + RS + \begin{bmatrix} -(md_{rows}-1) / 2\\ + -(md_{cols}-1) / 2\\ + 0 \end{bmatrix}\end{split}\]
+

To correct the faulty translation, we reverse it, and add the correct +translation for the unpacked slice size \((rd_{rows}, rd_{cols})\), giving +the true image position \(\mathbf{t}\):

+
+\[\begin{split}\mathbf{t} = \mathbf{i} - + (RS \begin{bmatrix} -(md_{rows}-1) / 2\\ + -(md_{cols}-1) / 2\\ + 0 \end{bmatrix}) + + (RS \begin{bmatrix} -(rd_{rows}-1) / 2\\ + -(rd_{cols}-1) / 2\\ + 0 \end{bmatrix})\end{split}\]
+

Because of the final zero in the voxel translations, this simplifies to:

+
+\[\begin{split}\mathbf{t} = \mathbf{i} + + Q \begin{bmatrix} (md_{rows} - rd_{rowss}) / 2 \\ + (md_{cols} - rd_{cols}) / 2 \end{bmatrix}\end{split}\]
+

where:

+
+\[\begin{split}Q = \begin{bmatrix} rs_{11} & rs_{12} \\ + rs_{21} & rs_{22} \\ + rs_{31} & rs_{32} \end{bmatrix}\end{split}\]
+
+
+

Data scaling

+

SPM gets the DICOM scaling, offset for the image (‘RescaleSlope’, +‘RescaleIntercept’). It writes these scalings into the nifti header. +Then it writes the raw image data (unscaled) to disk. Obviously these +will have the current scalings applied when the nifti image is read again.

+

A comment in the code here says that the data are not scaled by the +maximum amount. I assume by this they mean that the DICOM scaling may +not be the maximum scaling, whereas the standard SPM image write is, +hence the difference, because they are using the DICOM scaling rather +then their own. The comment continues by saying that the scaling as +applied (the DICOM - not maximum - scaling) can lead to rounding errors +but that it will get around some unspecified problems.

+
+
+ + +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/dicom/dicom_niftiheader.html b/dicom/dicom_niftiheader.html new file mode 100644 index 0000000000..7768830853 --- /dev/null +++ b/dicom/dicom_niftiheader.html @@ -0,0 +1,183 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
+
+ +
+
+

NiBabel

+

Access a cacophony of neuro-imaging file formats

+
+
+ + + + +
+
+
+
+ +
+

DICOM Tags in the NIfTI Header

+

NIfTI images include an extended header (see the NIfTI Extensions Standard) +to store, amongst others, DICOM tags and attributes. When NiBabel loads a NIfTI +file containing DICOM information (a NIfTI extension with ecode == 2), it +parses it and returns a pydicom dataset as the content of the NIfTI extension. +This can be read and written to in order to facilitate communication with +software that uses specific DICOM codes found in the NIfTI header.

+

For example, the commercial PMOD software stores the Frame Start and Duration +times of images using the DICOM tags (0055, 1001) and (0055, 1004). Here’s an +example of an image created in PMOD with those stored times accessed through +nibabel.

+
>> import nibabel as nib
+>> nim = nib.load('pmod_pet.nii')
+>> dcmext = nim.header.extensions[0]
+>> dcmext
+Nifti1Extension('dicom', '(0054, 1001) Units                               CS: 'Bq/ml'
+(0055, 0010) Private Creator                     LO: 'PMOD_1'
+(0055, 1001) [Frame Start Times Vector]          FD: [0.0, 30.0, 60.0, ..., 13720.0, 14320.0]
+(0055, 1004) [Frame Durations (ms) Vector]       FD: [30000.0, 30000.0, 30000.0,600000.0, 600000.0]'))
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +

Tag

Name

Value

(0054, 1001)

Units

CS: ‘Bq/ml’

(0055, 0010)

Private Creator

LO: ‘PMOD_1’

(0055, 1001)

[Frame Start Times Vector]

FD: [0.0, 30.0, 60.0, …, 13720.0, 14320.0

(0055, 1004)

[Frame Durations (ms) Vector]

FD: [30000.0, 30000.0, 30000.0, …, 600000.0, 600000.0

+

Access each value as you would with pydicom:

+
>> ds = dcmext.get_content()
+>> start_times = ds[0x0055, 0x1001].value
+>> durations   = ds[0x0055, 0x1004].value
+
+
+

Creating a PMOD-compatible header is just as easy:

+
>> nim = nib.load('pet.nii')
+>> nim.header.extensions
+[]
+>> from dicom.dataset import Dataset
+>> ds = Dataset()
+>> ds.add_new((0x0054,0x1001),'CS','Bq/ml')
+>> ds.add_new((0x0055,0x0010),'LO','PMOD_1')
+>> ds.add_new((0x0055,0x1001),'FD',[0.,30.,60.,13720.,14320.])
+>> ds.add_new((0x0055,0x1004),'FD',[30000.,30000.,30000.,600000.,600000.])
+>> dcmext = nib.nifti1.Nifti1DicomExtension(2,ds)  # Use DICOM ecode 2
+>> nim.header.extensions.append(dcmext)
+>> nib.save(nim,'pet_withdcm.nii')
+
+
+

Be careful! Many imaging tools don’t maintain information in the extended +header, so it’s possible [likely] that this information may be lost during +routine use. You’ll have to keep track, and re-write the information if +required.

+

Optional Dependency Note: If pydicom is not installed, nibabel uses a generic +nibabel.nifti1.Nifti1Extension header instead of parsing DICOM data.

+
+ + +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/dicom/dicom_orientation.html b/dicom/dicom_orientation.html new file mode 100644 index 0000000000..264fb695f6 --- /dev/null +++ b/dicom/dicom_orientation.html @@ -0,0 +1,461 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + + +
+
+ +
+
+

NiBabel

+

Access a cacophony of neuro-imaging file formats

+
+
+ + + + +
+
+
+
+ +
+

Defining the DICOM orientation

+
+

DICOM patient coordinate system

+

First we define the standard DICOM patient-based coordinate system. +This is what DICOM means by x, y and z axes in its orientation +specification. From section C.7.6.2.1.1 of the DICOM object +definitions (2009):

+
+

If Anatomical Orientation Type (0010,2210) is absent or has a value +of BIPED, the x-axis is increasing to the left hand side of the +patient. The y-axis is increasing to the posterior side of the +patient. The z-axis is increasing toward the head of the patient.

+
+

(we’ll ignore the quadupeds for now).

+

In a way it’s funny to call this the ‘patient-based’ coordinate system. +‘Doctor-based coordinate system’ is a better name. Think of a doctor +looking at the patient from the foot of the scanner bed. Imagine the +doctor’s right hand held in front of her like Spiderman about to shoot a +web, with her palm towards the patient, defining a right-handed +coordinate system. Her thumb points to her right (the patient’s left), +her index finger points down, and the middle finger points at the +patient.

+
+
+

DICOM pixel data

+
+
C.7.6.3.1.4 - Pixel Data

Pixel Data (7FE0,0010) for this image. The order of pixels sent for +each image plane is left to right, top to bottom, i.e., the upper +left pixel (labeled 1,1) is sent first followed by the remainder of +row 1, followed by the first pixel of row 2 (labeled 2,1) then the +remainder of row 2 and so on.

+
+
+

The resulting pixel array then has size (‘Rows’, ‘Columns’), with +row-major storage (rows first, then columns). We’ll call this the DICOM +pixel array.

+
+
+

Pixel spacing

+
+
Section 10.7.1.3: Pixel Spacing

The first value is the row spacing in mm, that is the spacing between +the centers of adjacent rows, or vertical spacing. The second value +is the column spacing in mm, that is the spacing between the centers +of adjacent columns, or horizontal spacing.

+
+
+
+
+

DICOM voxel to patient coordinate system mapping

+

See:

+ +

See wikipedia direction cosine for a definition of direction cosines.

+

From section C.7.6.2.1.1 of the DICOM object definitions (2009):

+
+

The Image Position (0020,0032) specifies the x, y, and z coordinates +of the upper left hand corner of the image; it is the center of the +first voxel transmitted. Image Orientation (0020,0037) specifies the +direction cosines of the first row and the first column with respect +to the patient. These Attributes shall be provide as a pair. Row +value for the x, y, and z axes respectively followed by the Column +value for the x, y, and z axes respectively.

+
+

From Section C.7.6.1.1.1 we see that the ‘positive row axis’ is left to +right, and is the direction of the rows, given by the direction of last +pixel in the first row from the first pixel in that row. Similarly the +‘positive column axis’ is top to bottom and is the direction of the +columns, given by the direction of the last pixel in the first column +from the first pixel in that column.

+

Let’s rephrase: the first three values of ‘Image Orientation Patient’ +are the direction cosine for the ‘positive row axis’. That is, they +express the direction change in (x, y, z), in the DICOM patient +coordinate system (DPCS), as you move along the row. That is, as you +move from one column to the next. That is, as the column array index +changes. Similarly, the second triplet of values of ‘Image Orientation +Patient’ (img_ornt_pat[3:] in Python), are the direction cosine for +the ‘positive column axis’, and express the direction you move, in the +DPCS, as you move from row to row, and therefore as the row index +changes.

+

Further down section C.7.6.2.1.1 (RCS below is the reference coordinate +system - see DICOM object definitions section 3.17.1):

+
+

The Image Plane Attributes, in conjunction with the Pixel Spacing +Attribute, describe the position and orientation of the image slices +relative to the patient-based coordinate system. In each image frame +the Image Position (Patient) (0020,0032) specifies the origin of the +image with respect to the patient-based coordinate system. RCS and +the Image Orientation (Patient) (0020,0037) attribute values specify +the orientation of the image frame rows and columns. The mapping of +pixel location (i, j) to the RCS is calculated as follows:

+
+\[\begin{split}\begin{bmatrix} P_x\\ + P_y\\ + P_z\\ + 1 \end{bmatrix} = +\begin{bmatrix} X_x\Delta{i} & Y_x\Delta{j} & 0 & S_x \\ + X_y\Delta{i} & Y_y\Delta{j} & 0 & S_y \\ + X_z\Delta{i} & Y_z\Delta{j} & 0 & S_z \\ + 0 & 0 & 0 & 1 \end{bmatrix} +\begin{bmatrix} i\\ + j\\ + 0\\ + 1 \end{bmatrix} += M +\begin{bmatrix} i\\ + j\\ + 0\\ + 1 \end{bmatrix}\end{split}\]
+

Where:

+
    +
  1. \(P_{xyz}\) : The coordinates of the voxel (i,j) in the frame’s +image plane in units of mm.

  2. +
  3. \(S_{xyz}\) : The three values of the Image Position (Patient) +(0020,0032) attributes. It is the location in mm from the origin +of the RCS.

  4. +
  5. \(X_{xyz}\) : The values from the row (X) direction cosine of the +Image Orientation (Patient) (0020,0037) attribute.

  6. +
  7. \(Y_{xyz}\) : The values from the column (Y) direction cosine of the +Image Orientation (Patient) (0020,0037) attribute.

  8. +
  9. \(i\) : Column index to the image plane. The first column is index +zero.

  10. +
  11. \(\Delta{i}\): Column pixel resolution of the Pixel Spacing +(0028,0030) attribute in units of mm.

  12. +
  13. \(j\) : Row index to the image plane. The first row index is zero.

  14. +
  15. \(\Delta{j}\) - Row pixel resolution of the Pixel Spacing +(0028,0030) attribute in units of mm.

  16. +
+
+
+
+

(i, j), columns, rows in DICOM

+

We stop to ask ourselves, what does DICOM mean by voxel (i, j)?

+

Isn’t that obvious? Oh dear, no it isn’t. See the +DICOM voxel to patient coordinate system mapping formula above. In particular, you’ll see:

+
    +
  • \(i\) : Column index to the image plane. The first column is index zero.

  • +
  • \(j\) : Row index to the image plane. The first row index is zero.

  • +
+

That is, if we have the DICOM pixel data as defined above, and +we call that pixel_array, then voxel (i, j) in the notation above is +given by pixel_array[j, i].

+

What does this mean? It means that, if we want to apply the formula +above to array indices in pixel_array, we first have to apply a +column / row flip to the indices. Say \(M_{pixar}\) (sorry) is the affine +to go from array indices in pixel_array to mm in the DPCS. Then, +given \(M\) above:

+
+\[\begin{split}M_{pixar} = M \left(\begin{smallmatrix}0 & 1 & 0 & 0\\1 & 0 & 0 & 0\\0 & 0 & 1 & 0\\0 & 0 & 0 & 1\end{smallmatrix}\right)\end{split}\]
+
+
+

DICOM affines again

+

The (i, j), columns, rows in DICOM is rather confusing, so we’re going to rephrase +the affine mapping; we’ll use \(r\) for the row index (instead of \(j\) +above), and \(c\) for the column index (instead of \(i\)).

+

Next we define a flipped version of ‘ImageOrientationPatient’, \(F\), that +has flipped columns. Thus if the vector of 6 values in +‘ImageOrientationPatient’ are \((i_1 .. i_6)\), then:

+
+\[\begin{split}F = \begin{bmatrix} i_4 & i_1 \\ + i_5 & i_2 \\ + i_6 & i_3 \end{bmatrix}\end{split}\]
+

Now the first column of F contains what the DICOM docs call the ‘column +(Y) direction cosine’, and second column contains the ‘row (X) direction +cosine’. We prefer to think of these as (respectively) the row index +direction cosine and the column index direction cosine.

+

Now we can rephrase the DICOM affine mapping with:

+
+
+

DICOM affine formula

+
+\[\begin{split}\begin{bmatrix} P_x\\ + P_y\\ + P_z\\ + 1 \end{bmatrix} = +\begin{bmatrix} F_{11}\Delta{r} & F_{12}\Delta{c} & 0 & S_x \\ + F_{21}\Delta{r} & F_{22}\Delta{c} & 0 & S_y \\ + F_{31}\Delta{r} & F_{32}\Delta{c} & 0 & S_z \\ + 0 & 0 & 0 & 1 \end{bmatrix} +\begin{bmatrix} r\\ + c\\ + 0\\ + 1 \end{bmatrix} += A +\begin{bmatrix} r\\ + c\\ + 0\\ + 1 \end{bmatrix}\end{split}\]
+

Where:

+
    +
  • \(P_{xyz}\) : The coordinates of the voxel (c, r) in the frame’s image +plane in units of mm.

  • +
  • \(S_{xyz}\) : The three values of the Image Position (Patient) +(0020,0032) attributes. It is the location in mm from the origin of +the RCS.

  • +
  • \(F_{:,1}\) : The values from the column (Y) direction cosine of the +Image Orientation (Patient) (0020,0037) attribute - see above.

  • +
  • \(F_{:,2}\) : The values from the row (X) direction cosine of the Image +Orientation (Patient) (0020,0037) attribute - see above.

  • +
  • \(r\) : Row index to the image plane. The first row index is zero.

  • +
  • \(\Delta{r}\) - Row pixel resolution of the Pixel Spacing (0028,0030) +attribute in units of mm.

  • +
  • \(c\) : Column index to the image plane. The first column is index zero.

  • +
  • \(\Delta{c}\): Column pixel resolution of the Pixel Spacing (0028,0030) +attribute in units of mm.

  • +
+

For later convenience we also define values useful for 3D volumes:

+
    +
  • \(s\) : Slice index to the slice plane. The first slice index is zero.

  • +
  • \(\Delta{s}\) - Spacing in mm between slices.

  • +
+
+
+

Getting a 3D affine from a DICOM slice or list of slices

+

Let us say, we have a single DICOM file, or a list of DICOM files that +we believe to be a set of slices from the same volume. We’ll call the +first the single slice case, and the second, multi slice.

+

In the multi slice case, we can assume that the +‘ImageOrientationPatient’ field is the same for all the slices.

+

We want to get the affine transformation matrix \(A\) that maps from voxel +coordinates in the DICOM file(s), to mm in the DICOM patient coordinate system.

+

By voxel coordinates, we mean coordinates of form \((r, c, s)\) - the row, +column and slice indices - as for the DICOM affine formula.

+

In the single slice case, the voxel coordinates are just the indices +into the pixel array, with the third (slice) coordinate always being 0.

+

In the multi-slice case, we have arranged the slices in ascending or +descending order, where slice numbers range from 0 to \(N-1\) - where \(N\) +is the number of slices - and the slice coordinate is a number on this +scale.

+

We know, from DICOM affine formula, that the first, second and +fourth columns in \(A\) are given directly by the (flipped) +‘ImageOrientationPatient’, ‘PixelSpacing’ and ‘ImagePositionPatient’ +field of the first (or only) slice.

+

Our job then is to fill the first three rows of the third column of \(A\). +Let’s call this the vector \(\mathbf{k}\) with values \(k_1, k_2, k_3\).

+
+

DICOM affine Definitions

+

See also the definitions in DICOM affine formula. In addition

+
    +
  • \(T^1\) is the 3 element vector of the ‘ImagePositionPatient’ field of +the first header in the list of headers for this volume.

  • +
  • \(T^N\) is the ‘ImagePositionPatient’ vector for the last header in the +list for this volume, if there is more than one header in the volume.

  • +
  • vector \(\mathbf{n} = (n_1, n_2, n_3)\) is the result of taking the +cross product of the two columns of \(F\) from +DICOM affine formula.

  • +
+
+
+

Derivations

+

For the single slice case we just fill \(\mathbf{k}\) with \(\mathbf{n} \cdot +\Delta{s}\) - on the basis that the Z dimension should be +right-handed orthogonal to the X and Y directions.

+

For the multi-slice case, we can fill in \(\mathbf{k}\) by using the information +from \(T^N\), because \(T^N\) is the translation needed to take the +first voxel in the last (slice index = \(N-1\)) slice to mm space. So:

+
+\[\begin{split}\left(\begin{smallmatrix}T^N\\1\end{smallmatrix}\right) = A \left(\begin{smallmatrix}0\\0\\N - 1\\1\end{smallmatrix}\right)\end{split}\]
+

From this it follows that:

+
+\[\begin{Bmatrix}k_{{1}} : \frac{T^{N}_{{1}} - T^{1}_{{1}}}{N - 1}, & k_{{2}} : \frac{T^{N}_{{2}} - T^{1}_{{2}}}{N - 1}, & k_{{3}} : \frac{T^{N}_{{3}} - T^{1}_{{3}}}{N - 1}\end{Bmatrix}\]
+

and therefore:

+
+
+

3D affine formulae

+
+\[ \begin{align}\begin{aligned}\begin{split}A_{multi} = \left(\begin{smallmatrix}F_{{11}} \Delta{r} & F_{{12}} \Delta{c} & \frac{T^{N}_{{1}} - T^{1}_{{1}}}{N - 1} & T^{1}_{{1}}\\F_{{21}} \Delta{r} & F_{{22}} \Delta{c} & \frac{T^{N}_{{2}} - T^{1}_{{2}}}{N - 1} & T^{1}_{{2}}\\F_{{31}} \Delta{r} & F_{{32}} \Delta{c} & \frac{T^{N}_{{3}} - T^{1}_{{3}}}{N - 1} & T^{1}_{{3}}\\0 & 0 & 0 & 1\end{smallmatrix}\right)\end{split}\\\begin{split}A_{single} = \left(\begin{smallmatrix}F_{{11}} \Delta{r} & F_{{12}} \Delta{c} & \Delta{s} n_{{1}} & T^{1}_{{1}}\\F_{{21}} \Delta{r} & F_{{22}} \Delta{c} & \Delta{s} n_{{2}} & T^{1}_{{2}}\\F_{{31}} \Delta{r} & F_{{32}} \Delta{c} & \Delta{s} n_{{3}} & T^{1}_{{3}}\\0 & 0 & 0 & 1\end{smallmatrix}\right)\end{split}\end{aligned}\end{align} \]
+

See derivations/spm_dicom_orient.py for the derivations and +some explanations.

+

For a single slice \(N=1\) the affine matrix is \(A_{single}\). In this +case, the slice spacing \(\Delta{s}\) may be obtained by the Spacing +Between Slices (0018,0088) attribute in units of mm, if it exists.

+
+
+
+

Working out the Z coordinates for a set of slices

+

We may have the problem (see e.g. Sorting files into volumes) of trying +to sort a set of slices into anatomical order. For this we want to use +the orientation information to tell us where the slices are in space, +and therefore, what order they should have.

+

To do this sorting, we need something that is proportional, plus a +constant, to the voxel coordinate for the slice (the value for the slice +index).

+

Our DICOM might have the ‘SliceLocation’ field (0020,1041). +‘SliceLocation’ seems to be proportional to slice location, at least for +some GE and Philips DICOMs I was looking at. But, there is a more +reliable way (that doesn’t depend on this field), and uses only the very +standard ‘ImageOrientationPatient’ and ‘ImagePositionPatient’ fields.

+

Consider the case where we have a set of slices, of unknown order, from +the same volume.

+

Now let us say we have one of these slices - slice \(i\). We have the +affine for this slice from the calculations above, for a single slice +(\(A_{single}\)).

+

Now let’s say we have another slice \(j\) from the same volume. It will +have the same affine, except that the ‘ImagePositionPatient’ field will +change to reflect the different position of this slice in space. Let us +say that there a translation of \(d\) slices between \(i\) and \(j\). If +\(A_i\) (\(A\) for slice \(i\)) is \(A_{single}\) then \(A_j\) for \(j\) is given +by:

+
+\[\begin{split}A_j = A_{single} \left(\begin{smallmatrix}1 & 0 & 0 & 0\\0 & 1 & 0 & 0\\0 & 0 & 1 & d\\0 & 0 & 0 & 1\end{smallmatrix}\right)\end{split}\]
+

and ‘ImagePositionPatient’ for \(j\) is:

+
+\[\begin{split}T^j = \left(\begin{smallmatrix}T^{1}_{{1}} + \Delta{s} d n_{{1}}\\T^{1}_{{2}} + \Delta{s} d n_{{2}}\\T^{1}_{{3}} + \Delta{s} d n_{{3}}\end{smallmatrix}\right)\end{split}\]
+

Remember that the third column of \(A\) gives the vector resulting from a +unit change in the slice voxel coordinate. So, the +‘ImagePositionPatient’ of slice - say slice \(j\) - can be thought of the +addition of two vectors \(T^j = \mathbf{a} + \mathbf{b}\), where +\(\mathbf{a}\) is the position of the first voxel in some slice (here +slice 1, therefore \(\mathbf{a} = T^1\)) and \(\mathbf{b}\) is \(d\) times the +third column of \(A\). Obviously \(d\) can be negative or positive. This +leads to various ways of recovering something that is proportional to +\(d\) plus a constant. The algorithm suggested in this ITK post on +ordering slices - and the one used by SPM - is to take the inner +product of \(T^j\) with the unit vector component of third column of +\(A_j\) - in the descriptions here, this is the vector \(\mathbf{n}\):

+
+\[T^j \cdot \mathbf{c} = \left(\begin{smallmatrix}T^{1}_{{1}} n_{{1}} + T^{1}_{{2}} n_{{2}} + T^{1}_{{3}} n_{{3}} + \Delta{s} d n_{{1}}^{2} + \Delta{s} d n_{{2}}^{2} + \Delta{s} d n_{{3}}^{2}\end{smallmatrix}\right)\]
+

This is the distance of ‘ImagePositionPatient’ along the slice direction +cosine.

+

The unknown \(T^1\) terms pool into a constant, and the operation has the +neat feature that, because the \(n_{123}^2\) terms, by definition, sum to 1, +the whole can be expressed as \(\lambda + \Delta{s} d\) - i.e. it is +equal to the slice voxel size (\(\Delta{s}\)) multiplied by \(d\), +plus a constant.

+

Again, see derivations/spm_dicom_orient.py for the derivations.

+
+
+ + +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/dicom/siemens_csa.html b/dicom/siemens_csa.html new file mode 100644 index 0000000000..77493607bd --- /dev/null +++ b/dicom/siemens_csa.html @@ -0,0 +1,248 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
+
+ +
+
+

NiBabel

+

Access a cacophony of neuro-imaging file formats

+
+
+ + + + +
+
+
+
+ +
+

Siemens format DICOM with CSA header

+

Recent Siemens DICOM images have useful information stored in a private +header. We’ll call this the CSA header.

+
+

CSA header

+

See this Siemens Syngo DICOM conformance statement, and a GDCM +Siemens header dump.

+

The CSA header is stored in DICOM private tags. In the images we are +looking at, there are several relevant tags:

+
(0029, 1008) [CSA Image Header Type]             OB: 'IMAGE NUM 4 '
+(0029, 1009) [CSA Image Header Version]          OB: '20100114'
+(0029, 1010) [CSA Image Header Info]             OB: Array of 11560 bytes
+(0029, 1018) [CSA Series Header Type]            OB: 'MR'
+(0029, 1019) [CSA Series Header Version]         OB: '20100114'
+(0029, 1020) [CSA Series Header Info]            OB: Array of 80248 bytes
+
+
+

In our case we want to read the ‘CSAImageHeaderInfo’.

+

From the SPM (SPM8) code spm_dicom_headers.m

+

The CSAImageHeaderInfo and the CSA Series Header Info fields are of the +same format. The fields can be of two types, CSA1 and CSA2.

+

Both are always little-endian, whatever the machine endian is.

+

The CSA2 format begins with the string ‘SV10’, the CSA1 format does +not.

+

The code below keeps track of the position within the CSA header +stream. We’ll call this csa_position. At this point (after +reading the 8 bytes of the header), csa_position == 8. There’s a +variable that sets the last byte position in the file that is sensibly +still CSA header, and we’ll call that csa_max_pos.

+
+
+

CSA1

+
+

Start header

+
    +
  1. n_tags, uint32, number of tags. Number of tags should apparently be +between 1 and 128. If this is not true we just abort and move to +csa_max_pos.

  2. +
  3. unused, uint32, apparently has value 77

  4. +
+
+
+

Each tag

+
    +
  1. name : S64, null terminated string 64 bytes

  2. +
  3. vm : int32

  4. +
  5. vr : S4, first 3 characters only

  6. +
  7. syngodt : int32

  8. +
  9. nitems : int32

  10. +
  11. xx : int32 - apparently either 77 or 205

  12. +
+

nitems gives the number of items in the tag. The items follow +directly after the tag.

+
+
+

Each item

+
    +
  1. xx : int32 * 4 . The first of these seems to be the length of the +item in bytes, modified as below.

  2. +
+

At this point SPM does a check, by calculating the length of this item +item_len with xx[0] - the nitems of the first read tag. +If item_len is less than 0 or greater than +csa_max_pos-csa_position (the remaining number of bytes to read in +the whole header) then we break from the item reading loop, +setting the value below to ‘’.

+

Then we calculate item_len rounded up to the nearest 4 byte boundary +tp get next_item_pos.

+
    +
  1. value : uint8, item_len.

  2. +
+

We set the stream position to next_item_pos.

+
+
+
+

CSA2

+
+

Start header

+
    +
  1. hdr_id : S4 == ‘SV10’

  2. +
  3. unused1 : uint8, 4

  4. +
  5. n_tags, uint32, number of tags. Number of tags should apparently be +between 1 and 128. If this is not true we just abort and move to +csa_max_pos.

  6. +
  7. unused2, uint32, apparently has value 77

  8. +
+
+
+

Each tag

+
    +
  1. name : S64, null terminated string 64 bytes

  2. +
  3. vm : int32

  4. +
  5. vr : S4, first 3 characters only

  6. +
  7. syngodt : int32

  8. +
  9. nitems : int32

  10. +
  11. xx : int32 - apparently either 77 or 205

  12. +
+

nitems gives the number of items in the tag. The items follow +directly after the tag.

+
+
+

Each item

+
    +
  1. xx : int32 * 4 . The first of these seems to be the length of the +item in bytes, modified as below.

  2. +
+

Now there’s a different length check from CSA1. item_len is given +just by xx[1]. If item_len > csa_max_pos - csa_position +(the remaining bytes in the header), then we just read the remaining +bytes in the header (as above) into value below, as uint8, move the +filepointer to the next 4 byte boundary, and give up reading.

+
    +
  1. value : uint8, item_len.

  2. +
+

We set the stream position to the next 4 byte boundary.

+
+
+
+ + +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/dicom/spm_dicom.html b/dicom/spm_dicom.html new file mode 100644 index 0000000000..51066dc063 --- /dev/null +++ b/dicom/spm_dicom.html @@ -0,0 +1,409 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + + +
+
+ +
+
+

NiBabel

+

Access a cacophony of neuro-imaging file formats

+
+
+ + + + +
+
+
+
+ +
+

SPM DICOM conversion

+

These are some notes on the algorithms that SPM uses to convert from +DICOM to nifti. There are other notes in Siemens mosaic format.

+

The relevant SPM files are spm_dicom_headers.m, +spm_dicom_dict.mat and spm_dicom_convert.m. These notes refer +the version in SPM8, as of around January 2010.

+
+

spm_dicom_dict.mat

+

This is obviously a Matlab .mat file. It contains variables +group and element, and values, where values is a struct +array, one element per (group, element) pair, with fields name and +vr (the last a cell array).

+
+
+

spm_dicom_headers.m

+

Reads the given DICOM files into a struct. It looks like this was +written by John Ahsburner (JA). Relevant fixes are:

+
+

File opening

+

When opening the DICOM file, SPM (subfunction readdicomfile)

+
    +
  1. opens as little endian

  2. +
  3. reads 4 characters starting at pos 128

  4. +
  5. checks if these are DICM; if so then continues file read; +otherwise, tests to see if this is what SPM calls truncated DICOM +file format - lacking 128 byte lead in and DICM string:

    +
      +
    1. Seeks to beginning of file

    2. +
    3. Reads two unsigned short values into group and tag

    4. +
    5. If the (group, element) pair exist in +spm_dicom_dict.mat, then set file pointer to 0 and continue +read with read_dicom subfunction..

    6. +
    7. If group == 8 and element == 0, this is apparently the +signature for a ‘GE Twin+excite’ for which JA notes there is no +documentation; set file pointer to 0 and continue read with +read_dicom subfunction.

    8. +
    9. Otherwise - crash out with error saying that this is not DICOM file.

    10. +
    +
  6. +
+
+
+

tag read for Philips Integra

+

The read_dicom subfunction reads a tag, then has a loop during which +the tag is processed (by setting values into the return structure). At +the end of the loop, it reads the next tag. The loop breaks when the +current tag is empty, or is the item delimitation tag (group=FFFE, +element=E00D).

+

After it has broken out of the loop, if the last tag was (FFFE, E00D) +(item delimitation tag), and the tag length was not 0, then SPM sets the +file pointer back by 4 bytes from the current position. JA comments +that he didn’t find that in the standard, but that it seemed to be +needed for the Philips Integra.

+
+
+

Tag length

+

Tag lengths as read in read_tag subfunction. If current format is +explicit (as in ‘explicit little endian’):

+
    +
  1. For VR of x00x00, then group, element must be (FFFE, E00D) (item +delimitation tag). JA comments that GE ‘ImageDelimitationItem’ has +no VR, just 4 0 bytes. In this case the tag length is zero, and we +read another two bytes ahead.

  2. +
+

There’s a check for not-even tag length. If not even:

+
    +
  1. 4294967295 appears to be OK - and decoded as Inf for tag length.

  2. +
  3. 13 appears to mean 10 and is reset to be 10

  4. +
  5. Any other odd number is not valid and gives a tag length of 0

  6. +
+
+
+

SQ VR type (Sequence of items type)

+

tag length of 13 set to tag length 10.

+
+
+
+

spm_dicom_convert.m

+

Written by John Ashburner and Jesper Andersson.

+
+

File categorization

+

SPM makes a special case of Siemens ‘spectroscopy images’. These are +images that have ‘SOPClassUID’ == ‘1.3.12.2.1107.5.9.1’ and the private +tag of (29, 1210); for these it pulls out the affine, and writes a +volume of ones corresponding to the acquisition planes.

+

For images that are not spectroscopy:

+
    +
  • Discards images that do not have any of (‘MR’, ‘PT’, ‘CT’) in ‘Modality’ field.

  • +
  • Discards images lacking any of ‘StartOfPixelData’, ‘SamplesperPixel’, +‘Rows’, ‘Columns’, ‘BitsAllocated’, ‘BitsStored’, ‘HighBit’, +‘PixelRespresentation’

  • +
  • Discards images lacking any of ‘PixelSpacing’, ‘ImagePositionPatient’, +‘ImageOrientationPatient’ - presumably on the basis that SPM cannot +reconstruct the affine.

  • +
  • Fields ‘SeriesNumber’, ‘AcquisitionNumber’ and ‘InstanceNumber’ are +set to 1 if absent.

  • +
+

Next SPM distinguishes between Siemens mosaic format and standard DICOM.

+

Mosaic images are those with the Siemens private tag:

+
(0029, 1009) [CSA Image Header Version]          OB: '20100114'
+
+
+

and a readable CSA header (see Siemens mosaic format), and with +non-empty fields from that header of ‘AcquisitionMatrixText’, +‘NumberOfImagesInMosaic’, and with non-zero ‘NumberOfImagesInMosaic’. The +rest are standard DICOM.

+

For converting mosaic format, see Siemens mosaic format. The rest of this +page refers to standard (slice by slice) DICOMs.

+
+
+

Sorting files into volumes

+
+

First pass

+

Take first header, put as start of first volume. For each subsequent header:

+
    +
  1. Get ICE_Dims if present. Look for Siemens ‘CSAImageHeaderInfo’, +check it has a ‘name’ field, then pull dimensions out of ‘ICE_Dims’ +field in form of 9 integers separated by ‘_’, where ‘X’ in this +string replaced by ‘-1’ - giving ‘ICE1’

  2. +
+

Then, for each currently identified volume:

+
    +
  1. If we have ICE1 above, and we do have ‘CSAIMageHeaderInfo’, with a +‘name’, in the first header in this volume, then extract ICE dims in +the same way as above, for the first header in this volume, and check +whether all but ICE1[6:8] are the same as ICE2. Set flag that all +ICE dims are identical for this volume. Set this flag to True if we +did not have ICE1 or CSA information.

  2. +
  3. Match the current header to the current volume iff the following match:

    +
      +
    1. SeriesNumber

    2. +
    3. Rows

    4. +
    5. Columns

    6. +
    7. ImageOrientationPatient (to tolerance of sum squared difference 1e-4)

    8. +
    9. PixelSpacing (to tolerance of sum squared difference 1e-4)

    10. +
    11. ICE dims as defined above

    12. +
    13. ImageType (iff imagetype exists in both)

    14. +
    15. SequenceName (iff sequencename exists in both)

    16. +
    17. SeriesInstanceUID (iff exists in both)

    18. +
    19. EchoNumbers (iff exists in both)

    20. +
    +
  4. +
  5. If the current header matches the current volume, insert it there, +otherwise make a new volume for this header

  6. +
+
+
+

Second pass

+

We now have a list of volumes, where each volume is a list of headers +that may match.

+

For each volume:

+
    +
  1. Estimate the z direction cosine by (effectively) finding the cross +product of the x and y direction cosines contained in +‘ImageOrientationPatient’ - call this z_dir_cos

  2. +
  3. For each header in this volume, get the z coordinate by taking the +dot product of the ‘ImagePositionPatient’ vector and z_dir_cos +(see Working out the Z coordinates for a set of slices).

  4. +
  5. Sort the headers according to this estimated z coordinate.

  6. +
  7. If this volume is more than one slice, and there are any slices with +the same z coordinate (as defined above), run the +Possible volume resort on this volume - on the basis that it may +have caught more than one volume-worth of slices. Return one or more +volume’s worth of lists.

  8. +
+
+
+

Final check

+

For each volume, recalculate z coordinate as above. Calculate the z +gaps. Subtract the mean of the z gaps from all z gaps. If the average of the +(gap-mean(gap)) is greater than 1e-4, then print a warning that there +are missing DICOM files.

+
+
+

Possible volume resort

+

This step happens if there were volumes with slices having the same z +coordinate in the Second pass step above. The resort is on the +set of DICOM headers that were in the volume, for which there were +slices with identical z coordinates. We’ll call the list of headers +that the routine is still working on - work_list.

+
    +
  1. If there is no ‘InstanceNumber’ field for the first header in +work_list, bail out.

  2. +
  3. Print a message about the ‘AcquisitionNumber’ not changing from +volume to volume. This may be a relic from previous code, because +this version of SPM does not use the ‘AcquisitionNumber’ field except +for making filenames.

  4. +
  5. Calculate the z coordinate as for Second pass, for each +DICOM header.

  6. +
  7. Sort the headers by ‘InstanceNumber’

  8. +
  9. If any headers have the same ‘InstanceNumber’, then discard all but +the first header with the same number. At this point the remaining +headers in work_list will have different ‘InstanceNumber’s, but +may have the same z coordinate.

  10. +
  11. Now sort by z coordinate

  12. +
  13. If there are N headers, make a N length vector of flags +is_processed, for which all values == False

  14. +
  15. Make an output list of header lists, call it hdr_vol_out, set to empty.

  16. +
  17. While there are still any False elements in is_processed:

    +
      +
    1. Find first header for which corresponding is_processed is +False - call this hdr_to_check

    2. +
    3. Collect indices (in work_list) of headers which have the same +z coordinate as hdr_to_check, call this list +z_same_indices.

    4. +
    5. Sort work_list[z_same_indices] by ‘InstanceNumber’

    6. +
    7. For each index in z_same_indices such that i indexes the +indices, and zsind is z_same_indices[i]: append header +corresponding to zsind to hdr_vol_out[i]. This assumes +that the original work_list contained two or more volumes, +each with an identical set of z coordinates.

    8. +
    9. Set corresponding is_processed flag to True for all z_same_indices.

    10. +
    +
  18. +
  19. Finally, if the headers in work_list have ‘InstanceNumber’s that +cannot be sorted to a sequence ascending in units of 1, or if any +of the lists in hdr_vol_out have different lengths, emit a +warning about missing DICOM files.

  20. +
+
+
+
+

Writing DICOM volumes

+

This means - writing DICOM volumes from standard (slice by slice) DICOM +datasets rather than Siemens mosaic format.

+
+

Making the affine

+

We need the (4,4) affine \(A\) going from voxel (array) coordinates in the +DICOM pixel data, to mm coordinates in the DICOM patient coordinate system.

+

This section tries to explain how SPM achieves this, but I don’t +completely understand their method. See Getting a 3D affine from a DICOM slice or list of slices for +what I believe to be a simpler explanation.

+

First define the constants, matrices and vectors as in +DICOM affine Definitions.

+

\(N\) is the number of slices in the volume.

+

Then define the following matrices:

+
+\[ \begin{align}\begin{aligned}\begin{split}R = \left(\begin{smallmatrix}1 & a & 1 & 0\\1 & b & 0 & 1\\1 & c & 0 & 0\\1 & d & 0 & 0\end{smallmatrix}\right)\end{split}\\\begin{split}L = \left(\begin{smallmatrix}T^{1}_{{1}} & e & F_{{11}} \Delta{r} & F_{{12}} \Delta{c}\\T^{1}_{{2}} & f & F_{{21}} \Delta{r} & F_{{22}} \Delta{c}\\T^{1}_{{3}} & g & F_{{31}} \Delta{r} & F_{{32}} \Delta{c}\\1 & h & 0 & 0\end{smallmatrix}\right)\end{split}\end{aligned}\end{align} \]
+

For a volume with more than one slice (header), then \(a=1; b=1, c=N, d=1\). \(e, f, g\) are the values from \(T^N\), +and \(h == 1\).

+

For a volume with only one slice (header) \(a=0, b=0, c=1, d=0\) and \(e, +f, g, h\) are \(n_1 \Delta{s}, n_2 \Delta{s}, n_3 \Delta{s}, 0\).

+

The full transform appears to be \(A_{spm} = R L^{-1}\).

+

Now, SPM, don’t forget, is working in terms of Matlab array indexing, +which starts at (1,1,1) for a three dimensional array, whereas DICOM +expects a (0,0,0) start (see DICOM affine formula). In this +particular part of the SPM DICOM code, somewhat confusingly, the (0,0,0) +to (1,1,1) indexing is dealt with in the \(A\) transform, rather than the +analyze_to_dicom transformation used by SPM in other places. So, the +transform \(A_{spm}\) goes from (1,1,1) based voxel indices to mm. To +get the (0, 0, 0)-based transform we want, we need to pre-apply the +transform to take 0-based voxel indices to 1-based voxel indices:

+
+\[\begin{split}A = R L^{-1} \left(\begin{smallmatrix}1 & 0 & 0 & 1\\0 & 1 & 0 & 1\\0 & 0 & 1 & 1\\0 & 0 & 0 & 1\end{smallmatrix}\right)\end{split}\]
+

This formula with the definitions above result in the single and multi +slice formulae in 3D affine formulae.

+

See derivations/spm_dicom_orient.py for the derivations and +some explanations.

+
+
+

Writing the voxel data

+

Just apply scaling and offset from ‘RescaleSlope’ and ‘RescaleIntercept’ +for each slice and write volume.

+
+
+
+
+ + +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/doc-requirements.txt b/doc-requirements.txt deleted file mode 100644 index 4136b0f815..0000000000 --- a/doc-requirements.txt +++ /dev/null @@ -1,7 +0,0 @@ -# Auto-generated by tools/update_requirements.py --r requirements.txt -sphinx -matplotlib>=3.5 -numpydoc -texext -tomli; python_version < '3.11' diff --git a/doc/Makefile b/doc/Makefile deleted file mode 100644 index 86e84cfb85..0000000000 --- a/doc/Makefile +++ /dev/null @@ -1,104 +0,0 @@ -# Makefile for Sphinx documentation -# - -# The Python executable to be used -# -PYTHON ?= python - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = - - -# Internal variables. -BUILDROOT = ../build -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDROOT)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source - -# API generation -API_DIR = source/reference -.PHONY: help clean html web pickle htmlhelp latex changes linkcheck - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " api to make the auto-generated API files" - @echo " pickle to make pickle files (usable by e.g. sphinx-web)" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " changes to make an overview over all changed/added/deprecated items" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - @echo " gitwash-update update git workflow from source repo" - -clean: api-clean - -rm -rf $(BUILDROOT)/* - -rm *-stamp - -api-clean: - rm -rf $(API_DIR)/*.rst - -api: api-stamp -api-stamp: - @mkdir -p $(API_DIR) - $(PYTHON) tools/build_modref_templates.py nibabel $(API_DIR) False - @echo "Build API docs...done." - @touch $@ - -html-only: - mkdir -p $(BUILDROOT)/html $(BUILDROOT)/doctrees - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDROOT)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDROOT)/html." - -html: api-stamp html-only - -pickle: - mkdir -p $(BUILDROOT)/pickle $(BUILDROOT)/doctrees - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDROOT)/pickle - @echo - @echo "Build finished; now you can process the pickle files or run" - @echo " sphinx-web $(BUILDROOT)/pickle" - @echo "to start the sphinx-web server." - -web: pickle - -htmlhelp: - mkdir -p $(BUILDROOT)/htmlhelp $(BUILDROOT)/doctrees - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDROOT)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDROOT)/htmlhelp." - -latex: - mkdir -p $(BUILDROOT)/latex $(BUILDROOT)/doctrees - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDROOT)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDROOT)/latex." - @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ - "run these through (pdf)latex." - -changes: - mkdir -p $(BUILDROOT)/changes $(BUILDROOT)/doctrees - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDROOT)/changes - @echo - @echo "The overview file is in $(BUILDROOT)/changes." - -linkcheck: - mkdir -p $(BUILDROOT)/linkcheck $(BUILDROOT)/doctrees - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDROOT)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDROOT)/linkcheck/output.txt." - -doctest: api-clean - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDROOT)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in _build/doctest/output.txt." - -gitwash-update: - python ../tools/gitwash_dumper.py source nibabel --github-user=nipy \ - --project-url=http://nipy.org/nibabel \ - --project-ml-url=https://mail.python.org/mailman/listinfo/neuroimaging diff --git a/doc/README.rst b/doc/README.rst deleted file mode 100644 index d5fd9765e6..0000000000 --- a/doc/README.rst +++ /dev/null @@ -1,9 +0,0 @@ -##################### -Nibabel documentation -##################### - -To build the documentation, change to the root directory (containing -``pyproject.toml``) and run:: - - pip install -r doc-requirements.txt - make -C doc html diff --git a/doc/downloads b/doc/downloads deleted file mode 120000 index 7df87cd57f..0000000000 --- a/doc/downloads +++ /dev/null @@ -1 +0,0 @@ -source/downloads \ No newline at end of file diff --git a/doc/misc/header.py b/doc/misc/header.py deleted file mode 100644 index 6a9ef9d6eb..0000000000 --- a/doc/misc/header.py +++ /dev/null @@ -1,8 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## diff --git a/doc/misc/pylintrc b/doc/misc/pylintrc deleted file mode 100644 index 61439e3d20..0000000000 --- a/doc/misc/pylintrc +++ /dev/null @@ -1,92 +0,0 @@ -# PyLint configuration file for the project nibabel. -# -# This pylintrc file will use the default settings except for the -# naming conventions, which will allow for camel case naming as found -# in Java code or several libraries such as PyQt, etc. -# -# At some moment it was modified by yoh from the original one -# which can be found on debian systems at -# /usr/share/doc/pylint/examples/pylintrc_camelcase -# -# Just place it in ~/.pylintrc for user-wide installation or simply -# use within a call to pylint or export environment variable -# export PYLINTRC=$PWD/doc/misc/pylintrc - - -[BASIC] -# Regular expression which should only match correct module names -module-rgx=([a-z][a-z0-9_]*)$ - -attr-rgx=[a-z_][a-z0-9_]{,30} - -# Regular expression which should only match correct class names -class-rgx=[A-Z_]+[a-zA-Z0-9]+$ - -# Regular expression which should only match correct function names -function-rgx=[a-z_]+[a-z0-9_][a-z0-9]*$ - -# Regular expression which should only match correct method names -method-rgx=(([a-z_]|__)[a-z0-9_]*(__)?|test[a-zA-Z0-9_]*)$ - -# Regular expression which should only match correct argument names -argument-rgx=[a-z][a-z0-9]*_*[a-z0-9]*_*[a-z0-9]*_?$ - -# Regular expression which should only match correct variable names -variable-rgx=([a-z_]+[a-z0-9]*_*[a-z0-9]*_*[a-z0-9]*_?||(__[a-zA-Z0-9_]*__))$||[A-Z]+ - -# Regular expression which should only match correct module level names -# Default: (([A-Z_][A-Z1-9_]*)|(__.*__))$ -const-rgx=([a-z_]+[a-z0-9]*_*[a-z0-9]*_*[a-z0-9]*_?|__[a-zA-Z0-9_]*__)$||[A-Z]+ - - -[FORMAT] -indent-string=' ' - - -[DESIGN] - -# We are capable to follow that many, yes! -max-branchs = 20 - -# some base class constructors have quite a few arguments -max-args = 14 - -# and due to ClassWithCollections and conditional attributes classes by default have lots -# of attributes -max-attributes = 14 - -# some sci computation can't be handled efficiently without having -#lots of locals -max-locals = 35 - -[MESSAGES CONTROL] -# Disable the following PyLint messages: -# R0903 - Not enough public methods -# W0105 - String statement has no effect # often used for after-line doc -# W0142 - Used * or ** magic -# W0232 - Class has no __init__ method -# W0212 - Access to a protected member ... of a client class -# W0613 - Unused argument -# E1101 - Has no member (countless false-positives) -disable-msg=R0903,W0142,W0105,W0212,W0613,E1101 - -[REPORTS] - -# set the output format. Available formats are text, parseable, colorized and -# html -output-format=colorized - -# Include message's id in output -include-ids=yes - -# Tells whether to display a full report or only the messages -reports=yes - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -# FIXME -- something which needs fixing -# TODO -- future plan -# XXX -- some concern -# YYY -- comment/answer to above mentioned concern -notes=FIXME,TODO,XXX,YYY diff --git a/doc/pics/fslview_pst.png b/doc/pics/fslview_pst.png deleted file mode 100644 index 5c6d9e1b09..0000000000 Binary files a/doc/pics/fslview_pst.png and /dev/null differ diff --git a/doc/pics/gnuplot_ts.png b/doc/pics/gnuplot_ts.png deleted file mode 100644 index f760062b5d..0000000000 Binary files a/doc/pics/gnuplot_ts.png and /dev/null differ diff --git a/doc/pics/logo.png b/doc/pics/logo.png deleted file mode 100644 index 570d38f476..0000000000 Binary files a/doc/pics/logo.png and /dev/null differ diff --git a/doc/pics/reggie.svg b/doc/pics/reggie.svg deleted file mode 100644 index 962c73445b..0000000000 --- a/doc/pics/reggie.svg +++ /dev/null @@ -1,100 +0,0 @@ - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - diff --git a/doc/source/.gitattributes b/doc/source/.gitattributes deleted file mode 100644 index 17c53f0692..0000000000 --- a/doc/source/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -*.rst diff merge crlf diff --git a/doc/source/.gitignore b/doc/source/.gitignore deleted file mode 100644 index 28970a1fd9..0000000000 --- a/doc/source/.gitignore +++ /dev/null @@ -1 +0,0 @@ -_long_description.inc diff --git a/doc/source/README.txt b/doc/source/README.txt deleted file mode 100644 index 32b5df8c09..0000000000 --- a/doc/source/README.txt +++ /dev/null @@ -1,24 +0,0 @@ -====================== - Nibabel Documentation -====================== - -This directory contains the documentation for the Nibabel_ project. -The documentation is written in reST_ (reStructuredText) and uses -Sphinx_ to render html documentation from the rst source files. - -A relatively recent version of Sphinx_ is required to build the -documentation, at least 0.6.x. - -Use the ``Makefile`` to build the documentation. Common commands: - -Discover available make targets:: - - make help - -Clean up previous build:: - - make clean - -Build html documentation:: - - make html diff --git a/doc/source/_templates/indexsidebar.html b/doc/source/_templates/indexsidebar.html deleted file mode 100644 index 642bae6738..0000000000 --- a/doc/source/_templates/indexsidebar.html +++ /dev/null @@ -1,21 +0,0 @@ -

Quick links

- - - - - -

Search mailing list archive

- -
- - -
diff --git a/doc/source/_templates/layout.html b/doc/source/_templates/layout.html deleted file mode 100644 index 4572ad88b7..0000000000 --- a/doc/source/_templates/layout.html +++ /dev/null @@ -1,35 +0,0 @@ -{% extends "!layout.html" %} -{% set title = 'Neuroimaging in Python' %} - -{% block rootrellink %} -
  • Community
  • -
  • NiBabel Home
  • -
  • Mailing list
  • -
  • License
  • -{% endblock %} - -{% block sidebar1 %}{{ sidebar() }}{% endblock %} -{% block sidebar2 %}{% endblock %} - -{% block extrahead %} - -{% endblock %} - -{% block header %} -
    -
    - -
    -
    -

    NiBabel

    -

    Access a cacophony of neuro-imaging file formats

    -
    -
    -{% endblock %} - -{% block sidebarsearch %} -{{ super() }} -

    Reggie -- the one

    -{% endblock %} - -{% block relbar2 %}{% endblock %} diff --git a/doc/source/_templates/reggie.html b/doc/source/_templates/reggie.html deleted file mode 100644 index 835c2570d3..0000000000 --- a/doc/source/_templates/reggie.html +++ /dev/null @@ -1 +0,0 @@ -

    Reggie -- the one

    diff --git a/doc/source/changelog.rst b/doc/source/changelog.rst deleted file mode 120000 index 909d564cb7..0000000000 --- a/doc/source/changelog.rst +++ /dev/null @@ -1 +0,0 @@ -../../Changelog \ No newline at end of file diff --git a/doc/source/conf.py b/doc/source/conf.py deleted file mode 100644 index 9811651223..0000000000 --- a/doc/source/conf.py +++ /dev/null @@ -1,307 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# nipype documentation build configuration file, created by -# sphinx-quickstart on Mon Jul 20 12:30:18 2009. -# -# This file is exec()d with the current directory set to its containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import os -from pathlib import Path -from runpy import run_path - -try: - import tomllib -except ImportError: - 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: - raise RuntimeError('Need to install "numpydoc" package for doc build') -try: - import texext -except ImportError: - raise RuntimeError('Need to install "texext" package for doc build') - -# Need nibabel installed as well -try: - import nibabel -except ImportError: - raise RuntimeError( - '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 -rel = run_path(os.path.join('..', '..', 'nibabel', 'info.py')) - -# Write long description from info -with open('_long_description.inc', 'wt') as fobj: - fobj.write(rel['long_description']) - -# Load metadata from setup.cfg -with open(Path('../../pyproject.toml'), 'rb') as fobj: - pyproject = tomllib.load(fobj) -authors = pyproject['project']['authors'][0] - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.doctest', - 'sphinx.ext.intersphinx', - 'sphinx.ext.todo', - 'sphinx.ext.mathjax', - 'sphinx.ext.inheritance_diagram', - 'sphinx.ext.autosummary', - 'texext.math_dollar', # has to go before numpydoc - 'numpydoc', - 'matplotlib.sphinxext.plot_directive', -] - -# Autosummary always wants to use a `generated/` directory. -# We generate with `make api-stamp` -# This could change in the future -autosummary_generate = False - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -# source_encoding = 'utf-8' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = 'NiBabel' -copyright = f"2006, {authors['name']} <{authors['email']}>" - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = nibabel.__version__ -# The full version, including alpha/beta/rc tags. -release = version - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -# today = '' -# Else, today_fmt is used as the format for a strftime call. -today_fmt = '%B %d, %Y, %H:%M PDT' - -# List of documents that shouldn't be included in the build. -unused_docs = ['api/generated/gen'] - -# what to put into API doc (just class doc, just init, or both -autoclass_content = 'both' - -# List of directories, relative to source directory, that shouldn't be searched -# for source files. -exclude_trees = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all documents -# default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -# add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -# add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -# show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -# modindex_common_prefix = [] - -# -- Sphinxext configuration -------------------------------------------------- - -# Set attributes for layout of inheritance diagrams -inheritance_graph_attrs = dict(rankdir='LR', size='"6.0, 8.0"', fontsize=14, ratio='compress') -inheritance_node_attrs = dict( - shape='ellipse', fontsize=14, height=0.75, color='dodgerblue1', style='filled' -) - -# Flag to show todo items in rendered output -todo_include_todos = True - -# -- Options for HTML output -------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. Major themes that come with -# Sphinx are currently 'default' and 'sphinxdoc'. -html_theme = 'sphinxdoc' - -# The style sheet to use for HTML and HTML Help pages. A file of that name -# must exist either in Sphinx' static/ path, or in one of the custom paths -# given in html_static_path. -html_style = 'nibabel.css' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -# html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -# html_title = '' - -# A shorter title for the navigation bar. Default is the same as html_title. -# html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -# html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -# html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -# html_last_updated_fmt = '%b %d, %Y' - -# Content template for the index page. -html_index = 'index.html' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -# html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -html_sidebars = { - 'index': [ - 'localtoc.html', - 'relations.html', - 'sourcelink.html', - 'indexsidebar.html', - 'searchbox.html', - 'reggie.html', - ] -} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -# html_additional_pages = {'index': 'index.html'} - -# If false, no module index is generated. -# html_use_modindex = True - -# If false, no index is generated. -# html_use_index = True - -# If true, the index is split into individual pages for each letter. -# html_split_index = False - -# If true, links to the reST sources are added to the pages. -html_show_sourcelink = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -# html_use_opensearch = '' - -# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). -# html_file_suffix = '' - -# Output file base name for HTML help builder. -htmlhelp_basename = 'nibabeldoc' - -mathjax_path = 'https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML' - -# -- Options for LaTeX output ------------------------------------------------- - -# The paper size ('letter' or 'a4'). -# latex_paper_size = 'letter' - -# The font size ('10pt', '11pt' or '12pt'). -# latex_font_size = '10pt' - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, -# documentclass [howto/manual]). -latex_documents = [('index', 'nibabel.tex', 'NiBabel Documentation', 'NiBabel Authors', 'manual')] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -# latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -# latex_use_parts = False - -# Additional stuff for the LaTeX preamble. -# latex_preamble = '' - -# Documents to append as an appendix to all manuals. -# latex_appendices = [] - -# If false, no module index is generated. -# latex_use_modindex = True - - -# Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = { - 'python': ('https://docs.python.org/3', None), - 'numpy': ('https://numpy.org/doc/stable', None), - 'scipy': ('https://docs.scipy.org/doc/scipy', None), - 'matplotlib': ('https://matplotlib.org/stable', None), -} - -# Config of plot_directive -plot_include_source = True -plot_html_show_source_link = False - -# Numpy extensions -# ---------------- -# Worked out by Steven Silvester in -# https://github.com/scikit-image/scikit-image/pull/1356 -numpydoc_show_class_members = False -numpydoc_class_members_toctree = False diff --git a/doc/source/dicom/derivations/dicom_mosaic.py b/doc/source/dicom/derivations/dicom_mosaic.py deleted file mode 100644 index 5def2e9490..0000000000 --- a/doc/source/dicom/derivations/dicom_mosaic.py +++ /dev/null @@ -1,26 +0,0 @@ -"""Just showing the mosaic simplification""" - -from sympy import Matrix, Symbol, simplify, symbols - - -def numbered_matrix(nrows, ncols, symbol_prefix): - return Matrix(nrows, ncols, lambda i, j: Symbol(symbol_prefix + '_{%d%d}' % (i + 1, j + 1))) - - -def numbered_vector(nrows, symbol_prefix): - return Matrix(nrows, 1, lambda i, j: Symbol(symbol_prefix + '_{%d}' % (i + 1))) - - -RS = numbered_matrix(3, 3, 'rs') - -mdc, mdr, rdc, rdr = symbols('md_{cols} md_{rows} rd_{cols} rd_{rows}') - -md_adj = Matrix((mdc - 1, mdr - 1, 0)) / -2 -rd_adj = Matrix((rdc - 1, rdr - 1, 0)) / -2 - -adj = -(RS * md_adj) + RS * rd_adj -adj.simplify() - -Q = RS[:, :2] * Matrix(((mdc - rdc) / 2, (mdr - rdr) / 2)) - -assert simplify(adj - Q) == Matrix([0, 0, 0]) diff --git a/doc/source/external/nifti1.h b/doc/source/external/nifti1.h deleted file mode 100644 index dce3a88c1a..0000000000 --- a/doc/source/external/nifti1.h +++ /dev/null @@ -1,1490 +0,0 @@ -/** \file nifti1.h - \brief Official definition of the nifti1 header. Written by Bob Cox, SSCC, NIMH. - - HISTORY: - - 29 Nov 2007 [rickr] - - added DT_RGBA32 and NIFTI_TYPE_RGBA32 - - added NIFTI_INTENT codes: - TIME_SERIES, NODE_INDEX, RGB_VECTOR, RGBA_VECTOR, SHAPE - */ - -#ifndef _NIFTI_HEADER_ -#define _NIFTI_HEADER_ - -/***************************************************************************** - ** This file defines the "NIFTI-1" header format. ** - ** It is derived from 2 meetings at the NIH (31 Mar 2003 and ** - ** 02 Sep 2003) of the Data Format Working Group (DFWG), ** - ** chartered by the NIfTI (Neuroimaging Informatics Technology ** - ** Initiative) at the National Institutes of Health (NIH). ** - **--------------------------------------------------------------** - ** Neither the National Institutes of Health (NIH), the DFWG, ** - ** nor any of the members or employees of these institutions ** - ** imply any warranty of usefulness of this material for any ** - ** purpose, and do not assume any liability for damages, ** - ** incidental or otherwise, caused by any use of this document. ** - ** If these conditions are not acceptable, do not use this! ** - **--------------------------------------------------------------** - ** Author: Robert W Cox (NIMH, Bethesda) ** - ** Advisors: John Ashburner (FIL, London), ** - ** Stephen Smith (FMRIB, Oxford), ** - ** Mark Jenkinson (FMRIB, Oxford) ** -******************************************************************************/ - -/*---------------------------------------------------------------------------*/ -/* Note that the ANALYZE 7.5 file header (dbh.h) is - (c) Copyright 1986-1995 - Biomedical Imaging Resource - Mayo Foundation - Incorporation of components of dbh.h are by permission of the - Mayo Foundation. - - Changes from the ANALYZE 7.5 file header in this file are released to the - public domain, including the functional comments and any amusing asides. ------------------------------------------------------------------------------*/ - -/*---------------------------------------------------------------------------*/ -/*! INTRODUCTION TO NIFTI-1: - ------------------------ - The twin (and somewhat conflicting) goals of this modified ANALYZE 7.5 - format are: - (a) To add information to the header that will be useful for functional - neuroimaging data analysis and display. These additions include: - - More basic data types. - - Two affine transformations to specify voxel coordinates. - - "Intent" codes and parameters to describe the meaning of the data. - - Affine scaling of the stored data values to their "true" values. - - Optional storage of the header and image data in one file (.nii). - (b) To maintain compatibility with non-NIFTI-aware ANALYZE 7.5 compatible - software (i.e., such a program should be able to do something useful - with a NIFTI-1 dataset -- at least, with one stored in a traditional - .img/.hdr file pair). - - Most of the unused fields in the ANALYZE 7.5 header have been taken, - and some of the lesser-used fields have been co-opted for other purposes. - Notably, most of the data_history substructure has been co-opted for - other purposes, since the ANALYZE 7.5 format describes this substructure - as "not required". - - NIFTI-1 FLAG (MAGIC STRINGS): - ---------------------------- - To flag such a struct as being conformant to the NIFTI-1 spec, the last 4 - bytes of the header must be either the C String "ni1" or "n+1"; - in hexadecimal, the 4 bytes - 6E 69 31 00 or 6E 2B 31 00 - (in any future version of this format, the '1' will be upgraded to '2', - etc.). Normally, such a "magic number" or flag goes at the start of the - file, but trying to avoid clobbering widely-used ANALYZE 7.5 fields led to - putting this marker last. However, recall that "the last shall be first" - (Matthew 20:16). - - If a NIFTI-aware program reads a header file that is NOT marked with a - NIFTI magic string, then it should treat the header as an ANALYZE 7.5 - structure. - - NIFTI-1 FILE STORAGE: - -------------------- - "ni1" means that the image data is stored in the ".img" file corresponding - to the header file (starting at file offset 0). - - "n+1" means that the image data is stored in the same file as the header - information. We recommend that the combined header+data filename suffix - be ".nii". When the dataset is stored in one file, the first byte of image - data is stored at byte location (int)vox_offset in this combined file. - The minimum allowed value of vox_offset is 352; for compatibility with - some software, vox_offset should be an integral multiple of 16. - - GRACE UNDER FIRE: - ---------------- - Most NIFTI-aware programs will only be able to handle a subset of the full - range of datasets possible with this format. All NIFTI-aware programs - should take care to check if an input dataset conforms to the program's - needs and expectations (e.g., check datatype, intent_code, etc.). If the - input dataset can't be handled by the program, the program should fail - gracefully (e.g., print a useful warning; not crash). - - SAMPLE CODES: - ------------ - The associated files nifti1_io.h and nifti1_io.c provide a sample - implementation in C of a set of functions to read, write, and manipulate - NIFTI-1 files. The file nifti1_test.c is a sample program that uses - the nifti1_io.c functions. ------------------------------------------------------------------------------*/ - -/*---------------------------------------------------------------------------*/ -/* HEADER STRUCT DECLARATION: - ------------------------- - In the comments below for each field, only NIFTI-1 specific requirements - or changes from the ANALYZE 7.5 format are described. For convenience, - the 348 byte header is described as a single struct, rather than as the - ANALYZE 7.5 group of 3 substructs. - - Further comments about the interpretation of various elements of this - header are after the data type definition itself. Fields that are - marked as ++UNUSED++ have no particular interpretation in this standard. - (Also see the UNUSED FIELDS comment section, far below.) - - The presumption below is that the various C types have particular sizes: - sizeof(int) = sizeof(float) = 4 ; sizeof(short) = 2 ------------------------------------------------------------------------------*/ - -/*=================*/ -#ifdef __cplusplus -extern "C" { -#endif -/*=================*/ - -/*! \struct nifti_1_header - \brief Data structure defining the fields in the nifti1 header. - This binary header should be found at the beginning of a valid - NIFTI-1 header file. - */ - /*************************/ /************************/ -struct nifti_1_header { /* NIFTI-1 usage */ /* ANALYZE 7.5 field(s) */ - /*************************/ /************************/ - - /*--- was header_key substruct ---*/ - int sizeof_hdr; /*!< MUST be 348 */ /* int sizeof_hdr; */ - char data_type[10]; /*!< ++UNUSED++ */ /* char data_type[10]; */ - char db_name[18]; /*!< ++UNUSED++ */ /* char db_name[18]; */ - int extents; /*!< ++UNUSED++ */ /* int extents; */ - short session_error; /*!< ++UNUSED++ */ /* short session_error; */ - char regular; /*!< ++UNUSED++ */ /* char regular; */ - char dim_info; /*!< MRI slice ordering. */ /* char hkey_un0; */ - - /*--- was image_dimension substruct ---*/ - short dim[8]; /*!< Data array dimensions.*/ /* short dim[8]; */ - float intent_p1 ; /*!< 1st intent parameter. */ /* short unused8; */ - /* short unused9; */ - float intent_p2 ; /*!< 2nd intent parameter. */ /* short unused10; */ - /* short unused11; */ - float intent_p3 ; /*!< 3rd intent parameter. */ /* short unused12; */ - /* short unused13; */ - short intent_code ; /*!< NIFTI_INTENT_* code. */ /* short unused14; */ - short datatype; /*!< Defines data type! */ /* short datatype; */ - short bitpix; /*!< Number bits/voxel. */ /* short bitpix; */ - short slice_start; /*!< First slice index. */ /* short dim_un0; */ - float pixdim[8]; /*!< Grid spacings. */ /* float pixdim[8]; */ - float vox_offset; /*!< Offset into .nii file */ /* float vox_offset; */ - float scl_slope ; /*!< Data scaling: slope. */ /* float funused1; */ - float scl_inter ; /*!< Data scaling: offset. */ /* float funused2; */ - short slice_end; /*!< Last slice index. */ /* float funused3; */ - char slice_code ; /*!< Slice timing order. */ - char xyzt_units ; /*!< Units of pixdim[1..4] */ - float cal_max; /*!< Max display intensity */ /* float cal_max; */ - float cal_min; /*!< Min display intensity */ /* float cal_min; */ - float slice_duration;/*!< Time for 1 slice. */ /* float compressed; */ - float toffset; /*!< Time axis shift. */ /* float verified; */ - int glmax; /*!< ++UNUSED++ */ /* int glmax; */ - int glmin; /*!< ++UNUSED++ */ /* int glmin; */ - - /*--- was data_history substruct ---*/ - char descrip[80]; /*!< any text you like. */ /* char descrip[80]; */ - char aux_file[24]; /*!< auxiliary filename. */ /* char aux_file[24]; */ - - short qform_code ; /*!< NIFTI_XFORM_* code. */ /*-- all ANALYZE 7.5 ---*/ - short sform_code ; /*!< NIFTI_XFORM_* code. */ /* fields below here */ - /* are replaced */ - float quatern_b ; /*!< Quaternion b param. */ - float quatern_c ; /*!< Quaternion c param. */ - float quatern_d ; /*!< Quaternion d param. */ - float qoffset_x ; /*!< Quaternion x shift. */ - float qoffset_y ; /*!< Quaternion y shift. */ - float qoffset_z ; /*!< Quaternion z shift. */ - - float srow_x[4] ; /*!< 1st row affine transform. */ - float srow_y[4] ; /*!< 2nd row affine transform. */ - float srow_z[4] ; /*!< 3rd row affine transform. */ - - char intent_name[16];/*!< 'name' or meaning of data. */ - - char magic[4] ; /*!< MUST be "ni1\0" or "n+1\0". */ - -} ; /**** 348 bytes total ****/ - -typedef struct nifti_1_header nifti_1_header ; - -/*---------------------------------------------------------------------------*/ -/* HEADER EXTENSIONS: - ----------------- - After the end of the 348 byte header (e.g., after the magic field), - the next 4 bytes are a char array field named "extension". By default, - all 4 bytes of this array should be set to zero. In a .nii file, these - 4 bytes will always be present, since the earliest start point for - the image data is byte #352. In a separate .hdr file, these bytes may - or may not be present. If not present (i.e., if the length of the .hdr - file is 348 bytes), then a NIfTI-1 compliant program should use the - default value of extension={0,0,0,0}. The first byte (extension[0]) - is the only value of this array that is specified at present. The other - 3 bytes are reserved for future use. - - If extension[0] is nonzero, it indicates that extended header information - is present in the bytes following the extension array. In a .nii file, - this extended header data is before the image data (and vox_offset - must be set correctly to allow for this). In a .hdr file, this extended - data follows extension and proceeds (potentially) to the end of the file. - - The format of extended header data is weakly specified. Each extension - must be an integer multiple of 16 bytes long. The first 8 bytes of each - extension comprise 2 integers: - int esize , ecode ; - These values may need to be byte-swapped, as indicated by dim[0] for - the rest of the header. - * esize is the number of bytes that form the extended header data - + esize must be a positive integral multiple of 16 - + this length includes the 8 bytes of esize and ecode themselves - * ecode is a non-negative integer that indicates the format of the - extended header data that follows - + different ecode values are assigned to different developer groups - + at present, the "registered" values for code are - = 0 = unknown private format (not recommended!) - = 2 = DICOM format (i.e., attribute tags and values) - = 4 = AFNI group (i.e., ASCII XML-ish elements) - In the interests of interoperability (a primary rationale for NIfTI), - groups developing software that uses this extension mechanism are - encouraged to document and publicize the format of their extensions. - To this end, the NIfTI DFWG will assign even numbered codes upon request - to groups submitting at least rudimentary documentation for the format - of their extension; at present, the contact is mailto:rwcox@nih.gov. - The assigned codes and documentation will be posted on the NIfTI - website. All odd values of ecode (and 0) will remain unassigned; - at least, until the even ones are used up, when we get to 2,147,483,646. - - Note that the other contents of the extended header data section are - totally unspecified by the NIfTI-1 standard. In particular, if binary - data is stored in such a section, its byte order is not necessarily - the same as that given by examining dim[0]; it is incumbent on the - programs dealing with such data to determine the byte order of binary - extended header data. - - Multiple extended header sections are allowed, each starting with an - esize,ecode value pair. The first esize value, as described above, - is at bytes #352-355 in the .hdr or .nii file (files start at byte #0). - If this value is positive, then the second (esize2) will be found - starting at byte #352+esize1 , the third (esize3) at byte #352+esize1+esize2, - et cetera. Of course, in a .nii file, the value of vox_offset must - be compatible with these extensions. If a malformed file indicates - that an extended header data section would run past vox_offset, then - the entire extended header section should be ignored. In a .hdr file, - if an extended header data section would run past the end-of-file, - that extended header data should also be ignored. - - With the above scheme, a program can successively examine the esize - and ecode values, and skip over each extended header section if the - program doesn't know how to interpret the data within. Of course, any - program can simply ignore all extended header sections simply by jumping - straight to the image data using vox_offset. ------------------------------------------------------------------------------*/ - -/*! \struct nifti1_extender - \brief This structure represents a 4-byte string that should follow the - binary nifti_1_header data in a NIFTI-1 header file. If the char - values are {1,0,0,0}, the file is expected to contain extensions, - values of {0,0,0,0} imply the file does not contain extensions. - Other sequences of values are not currently defined. - */ -struct nifti1_extender { char extension[4] ; } ; -typedef struct nifti1_extender nifti1_extender ; - -/*! \struct nifti1_extension - \brief Data structure defining the fields of a header extension. - */ -struct nifti1_extension { - int esize ; /*!< size of extension, in bytes (must be multiple of 16) */ - int ecode ; /*!< extension code, one of the NIFTI_ECODE_ values */ - char * edata ; /*!< raw data, with no byte swapping (length is esize-8) */ -} ; -typedef struct nifti1_extension nifti1_extension ; - -/*---------------------------------------------------------------------------*/ -/* DATA DIMENSIONALITY (as in ANALYZE 7.5): - --------------------------------------- - dim[0] = number of dimensions; - - if dim[0] is outside range 1..7, then the header information - needs to be byte swapped appropriately - - ANALYZE supports dim[0] up to 7, but NIFTI-1 reserves - dimensions 1,2,3 for space (x,y,z), 4 for time (t), and - 5,6,7 for anything else needed. - - dim[i] = length of dimension #i, for i=1..dim[0] (must be positive) - - also see the discussion of intent_code, far below - - pixdim[i] = voxel width along dimension #i, i=1..dim[0] (positive) - - cf. ORIENTATION section below for use of pixdim[0] - - the units of pixdim can be specified with the xyzt_units - field (also described far below). - - Number of bits per voxel value is in bitpix, which MUST correspond with - the datatype field. The total number of bytes in the image data is - dim[1] * ... * dim[dim[0]] * bitpix / 8 - - In NIFTI-1 files, dimensions 1,2,3 are for space, dimension 4 is for time, - and dimension 5 is for storing multiple values at each spatiotemporal - voxel. Some examples: - - A typical whole-brain FMRI experiment's time series: - - dim[0] = 4 - - dim[1] = 64 pixdim[1] = 3.75 xyzt_units = NIFTI_UNITS_MM - - dim[2] = 64 pixdim[2] = 3.75 | NIFTI_UNITS_SEC - - dim[3] = 20 pixdim[3] = 5.0 - - dim[4] = 120 pixdim[4] = 2.0 - - A typical T1-weighted anatomical volume: - - dim[0] = 3 - - dim[1] = 256 pixdim[1] = 1.0 xyzt_units = NIFTI_UNITS_MM - - dim[2] = 256 pixdim[2] = 1.0 - - dim[3] = 128 pixdim[3] = 1.1 - - A single slice EPI time series: - - dim[0] = 4 - - dim[1] = 64 pixdim[1] = 3.75 xyzt_units = NIFTI_UNITS_MM - - dim[2] = 64 pixdim[2] = 3.75 | NIFTI_UNITS_SEC - - dim[3] = 1 pixdim[3] = 5.0 - - dim[4] = 1200 pixdim[4] = 0.2 - - A 3-vector stored at each point in a 3D volume: - - dim[0] = 5 - - dim[1] = 256 pixdim[1] = 1.0 xyzt_units = NIFTI_UNITS_MM - - dim[2] = 256 pixdim[2] = 1.0 - - dim[3] = 128 pixdim[3] = 1.1 - - dim[4] = 1 pixdim[4] = 0.0 - - dim[5] = 3 intent_code = NIFTI_INTENT_VECTOR - - A single time series with a 3x3 matrix at each point: - - dim[0] = 5 - - dim[1] = 1 xyzt_units = NIFTI_UNITS_SEC - - dim[2] = 1 - - dim[3] = 1 - - dim[4] = 1200 pixdim[4] = 0.2 - - dim[5] = 9 intent_code = NIFTI_INTENT_GENMATRIX - - intent_p1 = intent_p2 = 3.0 (indicates matrix dimensions) ------------------------------------------------------------------------------*/ - -/*---------------------------------------------------------------------------*/ -/* DATA STORAGE: - ------------ - If the magic field is "n+1", then the voxel data is stored in the - same file as the header. In this case, the voxel data starts at offset - (int)vox_offset into the header file. Thus, vox_offset=352.0 means that - the data starts immediately after the NIFTI-1 header. If vox_offset is - greater than 352, the NIFTI-1 format does not say much about the - contents of the dataset file between the end of the header and the - start of the data. - - FILES: - ----- - If the magic field is "ni1", then the voxel data is stored in the - associated ".img" file, starting at offset 0 (i.e., vox_offset is not - used in this case, and should be set to 0.0). - - When storing NIFTI-1 datasets in pairs of files, it is customary to name - the files in the pattern "name.hdr" and "name.img", as in ANALYZE 7.5. - When storing in a single file ("n+1"), the file name should be in - the form "name.nii" (the ".nft" and ".nif" suffixes are already taken; - cf. http://www.icdatamaster.com/n.html ). - - BYTE ORDERING: - ------------- - The byte order of the data arrays is presumed to be the same as the byte - order of the header (which is determined by examining dim[0]). - - Floating point types are presumed to be stored in IEEE-754 format. ------------------------------------------------------------------------------*/ - -/*---------------------------------------------------------------------------*/ -/* DETAILS ABOUT vox_offset: - ------------------------ - In a .nii file, the vox_offset field value is interpreted as the start - location of the image data bytes in that file. In a .hdr/.img file pair, - the vox_offset field value is the start location of the image data - bytes in the .img file. - * If vox_offset is less than 352 in a .nii file, it is equivalent - to 352 (i.e., image data never starts before byte #352 in a .nii file). - * The default value for vox_offset in a .nii file is 352. - * In a .hdr file, the default value for vox_offset is 0. - * vox_offset should be an integer multiple of 16; otherwise, some - programs may not work properly (e.g., SPM). This is to allow - memory-mapped input to be properly byte-aligned. - Note that since vox_offset is an IEEE-754 32 bit float (for compatibility - with the ANALYZE-7.5 format), it effectively has a 24 bit mantissa. All - integers from 0 to 2^24 can be represented exactly in this format, but not - all larger integers are exactly storable as IEEE-754 32 bit floats. However, - unless you plan to have vox_offset be potentially larger than 16 MB, this - should not be an issue. (Actually, any integral multiple of 16 up to 2^27 - can be represented exactly in this format, which allows for up to 128 MB - of random information before the image data. If that isn't enough, then - perhaps this format isn't right for you.) - - In a .img file (i.e., image data stored separately from the NIfTI-1 - header), data bytes between #0 and #vox_offset-1 (inclusive) are completely - undefined and unregulated by the NIfTI-1 standard. One potential use of - having vox_offset > 0 in the .hdr/.img file pair storage method is to make - the .img file be a copy of (or link to) a pre-existing image file in some - other format, such as DICOM; then vox_offset would be set to the offset of - the image data in this file. (It may not be possible to follow the - "multiple-of-16 rule" with an arbitrary external file; using the NIfTI-1 - format in such a case may lead to a file that is incompatible with software - that relies on vox_offset being a multiple of 16.) - - In a .nii file, data bytes between #348 and #vox_offset-1 (inclusive) may - be used to store user-defined extra information; similarly, in a .hdr file, - any data bytes after byte #347 are available for user-defined extra - information. The (very weak) regulation of this extra header data is - described elsewhere. ------------------------------------------------------------------------------*/ - -/*---------------------------------------------------------------------------*/ -/* DATA SCALING: - ------------ - If the scl_slope field is nonzero, then each voxel value in the dataset - should be scaled as - y = scl_slope * x + scl_inter - where x = voxel value stored - y = "true" voxel value - Normally, we would expect this scaling to be used to store "true" floating - values in a smaller integer datatype, but that is not required. That is, - it is legal to use scaling even if the datatype is a float type (crazy, - perhaps, but legal). - - However, the scaling is to be ignored if datatype is DT_RGB24. - - If datatype is a complex type, then the scaling is to be - applied to both the real and imaginary parts. - - The cal_min and cal_max fields (if nonzero) are used for mapping (possibly - scaled) dataset values to display colors: - - Minimum display intensity (black) corresponds to dataset value cal_min. - - Maximum display intensity (white) corresponds to dataset value cal_max. - - Dataset values below cal_min should display as black also, and values - above cal_max as white. - - Colors "black" and "white", of course, may refer to any scalar display - scheme (e.g., a color lookup table specified via aux_file). - - cal_min and cal_max only make sense when applied to scalar-valued - datasets (i.e., dim[0] < 5 or dim[5] = 1). ------------------------------------------------------------------------------*/ - -/*---------------------------------------------------------------------------*/ -/* TYPE OF DATA (acceptable values for datatype field): - --------------------------------------------------- - Values of datatype smaller than 256 are ANALYZE 7.5 compatible. - Larger values are NIFTI-1 additions. These are all multiples of 256, so - that no bits below position 8 are set in datatype. But there is no need - to use only powers-of-2, as the original ANALYZE 7.5 datatype codes do. - - The additional codes are intended to include a complete list of basic - scalar types, including signed and unsigned integers from 8 to 64 bits, - floats from 32 to 128 bits, and complex (float pairs) from 64 to 256 bits. - - Note that most programs will support only a few of these datatypes! - A NIFTI-1 program should fail gracefully (e.g., print a warning message) - when it encounters a dataset with a type it doesn't like. ------------------------------------------------------------------------------*/ - -#undef DT_UNKNOWN /* defined in dirent.h on some Unix systems */ - -/*! \defgroup NIFTI1_DATATYPES - \brief nifti1 datatype codes - @{ - */ - /*--- the original ANALYZE 7.5 type codes ---*/ -#define DT_NONE 0 -#define DT_UNKNOWN 0 /* what it says, dude */ -#define DT_BINARY 1 /* binary (1 bit/voxel) */ -#define DT_UNSIGNED_CHAR 2 /* unsigned char (8 bits/voxel) */ -#define DT_SIGNED_SHORT 4 /* signed short (16 bits/voxel) */ -#define DT_SIGNED_INT 8 /* signed int (32 bits/voxel) */ -#define DT_FLOAT 16 /* float (32 bits/voxel) */ -#define DT_COMPLEX 32 /* complex (64 bits/voxel) */ -#define DT_DOUBLE 64 /* double (64 bits/voxel) */ -#define DT_RGB 128 /* RGB triple (24 bits/voxel) */ -#define DT_ALL 255 /* not very useful (?) */ - - /*----- another set of names for the same ---*/ -#define DT_UINT8 2 -#define DT_INT16 4 -#define DT_INT32 8 -#define DT_FLOAT32 16 -#define DT_COMPLEX64 32 -#define DT_FLOAT64 64 -#define DT_RGB24 128 - - /*------------------- new codes for NIFTI ---*/ -#define DT_INT8 256 /* signed char (8 bits) */ -#define DT_UINT16 512 /* unsigned short (16 bits) */ -#define DT_UINT32 768 /* unsigned int (32 bits) */ -#define DT_INT64 1024 /* long long (64 bits) */ -#define DT_UINT64 1280 /* unsigned long long (64 bits) */ -#define DT_FLOAT128 1536 /* long double (128 bits) */ -#define DT_COMPLEX128 1792 /* double pair (128 bits) */ -#define DT_COMPLEX256 2048 /* long double pair (256 bits) */ -#define DT_RGBA32 2304 /* 4 byte RGBA (32 bits/voxel) */ -/* @} */ - - - /*------- aliases for all the above codes ---*/ - -/*! \defgroup NIFTI1_DATATYPE_ALIASES - \brief aliases for the nifti1 datatype codes - @{ - */ - /*! unsigned char. */ -#define NIFTI_TYPE_UINT8 2 - /*! signed short. */ -#define NIFTI_TYPE_INT16 4 - /*! signed int. */ -#define NIFTI_TYPE_INT32 8 - /*! 32 bit float. */ -#define NIFTI_TYPE_FLOAT32 16 - /*! 64 bit complex = 2 32 bit floats. */ -#define NIFTI_TYPE_COMPLEX64 32 - /*! 64 bit float = double. */ -#define NIFTI_TYPE_FLOAT64 64 - /*! 3 8 bit bytes. */ -#define NIFTI_TYPE_RGB24 128 - /*! signed char. */ -#define NIFTI_TYPE_INT8 256 - /*! unsigned short. */ -#define NIFTI_TYPE_UINT16 512 - /*! unsigned int. */ -#define NIFTI_TYPE_UINT32 768 - /*! signed long long. */ -#define NIFTI_TYPE_INT64 1024 - /*! unsigned long long. */ -#define NIFTI_TYPE_UINT64 1280 - /*! 128 bit float = long double. */ -#define NIFTI_TYPE_FLOAT128 1536 - /*! 128 bit complex = 2 64 bit floats. */ -#define NIFTI_TYPE_COMPLEX128 1792 - /*! 256 bit complex = 2 128 bit floats */ -#define NIFTI_TYPE_COMPLEX256 2048 - /*! 4 8 bit bytes. */ -#define NIFTI_TYPE_RGBA32 2304 -/* @} */ - - /*-------- sample typedefs for complicated types ---*/ -#if 0 -typedef struct { float r,i; } complex_float ; -typedef struct { double r,i; } complex_double ; -typedef struct { long double r,i; } complex_longdouble ; -typedef struct { unsigned char r,g,b; } rgb_byte ; -#endif - -/*---------------------------------------------------------------------------*/ -/* INTERPRETATION OF VOXEL DATA: - ---------------------------- - The intent_code field can be used to indicate that the voxel data has - some particular meaning. In particular, a large number of codes is - given to indicate that the the voxel data should be interpreted as - being drawn from a given probability distribution. - - VECTOR-VALUED DATASETS: - ---------------------- - The 5th dimension of the dataset, if present (i.e., dim[0]=5 and - dim[5] > 1), contains multiple values (e.g., a vector) to be stored - at each spatiotemporal location. For example, the header values - - dim[0] = 5 - - dim[1] = 64 - - dim[2] = 64 - - dim[3] = 20 - - dim[4] = 1 (indicates no time axis) - - dim[5] = 3 - - datatype = DT_FLOAT - - intent_code = NIFTI_INTENT_VECTOR - mean that this dataset should be interpreted as a 3D volume (64x64x20), - with a 3-vector of floats defined at each point in the 3D grid. - - A program reading a dataset with a 5th dimension may want to reformat - the image data to store each voxels' set of values together in a struct - or array. This programming detail, however, is beyond the scope of the - NIFTI-1 file specification! Uses of dimensions 6 and 7 are also not - specified here. - - STATISTICAL PARAMETRIC DATASETS (i.e., SPMs): - -------------------------------------------- - Values of intent_code from NIFTI_FIRST_STATCODE to NIFTI_LAST_STATCODE - (inclusive) indicate that the numbers in the dataset should be interpreted - as being drawn from a given distribution. Most such distributions have - auxiliary parameters (e.g., NIFTI_INTENT_TTEST has 1 DOF parameter). - - If the dataset DOES NOT have a 5th dimension, then the auxiliary parameters - are the same for each voxel, and are given in header fields intent_p1, - intent_p2, and intent_p3. - - If the dataset DOES have a 5th dimension, then the auxiliary parameters - are different for each voxel. For example, the header values - - dim[0] = 5 - - dim[1] = 128 - - dim[2] = 128 - - dim[3] = 1 (indicates a single slice) - - dim[4] = 1 (indicates no time axis) - - dim[5] = 2 - - datatype = DT_FLOAT - - intent_code = NIFTI_INTENT_TTEST - mean that this is a 2D dataset (128x128) of t-statistics, with the - t-statistic being in the first "plane" of data and the degrees-of-freedom - parameter being in the second "plane" of data. - - If the dataset 5th dimension is used to store the voxel-wise statistical - parameters, then dim[5] must be 1 plus the number of parameters required - by that distribution (e.g., intent_code=NIFTI_INTENT_TTEST implies dim[5] - must be 2, as in the example just above). - - Note: intent_code values 2..10 are compatible with AFNI 1.5x (which is - why there is no code with value=1, which is obsolescent in AFNI). - - OTHER INTENTIONS: - ---------------- - The purpose of the intent_* fields is to help interpret the values - stored in the dataset. Some non-statistical values for intent_code - and conventions are provided for storing other complex data types. - - The intent_name field provides space for a 15 character (plus 0 byte) - 'name' string for the type of data stored. Examples: - - intent_code = NIFTI_INTENT_ESTIMATE; intent_name = "T1"; - could be used to signify that the voxel values are estimates of the - NMR parameter T1. - - intent_code = NIFTI_INTENT_TTEST; intent_name = "House"; - could be used to signify that the voxel values are t-statistics - for the significance of 'activation' response to a House stimulus. - - intent_code = NIFTI_INTENT_DISPVECT; intent_name = "ToMNI152"; - could be used to signify that the voxel values are a displacement - vector that transforms each voxel (x,y,z) location to the - corresponding location in the MNI152 standard brain. - - intent_code = NIFTI_INTENT_SYMMATRIX; intent_name = "DTI"; - could be used to signify that the voxel values comprise a diffusion - tensor image. - - If no data name is implied or needed, intent_name[0] should be set to 0. ------------------------------------------------------------------------------*/ - - /*! default: no intention is indicated in the header. */ - -#define NIFTI_INTENT_NONE 0 - - /*-------- These codes are for probability distributions ---------------*/ - /* Most distributions have a number of parameters, - below denoted by p1, p2, and p3, and stored in - - intent_p1, intent_p2, intent_p3 if dataset doesn't have 5th dimension - - image data array if dataset does have 5th dimension - - Functions to compute with many of the distributions below can be found - in the CDF library from U Texas. - - Formulas for and discussions of these distributions can be found in the - following books: - - [U] Univariate Discrete Distributions, - NL Johnson, S Kotz, AW Kemp. - - [C1] Continuous Univariate Distributions, vol. 1, - NL Johnson, S Kotz, N Balakrishnan. - - [C2] Continuous Univariate Distributions, vol. 2, - NL Johnson, S Kotz, N Balakrishnan. */ - /*----------------------------------------------------------------------*/ - - /*! [C2, chap 32] Correlation coefficient R (1 param): - p1 = degrees of freedom - R/sqrt(1-R*R) is t-distributed with p1 DOF. */ - -/*! \defgroup NIFTI1_INTENT_CODES - \brief nifti1 intent codes, to describe intended meaning of dataset contents - @{ - */ -#define NIFTI_INTENT_CORREL 2 - - /*! [C2, chap 28] Student t statistic (1 param): p1 = DOF. */ - -#define NIFTI_INTENT_TTEST 3 - - /*! [C2, chap 27] Fisher F statistic (2 params): - p1 = numerator DOF, p2 = denominator DOF. */ - -#define NIFTI_INTENT_FTEST 4 - - /*! [C1, chap 13] Standard normal (0 params): Density = N(0,1). */ - -#define NIFTI_INTENT_ZSCORE 5 - - /*! [C1, chap 18] Chi-squared (1 param): p1 = DOF. - Density(x) proportional to exp(-x/2) * x^(p1/2-1). */ - -#define NIFTI_INTENT_CHISQ 6 - - /*! [C2, chap 25] Beta distribution (2 params): p1=a, p2=b. - Density(x) proportional to x^(a-1) * (1-x)^(b-1). */ - -#define NIFTI_INTENT_BETA 7 - - /*! [U, chap 3] Binomial distribution (2 params): - p1 = number of trials, p2 = probability per trial. - Prob(x) = (p1 choose x) * p2^x * (1-p2)^(p1-x), for x=0,1,...,p1. */ - -#define NIFTI_INTENT_BINOM 8 - - /*! [C1, chap 17] Gamma distribution (2 params): - p1 = shape, p2 = scale. - Density(x) proportional to x^(p1-1) * exp(-p2*x). */ - -#define NIFTI_INTENT_GAMMA 9 - - /*! [U, chap 4] Poisson distribution (1 param): p1 = mean. - Prob(x) = exp(-p1) * p1^x / x! , for x=0,1,2,.... */ - -#define NIFTI_INTENT_POISSON 10 - - /*! [C1, chap 13] Normal distribution (2 params): - p1 = mean, p2 = standard deviation. */ - -#define NIFTI_INTENT_NORMAL 11 - - /*! [C2, chap 30] Noncentral F statistic (3 params): - p1 = numerator DOF, p2 = denominator DOF, - p3 = numerator noncentrality parameter. */ - -#define NIFTI_INTENT_FTEST_NONC 12 - - /*! [C2, chap 29] Noncentral chi-squared statistic (2 params): - p1 = DOF, p2 = noncentrality parameter. */ - -#define NIFTI_INTENT_CHISQ_NONC 13 - - /*! [C2, chap 23] Logistic distribution (2 params): - p1 = location, p2 = scale. - Density(x) proportional to sech^2((x-p1)/(2*p2)). */ - -#define NIFTI_INTENT_LOGISTIC 14 - - /*! [C2, chap 24] Laplace distribution (2 params): - p1 = location, p2 = scale. - Density(x) proportional to exp(-abs(x-p1)/p2). */ - -#define NIFTI_INTENT_LAPLACE 15 - - /*! [C2, chap 26] Uniform distribution: p1 = lower end, p2 = upper end. */ - -#define NIFTI_INTENT_UNIFORM 16 - - /*! [C2, chap 31] Noncentral t statistic (2 params): - p1 = DOF, p2 = noncentrality parameter. */ - -#define NIFTI_INTENT_TTEST_NONC 17 - - /*! [C1, chap 21] Weibull distribution (3 params): - p1 = location, p2 = scale, p3 = power. - Density(x) proportional to - ((x-p1)/p2)^(p3-1) * exp(-((x-p1)/p2)^p3) for x > p1. */ - -#define NIFTI_INTENT_WEIBULL 18 - - /*! [C1, chap 18] Chi distribution (1 param): p1 = DOF. - Density(x) proportional to x^(p1-1) * exp(-x^2/2) for x > 0. - p1 = 1 = 'half normal' distribution - p1 = 2 = Rayleigh distribution - p1 = 3 = Maxwell-Boltzmann distribution. */ - -#define NIFTI_INTENT_CHI 19 - - /*! [C1, chap 15] Inverse Gaussian (2 params): - p1 = mu, p2 = lambda - Density(x) proportional to - exp(-p2*(x-p1)^2/(2*p1^2*x)) / x^3 for x > 0. */ - -#define NIFTI_INTENT_INVGAUSS 20 - - /*! [C2, chap 22] Extreme value type I (2 params): - p1 = location, p2 = scale - cdf(x) = exp(-exp(-(x-p1)/p2)). */ - -#define NIFTI_INTENT_EXTVAL 21 - - /*! Data is a 'p-value' (no params). */ - -#define NIFTI_INTENT_PVAL 22 - - /*! Data is ln(p-value) (no params). - To be safe, a program should compute p = exp(-abs(this_value)). - The nifti_stats.c library returns this_value - as positive, so that this_value = -log(p). */ - - -#define NIFTI_INTENT_LOGPVAL 23 - - /*! Data is log10(p-value) (no params). - To be safe, a program should compute p = pow(10.,-abs(this_value)). - The nifti_stats.c library returns this_value - as positive, so that this_value = -log10(p). */ - -#define NIFTI_INTENT_LOG10PVAL 24 - - /*! Smallest intent_code that indicates a statistic. */ - -#define NIFTI_FIRST_STATCODE 2 - - /*! Largest intent_code that indicates a statistic. */ - -#define NIFTI_LAST_STATCODE 24 - - /*---------- these values for intent_code aren't for statistics ----------*/ - - /*! To signify that the value at each voxel is an estimate - of some parameter, set intent_code = NIFTI_INTENT_ESTIMATE. - The name of the parameter may be stored in intent_name. */ - -#define NIFTI_INTENT_ESTIMATE 1001 - - /*! To signify that the value at each voxel is an index into - some set of labels, set intent_code = NIFTI_INTENT_LABEL. - The filename with the labels may stored in aux_file. */ - -#define NIFTI_INTENT_LABEL 1002 - - /*! To signify that the value at each voxel is an index into the - NeuroNames labels set, set intent_code = NIFTI_INTENT_NEURONAME. */ - -#define NIFTI_INTENT_NEURONAME 1003 - - /*! To store an M x N matrix at each voxel: - - dataset must have a 5th dimension (dim[0]=5 and dim[5]>1) - - intent_code must be NIFTI_INTENT_GENMATRIX - - dim[5] must be M*N - - intent_p1 must be M (in float format) - - intent_p2 must be N (ditto) - - the matrix values A[i][[j] are stored in row-order: - - A[0][0] A[0][1] ... A[0][N-1] - - A[1][0] A[1][1] ... A[1][N-1] - - etc., until - - A[M-1][0] A[M-1][1] ... A[M-1][N-1] */ - -#define NIFTI_INTENT_GENMATRIX 1004 - - /*! To store an NxN symmetric matrix at each voxel: - - dataset must have a 5th dimension - - intent_code must be NIFTI_INTENT_SYMMATRIX - - dim[5] must be N*(N+1)/2 - - intent_p1 must be N (in float format) - - the matrix values A[i][[j] are stored in row-order: - - A[0][0] - - A[1][0] A[1][1] - - A[2][0] A[2][1] A[2][2] - - etc.: row-by-row */ - -#define NIFTI_INTENT_SYMMATRIX 1005 - - /*! To signify that the vector value at each voxel is to be taken - as a displacement field or vector: - - dataset must have a 5th dimension - - intent_code must be NIFTI_INTENT_DISPVECT - - dim[5] must be the dimensionality of the displacement - vector (e.g., 3 for spatial displacement, 2 for in-plane) */ - -#define NIFTI_INTENT_DISPVECT 1006 /* specifically for displacements */ -#define NIFTI_INTENT_VECTOR 1007 /* for any other type of vector */ - - /*! To signify that the vector value at each voxel is really a - spatial coordinate (e.g., the vertices or nodes of a surface mesh): - - dataset must have a 5th dimension - - intent_code must be NIFTI_INTENT_POINTSET - - dim[0] = 5 - - dim[1] = number of points - - dim[2] = dim[3] = dim[4] = 1 - - dim[5] must be the dimensionality of space (e.g., 3 => 3D space). - - intent_name may describe the object these points come from - (e.g., "pial", "gray/white" , "EEG", "MEG"). */ - -#define NIFTI_INTENT_POINTSET 1008 - - /*! To signify that the vector value at each voxel is really a triple - of indexes (e.g., forming a triangle) from a pointset dataset: - - dataset must have a 5th dimension - - intent_code must be NIFTI_INTENT_TRIANGLE - - dim[0] = 5 - - dim[1] = number of triangles - - dim[2] = dim[3] = dim[4] = 1 - - dim[5] = 3 - - datatype should be an integer type (preferably DT_INT32) - - the data values are indexes (0,1,...) into a pointset dataset. */ - -#define NIFTI_INTENT_TRIANGLE 1009 - - /*! To signify that the vector value at each voxel is a quaternion: - - dataset must have a 5th dimension - - intent_code must be NIFTI_INTENT_QUATERNION - - dim[0] = 5 - - dim[5] = 4 - - datatype should be a floating point type */ - -#define NIFTI_INTENT_QUATERNION 1010 - - /*! Dimensionless value - no params - although, as in _ESTIMATE - the name of the parameter may be stored in intent_name. */ - -#define NIFTI_INTENT_DIMLESS 1011 - - /*---------- these values apply to GIFTI datasets ----------*/ - - /*! To signify that the value at each location is from a time series. */ - -#define NIFTI_INTENT_TIME_SERIES 2001 - - /*! To signify that the value at each location is a node index, from - a complete surface dataset. */ - -#define NIFTI_INTENT_NODE_INDEX 2002 - - /*! To signify that the vector value at each location is an RGB triplet, - of whatever type. - - dataset must have a 5th dimension - - dim[0] = 5 - - dim[1] = number of nodes - - dim[2] = dim[3] = dim[4] = 1 - - dim[5] = 3 - */ - -#define NIFTI_INTENT_RGB_VECTOR 2003 - - /*! To signify that the vector value at each location is a 4 valued RGBA - vector, of whatever type. - - dataset must have a 5th dimension - - dim[0] = 5 - - dim[1] = number of nodes - - dim[2] = dim[3] = dim[4] = 1 - - dim[5] = 4 - */ - -#define NIFTI_INTENT_RGBA_VECTOR 2004 - - /*! To signify that the value at each location is a shape value, such - as the curvature. */ - -#define NIFTI_INTENT_SHAPE 2005 - -/* @} */ - -/*---------------------------------------------------------------------------*/ -/* 3D IMAGE (VOLUME) ORIENTATION AND LOCATION IN SPACE: - --------------------------------------------------- - There are 3 different methods by which continuous coordinates can - attached to voxels. The discussion below emphasizes 3D volumes, and - the continuous coordinates are referred to as (x,y,z). The voxel - index coordinates (i.e., the array indexes) are referred to as (i,j,k), - with valid ranges: - i = 0 .. dim[1]-1 - j = 0 .. dim[2]-1 (if dim[0] >= 2) - k = 0 .. dim[3]-1 (if dim[0] >= 3) - The (x,y,z) coordinates refer to the CENTER of a voxel. In methods - 2 and 3, the (x,y,z) axes refer to a subject-based coordinate system, - with - +x = Right +y = Anterior +z = Superior. - This is a right-handed coordinate system. However, the exact direction - these axes point with respect to the subject depends on qform_code - (Method 2) and sform_code (Method 3). - - N.B.: The i index varies most rapidly, j index next, k index slowest. - Thus, voxel (i,j,k) is stored starting at location - (i + j*dim[1] + k*dim[1]*dim[2]) * (bitpix/8) - into the dataset array. - - N.B.: The ANALYZE 7.5 coordinate system is - +x = Left +y = Anterior +z = Superior - which is a left-handed coordinate system. This backwardness is - too difficult to tolerate, so this NIFTI-1 standard specifies the - coordinate order which is most common in functional neuroimaging. - - N.B.: The 3 methods below all give the locations of the voxel centers - in the (x,y,z) coordinate system. In many cases, programs will wish - to display image data on some other grid. In such a case, the program - will need to convert its desired (x,y,z) values into (i,j,k) values - in order to extract (or interpolate) the image data. This operation - would be done with the inverse transformation to those described below. - - N.B.: Method 2 uses a factor 'qfac' which is either -1 or 1; qfac is - stored in the otherwise unused pixdim[0]. If pixdim[0]=0.0 (which - should not occur), we take qfac=1. Of course, pixdim[0] is only used - when reading a NIFTI-1 header, not when reading an ANALYZE 7.5 header. - - N.B.: The units of (x,y,z) can be specified using the xyzt_units field. - - METHOD 1 (the "old" way, used only when qform_code = 0): - ------------------------------------------------------- - The coordinate mapping from (i,j,k) to (x,y,z) is the ANALYZE - 7.5 way. This is a simple scaling relationship: - - x = pixdim[1] * i - y = pixdim[2] * j - z = pixdim[3] * k - - No particular spatial orientation is attached to these (x,y,z) - coordinates. (NIFTI-1 does not have the ANALYZE 7.5 orient field, - which is not general and is often not set properly.) This method - is not recommended, and is present mainly for compatibility with - ANALYZE 7.5 files. - - METHOD 2 (used when qform_code > 0, which should be the "normal" case): - --------------------------------------------------------------------- - The (x,y,z) coordinates are given by the pixdim[] scales, a rotation - matrix, and a shift. This method is intended to represent - "scanner-anatomical" coordinates, which are often embedded in the - image header (e.g., DICOM fields (0020,0032), (0020,0037), (0028,0030), - and (0018,0050)), and represent the nominal orientation and location of - the data. This method can also be used to represent "aligned" - coordinates, which would typically result from some post-acquisition - alignment of the volume to a standard orientation (e.g., the same - subject on another day, or a rigid rotation to true anatomical - orientation from the tilted position of the subject in the scanner). - The formula for (x,y,z) in terms of header parameters and (i,j,k) is: - - [ x ] [ R11 R12 R13 ] [ pixdim[1] * i ] [ qoffset_x ] - [ y ] = [ R21 R22 R23 ] [ pixdim[2] * j ] + [ qoffset_y ] - [ z ] [ R31 R32 R33 ] [ qfac * pixdim[3] * k ] [ qoffset_z ] - - The qoffset_* shifts are in the NIFTI-1 header. Note that the center - of the (i,j,k)=(0,0,0) voxel (first value in the dataset array) is - just (x,y,z)=(qoffset_x,qoffset_y,qoffset_z). - - The rotation matrix R is calculated from the quatern_* parameters. - This calculation is described below. - - The scaling factor qfac is either 1 or -1. The rotation matrix R - defined by the quaternion parameters is "proper" (has determinant 1). - This may not fit the needs of the data; for example, if the image - grid is - i increases from Left-to-Right - j increases from Anterior-to-Posterior - k increases from Inferior-to-Superior - Then (i,j,k) is a left-handed triple. In this example, if qfac=1, - the R matrix would have to be - - [ 1 0 0 ] - [ 0 -1 0 ] which is "improper" (determinant = -1). - [ 0 0 1 ] - - If we set qfac=-1, then the R matrix would be - - [ 1 0 0 ] - [ 0 -1 0 ] which is proper. - [ 0 0 -1 ] - - This R matrix is represented by quaternion [a,b,c,d] = [0,1,0,0] - (which encodes a 180 degree rotation about the x-axis). - - METHOD 3 (used when sform_code > 0): - ----------------------------------- - The (x,y,z) coordinates are given by a general affine transformation - of the (i,j,k) indexes: - - x = srow_x[0] * i + srow_x[1] * j + srow_x[2] * k + srow_x[3] - y = srow_y[0] * i + srow_y[1] * j + srow_y[2] * k + srow_y[3] - z = srow_z[0] * i + srow_z[1] * j + srow_z[2] * k + srow_z[3] - - The srow_* vectors are in the NIFTI_1 header. Note that no use is - made of pixdim[] in this method. - - WHY 3 METHODS? - -------------- - Method 1 is provided only for backwards compatibility. The intention - is that Method 2 (qform_code > 0) represents the nominal voxel locations - as reported by the scanner, or as rotated to some fiducial orientation and - location. Method 3, if present (sform_code > 0), is to be used to give - the location of the voxels in some standard space. The sform_code - indicates which standard space is present. Both methods 2 and 3 can be - present, and be useful in different contexts (method 2 for displaying the - data on its original grid; method 3 for displaying it on a standard grid). - - In this scheme, a dataset would originally be set up so that the - Method 2 coordinates represent what the scanner reported. Later, - a registration to some standard space can be computed and inserted - in the header. Image display software can use either transform, - depending on its purposes and needs. - - In Method 2, the origin of coordinates would generally be whatever - the scanner origin is; for example, in MRI, (0,0,0) is the center - of the gradient coil. - - In Method 3, the origin of coordinates would depend on the value - of sform_code; for example, for the Talairach coordinate system, - (0,0,0) corresponds to the Anterior Commissure. - - QUATERNION REPRESENTATION OF ROTATION MATRIX (METHOD 2) - ------------------------------------------------------- - The orientation of the (x,y,z) axes relative to the (i,j,k) axes - in 3D space is specified using a unit quaternion [a,b,c,d], where - a*a+b*b+c*c+d*d=1. The (b,c,d) values are all that is needed, since - we require that a = sqrt(1.0-(b*b+c*c+d*d)) be nonnegative. The (b,c,d) - values are stored in the (quatern_b,quatern_c,quatern_d) fields. - - The quaternion representation is chosen for its compactness in - representing rotations. The (proper) 3x3 rotation matrix that - corresponds to [a,b,c,d] is - - [ a*a+b*b-c*c-d*d 2*b*c-2*a*d 2*b*d+2*a*c ] - R = [ 2*b*c+2*a*d a*a+c*c-b*b-d*d 2*c*d-2*a*b ] - [ 2*b*d-2*a*c 2*c*d+2*a*b a*a+d*d-c*c-b*b ] - - [ R11 R12 R13 ] - = [ R21 R22 R23 ] - [ R31 R32 R33 ] - - If (p,q,r) is a unit 3-vector, then rotation of angle h about that - direction is represented by the quaternion - - [a,b,c,d] = [cos(h/2), p*sin(h/2), q*sin(h/2), r*sin(h/2)]. - - Requiring a >= 0 is equivalent to requiring -Pi <= h <= Pi. (Note that - [-a,-b,-c,-d] represents the same rotation as [a,b,c,d]; there are 2 - quaternions that can be used to represent a given rotation matrix R.) - To rotate a 3-vector (x,y,z) using quaternions, we compute the - quaternion product - - [0,x',y',z'] = [a,b,c,d] * [0,x,y,z] * [a,-b,-c,-d] - - which is equivalent to the matrix-vector multiply - - [ x' ] [ x ] - [ y' ] = R [ y ] (equivalence depends on a*a+b*b+c*c+d*d=1) - [ z' ] [ z ] - - Multiplication of 2 quaternions is defined by the following: - - [a,b,c,d] = a*1 + b*I + c*J + d*K - where - I*I = J*J = K*K = -1 (I,J,K are square roots of -1) - I*J = K J*K = I K*I = J - J*I = -K K*J = -I I*K = -J (not commutative!) - For example - [a,b,0,0] * [0,0,0,1] = [0,0,-b,a] - since this expands to - (a+b*I)*(K) = (a*K+b*I*K) = (a*K-b*J). - - The above formula shows how to go from quaternion (b,c,d) to - rotation matrix and direction cosines. Conversely, given R, - we can compute the fields for the NIFTI-1 header by - - a = 0.5 * sqrt(1+R11+R22+R33) (not stored) - b = 0.25 * (R32-R23) / a => quatern_b - c = 0.25 * (R13-R31) / a => quatern_c - d = 0.25 * (R21-R12) / a => quatern_d - - If a=0 (a 180 degree rotation), alternative formulas are needed. - See the nifti1_io.c function mat44_to_quatern() for an implementation - of the various cases in converting R to [a,b,c,d]. - - Note that R-transpose (= R-inverse) would lead to the quaternion - [a,-b,-c,-d]. - - The choice to specify the qoffset_x (etc.) values in the final - coordinate system is partly to make it easy to convert DICOM images to - this format. The DICOM attribute "Image Position (Patient)" (0020,0032) - stores the (Xd,Yd,Zd) coordinates of the center of the first voxel. - Here, (Xd,Yd,Zd) refer to DICOM coordinates, and Xd=-x, Yd=-y, Zd=z, - where (x,y,z) refers to the NIFTI coordinate system discussed above. - (i.e., DICOM +Xd is Left, +Yd is Posterior, +Zd is Superior, - whereas +x is Right, +y is Anterior , +z is Superior. ) - Thus, if the (0020,0032) DICOM attribute is extracted into (px,py,pz), then - qoffset_x = -px qoffset_y = -py qoffset_z = pz - is a reasonable setting when qform_code=NIFTI_XFORM_SCANNER_ANAT. - - That is, DICOM's coordinate system is 180 degrees rotated about the z-axis - from the neuroscience/NIFTI coordinate system. To transform between DICOM - and NIFTI, you just have to negate the x- and y-coordinates. - - The DICOM attribute (0020,0037) "Image Orientation (Patient)" gives the - orientation of the x- and y-axes of the image data in terms of 2 3-vectors. - The first vector is a unit vector along the x-axis, and the second is - along the y-axis. If the (0020,0037) attribute is extracted into the - value (xa,xb,xc,ya,yb,yc), then the first two columns of the R matrix - would be - [ -xa -ya ] - [ -xb -yb ] - [ xc yc ] - The negations are because DICOM's x- and y-axes are reversed relative - to NIFTI's. The third column of the R matrix gives the direction of - displacement (relative to the subject) along the slice-wise direction. - This orientation is not encoded in the DICOM standard in a simple way; - DICOM is mostly concerned with 2D images. The third column of R will be - either the cross-product of the first 2 columns or its negative. It is - possible to infer the sign of the 3rd column by examining the coordinates - in DICOM attribute (0020,0032) "Image Position (Patient)" for successive - slices. However, this method occasionally fails for reasons that I - (RW Cox) do not understand. ------------------------------------------------------------------------------*/ - - /* [qs]form_code value: */ /* x,y,z coordinate system refers to: */ - /*-----------------------*/ /*---------------------------------------*/ - -/*! \defgroup NIFTI1_XFORM_CODES - \brief nifti1 xform codes to describe the "standard" coordinate system - @{ - */ - /*! Arbitrary coordinates (Method 1). */ - -#define NIFTI_XFORM_UNKNOWN 0 - - /*! Scanner-based anatomical coordinates */ - -#define NIFTI_XFORM_SCANNER_ANAT 1 - - /*! Coordinates aligned to another file's, - or to anatomical "truth". */ - -#define NIFTI_XFORM_ALIGNED_ANAT 2 - - /*! Coordinates aligned to Talairach- - Tournoux Atlas; (0,0,0)=AC, etc. */ - -#define NIFTI_XFORM_TALAIRACH 3 - - /*! MNI 152 normalized coordinates. */ - -#define NIFTI_XFORM_MNI_152 4 -/* @} */ - -/*---------------------------------------------------------------------------*/ -/* UNITS OF SPATIAL AND TEMPORAL DIMENSIONS: - ---------------------------------------- - The codes below can be used in xyzt_units to indicate the units of pixdim. - As noted earlier, dimensions 1,2,3 are for x,y,z; dimension 4 is for - time (t). - - If dim[4]=1 or dim[0] < 4, there is no time axis. - - A single time series (no space) would be specified with - - dim[0] = 4 (for scalar data) or dim[0] = 5 (for vector data) - - dim[1] = dim[2] = dim[3] = 1 - - dim[4] = number of time points - - pixdim[4] = time step - - xyzt_units indicates units of pixdim[4] - - dim[5] = number of values stored at each time point - - Bits 0..2 of xyzt_units specify the units of pixdim[1..3] - (e.g., spatial units are values 1..7). - Bits 3..5 of xyzt_units specify the units of pixdim[4] - (e.g., temporal units are multiples of 8). - - This compression of 2 distinct concepts into 1 byte is due to the - limited space available in the 348 byte ANALYZE 7.5 header. The - macros XYZT_TO_SPACE and XYZT_TO_TIME can be used to mask off the - undesired bits from the xyzt_units fields, leaving "pure" space - and time codes. Inversely, the macro SPACE_TIME_TO_XYZT can be - used to assemble a space code (0,1,2,...,7) with a time code - (0,8,16,32,...,56) into the combined value for xyzt_units. - - Note that codes are provided to indicate the "time" axis units are - actually frequency in Hertz (_HZ), in part-per-million (_PPM) - or in radians-per-second (_RADS). - - The toffset field can be used to indicate a nonzero start point for - the time axis. That is, time point #m is at t=toffset+m*pixdim[4] - for m=0..dim[4]-1. ------------------------------------------------------------------------------*/ - -/*! \defgroup NIFTI1_UNITS - \brief nifti1 units codes to describe the unit of measurement for - each dimension of the dataset - @{ - */ - /*! NIFTI code for unspecified units. */ -#define NIFTI_UNITS_UNKNOWN 0 - - /** Space codes are multiples of 1. **/ - /*! NIFTI code for meters. */ -#define NIFTI_UNITS_METER 1 - /*! NIFTI code for millimeters. */ -#define NIFTI_UNITS_MM 2 - /*! NIFTI code for micrometers. */ -#define NIFTI_UNITS_MICRON 3 - - /** Time codes are multiples of 8. **/ - /*! NIFTI code for seconds. */ -#define NIFTI_UNITS_SEC 8 - /*! NIFTI code for milliseconds. */ -#define NIFTI_UNITS_MSEC 16 - /*! NIFTI code for microseconds. */ -#define NIFTI_UNITS_USEC 24 - - /*** These units are for spectral data: ***/ - /*! NIFTI code for Hertz. */ -#define NIFTI_UNITS_HZ 32 - /*! NIFTI code for ppm. */ -#define NIFTI_UNITS_PPM 40 - /*! NIFTI code for radians per second. */ -#define NIFTI_UNITS_RADS 48 -/* @} */ - -#undef XYZT_TO_SPACE -#undef XYZT_TO_TIME -#define XYZT_TO_SPACE(xyzt) ( (xyzt) & 0x07 ) -#define XYZT_TO_TIME(xyzt) ( (xyzt) & 0x38 ) - -#undef SPACE_TIME_TO_XYZT -#define SPACE_TIME_TO_XYZT(ss,tt) ( (((char)(ss)) & 0x07) \ - | (((char)(tt)) & 0x38) ) - -/*---------------------------------------------------------------------------*/ -/* MRI-SPECIFIC SPATIAL AND TEMPORAL INFORMATION: - --------------------------------------------- - A few fields are provided to store some extra information - that is sometimes important when storing the image data - from an FMRI time series experiment. (After processing such - data into statistical images, these fields are not likely - to be useful.) - - { freq_dim } = These fields encode which spatial dimension (1,2, or 3) - { phase_dim } = corresponds to which acquisition dimension for MRI data. - { slice_dim } = - Examples: - Rectangular scan multi-slice EPI: - freq_dim = 1 phase_dim = 2 slice_dim = 3 (or some permutation) - Spiral scan multi-slice EPI: - freq_dim = phase_dim = 0 slice_dim = 3 - since the concepts of frequency- and phase-encoding directions - don't apply to spiral scan - - slice_duration = If this is positive, AND if slice_dim is nonzero, - indicates the amount of time used to acquire 1 slice. - slice_duration*dim[slice_dim] can be less than pixdim[4] - with a clustered acquisition method, for example. - - slice_code = If this is nonzero, AND if slice_dim is nonzero, AND - if slice_duration is positive, indicates the timing - pattern of the slice acquisition. The following codes - are defined: - NIFTI_SLICE_SEQ_INC == sequential increasing - NIFTI_SLICE_SEQ_DEC == sequential decreasing - NIFTI_SLICE_ALT_INC == alternating increasing - NIFTI_SLICE_ALT_DEC == alternating decreasing - NIFTI_SLICE_ALT_INC2 == alternating increasing #2 - NIFTI_SLICE_ALT_DEC2 == alternating decreasing #2 - { slice_start } = Indicates the start and end of the slice acquisition - { slice_end } = pattern, when slice_code is nonzero. These values - are present to allow for the possible addition of - "padded" slices at either end of the volume, which - don't fit into the slice timing pattern. If there - are no padding slices, then slice_start=0 and - slice_end=dim[slice_dim]-1 are the correct values. - For these values to be meaningful, slice_start must - be non-negative and slice_end must be greater than - slice_start. Otherwise, they should be ignored. - - The following table indicates the slice timing pattern, relative to - time=0 for the first slice acquired, for some sample cases. Here, - dim[slice_dim]=7 (there are 7 slices, labeled 0..6), slice_duration=0.1, - and slice_start=1, slice_end=5 (1 padded slice on each end). - - slice - index SEQ_INC SEQ_DEC ALT_INC ALT_DEC ALT_INC2 ALT_DEC2 - 6 : n/a n/a n/a n/a n/a n/a n/a = not applicable - 5 : 0.4 0.0 0.2 0.0 0.4 0.2 (slice time offset - 4 : 0.3 0.1 0.4 0.3 0.1 0.0 doesn't apply to - 3 : 0.2 0.2 0.1 0.1 0.3 0.3 slices outside - 2 : 0.1 0.3 0.3 0.4 0.0 0.1 the range - 1 : 0.0 0.4 0.0 0.2 0.2 0.4 slice_start .. - 0 : n/a n/a n/a n/a n/a n/a slice_end) - - The SEQ slice_codes are sequential ordering (uncommon but not unknown), - either increasing in slice number or decreasing (INC or DEC), as - illustrated above. - - The ALT slice codes are alternating ordering. The 'standard' way for - these to operate (without the '2' on the end) is for the slice timing - to start at the edge of the slice_start .. slice_end group (at slice_start - for INC and at slice_end for DEC). For the 'ALT_*2' slice_codes, the - slice timing instead starts at the first slice in from the edge (at - slice_start+1 for INC2 and at slice_end-1 for DEC2). This latter - acquisition scheme is found on some Siemens scanners. - - The fields freq_dim, phase_dim, slice_dim are all squished into the single - byte field dim_info (2 bits each, since the values for each field are - limited to the range 0..3). This unpleasantness is due to lack of space - in the 348 byte allowance. - - The macros DIM_INFO_TO_FREQ_DIM, DIM_INFO_TO_PHASE_DIM, and - DIM_INFO_TO_SLICE_DIM can be used to extract these values from the - dim_info byte. - - The macro FPS_INTO_DIM_INFO can be used to put these 3 values - into the dim_info byte. ------------------------------------------------------------------------------*/ - -#undef DIM_INFO_TO_FREQ_DIM -#undef DIM_INFO_TO_PHASE_DIM -#undef DIM_INFO_TO_SLICE_DIM - -#define DIM_INFO_TO_FREQ_DIM(di) ( ((di) ) & 0x03 ) -#define DIM_INFO_TO_PHASE_DIM(di) ( ((di) >> 2) & 0x03 ) -#define DIM_INFO_TO_SLICE_DIM(di) ( ((di) >> 4) & 0x03 ) - -#undef FPS_INTO_DIM_INFO -#define FPS_INTO_DIM_INFO(fd,pd,sd) ( ( ( ((char)(fd)) & 0x03) ) | \ - ( ( ((char)(pd)) & 0x03) << 2 ) | \ - ( ( ((char)(sd)) & 0x03) << 4 ) ) - -/*! \defgroup NIFTI1_SLICE_ORDER - \brief nifti1 slice order codes, describing the acquisition order - of the slices - @{ - */ -#define NIFTI_SLICE_UNKNOWN 0 -#define NIFTI_SLICE_SEQ_INC 1 -#define NIFTI_SLICE_SEQ_DEC 2 -#define NIFTI_SLICE_ALT_INC 3 -#define NIFTI_SLICE_ALT_DEC 4 -#define NIFTI_SLICE_ALT_INC2 5 /* 05 May 2005: RWCox */ -#define NIFTI_SLICE_ALT_DEC2 6 /* 05 May 2005: RWCox */ -/* @} */ - -/*---------------------------------------------------------------------------*/ -/* UNUSED FIELDS: - ------------- - Some of the ANALYZE 7.5 fields marked as ++UNUSED++ may need to be set - to particular values for compatibility with other programs. The issue - of interoperability of ANALYZE 7.5 files is a murky one -- not all - programs require exactly the same set of fields. (Unobscuring this - murkiness is a principal motivation behind NIFTI-1.) - - Some of the fields that may need to be set for other (non-NIFTI aware) - software to be happy are: - - extents dbh.h says this should be 16384 - regular dbh.h says this should be the character 'r' - glmin, } dbh.h says these values should be the min and max voxel - glmax } values for the entire dataset - - It is best to initialize ALL fields in the NIFTI-1 header to 0 - (e.g., with calloc()), then fill in what is needed. ------------------------------------------------------------------------------*/ - -/*---------------------------------------------------------------------------*/ -/* MISCELLANEOUS C MACROS ------------------------------------------------------------------------------*/ - -/*.................*/ -/*! Given a nifti_1_header struct, check if it has a good magic number. - Returns NIFTI version number (1..9) if magic is good, 0 if it is not. */ - -#define NIFTI_VERSION(h) \ - ( ( (h).magic[0]=='n' && (h).magic[3]=='\0' && \ - ( (h).magic[1]=='i' || (h).magic[1]=='+' ) && \ - ( (h).magic[2]>='1' && (h).magic[2]<='9' ) ) \ - ? (h).magic[2]-'0' : 0 ) - -/*.................*/ -/*! Check if a nifti_1_header struct says if the data is stored in the - same file or in a separate file. Returns 1 if the data is in the same - file as the header, 0 if it is not. */ - -#define NIFTI_ONEFILE(h) ( (h).magic[1] == '+' ) - -/*.................*/ -/*! Check if a nifti_1_header struct needs to be byte swapped. - Returns 1 if it needs to be swapped, 0 if it does not. */ - -#define NIFTI_NEEDS_SWAP(h) ( (h).dim[0] < 0 || (h).dim[0] > 7 ) - -/*.................*/ -/*! Check if a nifti_1_header struct contains a 5th (vector) dimension. - Returns size of 5th dimension if > 1, returns 0 otherwise. */ - -#define NIFTI_5TH_DIM(h) ( ((h).dim[0]>4 && (h).dim[5]>1) ? (h).dim[5] : 0 ) - -/*****************************************************************************/ - -/*=================*/ -#ifdef __cplusplus -} -#endif -/*=================*/ - -#endif /* _NIFTI_HEADER_ */ diff --git a/doc/source/gitwash/git_links.inc b/doc/source/gitwash/git_links.inc deleted file mode 100644 index d8bfb0fafe..0000000000 --- a/doc/source/gitwash/git_links.inc +++ /dev/null @@ -1,61 +0,0 @@ -.. This (-*- rst -*-) format file contains commonly used link targets - and name substitutions. It may be included in many files, - therefore it should only contain link targets and name - substitutions. Try grepping for "^\.\. _" to find plausible - candidates for this list. - -.. NOTE: reST targets are - __not_case_sensitive__, so only one target definition is needed for - nipy, NIPY, Nipy, etc... - -.. git stuff -.. _git: https://git-scm.com/ -.. _github: https://github.com -.. _github help: https://help.github.com -.. _msysgit: https://msysgit.github.io/ -.. _git-osx-installer: https://code.google.com/p/git-osx-installer/downloads/list -.. _subversion: https://subversion.apache.org/ -.. _git cheat sheet: https://github.com/guides/git-cheat-sheet -.. _pro git book: https://progit.org/ -.. _git svn crash course: https://git-scm.com/course/svn.html -.. _learn.github: https://guides.github.com/ -.. _network graph visualizer: https://github.com/blog/39-say-hello-to-the-network-graph-visualizer -.. _git user manual: https://www.kernel.org/pub/software/scm/git/docs/user-manual.html -.. _git tutorial: https://www.kernel.org/pub/software/scm/git/docs/gittutorial.html -.. _git community book: https://git-scm.com/book/en/v2 -.. _git ready: http://www.gitready.com/ -.. _git casts: http://www.gitcasts.com/ -.. _Fernando's git page: http://www.fperez.org/py4science/git.html -.. _git magic: http://www-cs-students.stanford.edu/~blynn/gitmagic/index.html -.. _git concepts: http://www.sbf5.com/~cduan/technical/git/ -.. _git clone: https://www.kernel.org/pub/software/scm/git/docs/git-clone.html -.. _git checkout: https://www.kernel.org/pub/software/scm/git/docs/git-checkout.html -.. _git commit: https://www.kernel.org/pub/software/scm/git/docs/git-commit.html -.. _git push: https://www.kernel.org/pub/software/scm/git/docs/git-push.html -.. _git pull: https://www.kernel.org/pub/software/scm/git/docs/git-pull.html -.. _git add: https://www.kernel.org/pub/software/scm/git/docs/git-add.html -.. _git status: https://www.kernel.org/pub/software/scm/git/docs/git-status.html -.. _git diff: https://www.kernel.org/pub/software/scm/git/docs/git-diff.html -.. _git log: https://www.kernel.org/pub/software/scm/git/docs/git-log.html -.. _git branch: https://www.kernel.org/pub/software/scm/git/docs/git-branch.html -.. _git remote: https://www.kernel.org/pub/software/scm/git/docs/git-remote.html -.. _git rebase: https://www.kernel.org/pub/software/scm/git/docs/git-rebase.html -.. _git config: https://www.kernel.org/pub/software/scm/git/docs/git-config.html -.. _why the -a flag?: http://www.gitready.com/beginner/2009/01/18/the-staging-area.html -.. _git staging area: http://www.gitready.com/beginner/2009/01/18/the-staging-area.html -.. _tangled working copy problem: http://2ndscale.com/rtomayko/2008/the-thing-about-git -.. _git management: http://kerneltrap.org/Linux/Git_Management -.. _linux git workflow: https://www.mail-archive.com/dri-devel@lists.sourceforge.net/msg39091.html -.. _git parable: http://tom.preston-werner.com/2009/05/19/the-git-parable.html -.. _git foundation: https://matthew-brett.github.io/pydagogue/foundation.html -.. _deleting master on github: https://matthew-brett.github.io/pydagogue/gh_delete_master.html -.. _rebase without tears: https://matthew-brett.github.io/pydagogue/rebase_without_tears.html -.. _resolving a merge: https://www.kernel.org/pub/software/scm/git/docs/user-manual.html#resolving-a-merge -.. _ipython git workflow: http://mail.scipy.org/pipermail/ipython-dev/2010-October/006746.html - -.. other stuff -.. _python: https://www.python.org - -.. |emdash| unicode:: U+02014 - -.. vim: ft=rst diff --git a/doc/source/gitwash/known_projects.inc b/doc/source/gitwash/known_projects.inc deleted file mode 100644 index 1761d975aa..0000000000 --- a/doc/source/gitwash/known_projects.inc +++ /dev/null @@ -1,41 +0,0 @@ -.. Known projects - -.. PROJECTNAME placeholders -.. _PROJECTNAME: http://nipy.org -.. _`PROJECTNAME github`: https://github.com/nipy -.. _`PROJECTNAME mailing list`: https://mail.python.org/mailman/listinfo/neuroimaging - -.. numpy -.. _numpy: http://www.numpy.org -.. _`numpy github`: https://github.com/numpy/numpy -.. _`numpy mailing list`: http://mail.scipy.org/mailman/listinfo/numpy-discussion - -.. scipy -.. _scipy: https://www.scipy.org -.. _`scipy github`: https://github.com/scipy/scipy -.. _`scipy mailing list`: http://mail.scipy.org/mailman/listinfo/scipy-dev - -.. nipy -.. _nipy: http://nipy.org/nipy -.. _`nipy github`: https://github.com/nipy/nipy -.. _`nipy mailing list`: https://mail.python.org/mailman/listinfo/neuroimaging - -.. ipython -.. _ipython: https://ipython.org -.. _`ipython github`: https://github.com/ipython/ipython -.. _`ipython mailing list`: http://mail.scipy.org/mailman/listinfo/IPython-dev - -.. dipy -.. _dipy: http://nipy.org/dipy -.. _`dipy github`: https://github.com/Garyfallidis/dipy -.. _`dipy mailing list`: https://mail.python.org/mailman/listinfo/neuroimaging - -.. nibabel -.. _nibabel: http://nipy.org/nibabel -.. _`nibabel github`: https://github.com/nipy/nibabel -.. _`nibabel mailing list`: https://mail.python.org/mailman/listinfo/neuroimaging - -.. marsbar -.. _marsbar: http://marsbar.sourceforge.net -.. _`marsbar github`: https://github.com/matthew-brett/marsbar -.. _`MarsBaR mailing list`: https://lists.sourceforge.net/lists/listinfo/marsbar-users diff --git a/doc/source/gitwash/links.inc b/doc/source/gitwash/links.inc deleted file mode 100644 index 20f4dcfffd..0000000000 --- a/doc/source/gitwash/links.inc +++ /dev/null @@ -1,4 +0,0 @@ -.. compiling links file -.. include:: known_projects.inc -.. include:: this_project.inc -.. include:: git_links.inc diff --git a/doc/source/gitwash/this_project.inc b/doc/source/gitwash/this_project.inc deleted file mode 100644 index 4557f70556..0000000000 --- a/doc/source/gitwash/this_project.inc +++ /dev/null @@ -1,3 +0,0 @@ -.. nibabel -.. _nibabel: http://nipy.org/nibabel -.. _`nibabel mailing list`: https://mail.python.org/mailman/listinfo/neuroimaging diff --git a/doc/source/legal.rst b/doc/source/legal.rst deleted file mode 120000 index 7d29222e4c..0000000000 --- a/doc/source/legal.rst +++ /dev/null @@ -1 +0,0 @@ -../../COPYING \ No newline at end of file diff --git a/doc/source/links_names.txt b/doc/source/links_names.txt deleted file mode 100644 index 1ab1242c08..0000000000 --- a/doc/source/links_names.txt +++ /dev/null @@ -1,256 +0,0 @@ -.. -*- rst -*- -.. vim: ft=rst - -.. This rst format file contains commonly used link targets - and name substitutions. It may be included in many files, - therefore it should only contain link targets and name - substitutions. Try grepping for "^\.\. _" to find plausible - candidates for this list. - - -.. NOTE: reST targets are - __not_case_sensitive__, so only one target definition is needed for - nipy, NIPY, Nipy, etc... - -.. nibabel -.. _nibabel: http://nipy.org/nibabel -.. _`nibabel github`: https://github.com/nipy/nibabel -.. _`nibabel mailing list`: https://mail.python.org/mailman/listinfo/neuroimaging -.. _nibabel pypi: https://pypi.python.org/pypi/nibabel -.. _nibabel issues: https://github.com/nipy/nibabel/issues -.. _nibabel github issues: https://github.com/nipy/nibabel/issues -.. _nibabel wiki: https://github.com/nipy/nibabel.wiki -.. _nibabel on travis: https://travis-ci.org/nipy/nibabel - -.. other related projects -.. _nipy community: http://nipy.org -.. _nipy: http://nipy.org/nipy -.. _`Brain Imaging Center`: http://bic.berkeley.edu/ -.. _dipy: http://nipy.org/dipy -.. _`dipy github`: https://github.com/Garyfallidis/dipy -.. _nibabel: http://nipy.org/nibabel -.. _nipy development guidelines: http://nipy.org/devel -.. _`nipy github`: https://github.com/nipy/nipy -.. _nipy buildbot: https://nipy.bic.berkeley.edu -.. _travis-ci: https://travis-ci.org -.. _nibotmi: https://github.com/nipy/nibotmi - -.. Documentation tools -.. _graphviz: http://www.graphviz.org/ -.. _Sphinx: http://sphinx-doc.org/ -.. _`Sphinx reST`: http://sphinx-doc.org/rest.html -.. _reST: http://docutils.sourceforge.net/rst.html -.. _docutils: http://docutils.sourceforge.net -.. _IPython notebook viewer: http://nbviewer.ipython.org -.. _zenodo: https://zenodo.org - -.. Licenses -.. _GPL: https://www.gnu.org/licenses/gpl.html -.. _BSD: http://www.opensource.org/licenses/bsd-license.php -.. _LGPL: https://www.gnu.org/copyleft/lesser.html -.. _MIT License: http://www.opensource.org/licenses/mit-license.php -.. _PDDL 1.0: http://opendatacommons.org/licenses/pddl/1.0/ -.. _CC0: http://opendefinition.org/licenses/cc-zero - - -.. Installation -.. _pypi: https://pypi.python.org/pypi - -.. Working process -.. _pynifti: http://niftilib.sourceforge.net/pynifti/ -.. _nifticlibs: http://nifti.nimh.nih.gov -.. _nifti: http://nifti.nimh.nih.gov -.. _`nipy sourceforge`: http://nipy.sourceforge.net/ -.. _sourceforge: http://nipy.sourceforge.net/ -.. _`nipy launchpad`: https://launchpad.net/nipy -.. _launchpad: https://launchpad.net/ -.. _`nipy trunk`: https://github.com/nipy/nipy/tree/master -.. _`nipy mailing list`: https://mail.python.org/mailman/listinfo/neuroimaging -.. _`nipy bugs`: https://github.com/nipy/nipy/issues -.. _sourceforge download page: https://github.com/nipy/nipy/releases - -.. Code support stuff -.. _pychecker: http://pychecker.sourceforge.net/ -.. _pylint: https://bitbucket.org/logilab/pylint/ -.. _pyflakes: https://github.com/pyflakes/pyflakes -.. _virtualenv: https://virtualenv.pypa.io/en/latest/ -.. _github: https://github.com -.. _flymake: http://flymake.sourceforge.net/ -.. _rope: http://rope.sourceforge.net/ -.. _pymacs: http://pymacs.progiciels-bpi.ca/pymacs.html -.. _ropemacs: http://rope.sourceforge.net/ropemacs.html -.. _ECB: http://ecb.sourceforge.net/ -.. _emacs_python_mode: http://www.emacswiki.org/cgi-bin/wiki/PythonMode -.. _doctest-mode: http://ed.loper.org/projects/doctestmode/ -.. _nose: http://somethingaboutorange.com/mrl/projects/nose -.. _pytest: https://docs.pytest.org/ -.. _`python coverage tester`: http://nedbatchelder.com/code/coverage/ -.. _bitbucket: https://bitbucket.org -.. _six: http://pythonhosted.org/six - -.. Other python projects -.. _numpy: http://www.numpy.org -.. _scipy: https://www.scipy.org -.. _ipython: https://ipython.org -.. _`ipython github`: https://github.com/ipython/ipython -.. _`ipython mailing list`: http://mail.scipy.org/mailman/listinfo/IPython-dev -.. _`ipython manual`: https://ipython.org/ipython-doc/stable/index.html -.. _matplotlib: http://matplotlib.org/ -.. _pythonxy: http://www.pythonxy.com -.. _EPD: https://www.enthought.com/products/epd/ -.. _ETS: http://code.enthought.com/ -.. _`Enthought Tool Suite`: http://code.enthought.com/ -.. _python: https://www.python.org -.. _mayavi: http://mayavi.sourceforge.net/ -.. _sympy: http://www.sympy.org/ -.. _networkx: https://networkx.github.io/ -.. _setuptools: https://pypi.python.org/pypi/setuptools -.. _distribute: https://pythonhosted.org/distribute -.. _pip: https://pip.readthedocs.org/en/latest -.. _pip install instructions: - https://pip.readthedocs.org/en/latest/installing.html -.. _twine: https://pypi.python.org/pypi/twine -.. _datapkg: https://pythonhosted.org/datapkg/ -.. _python imaging library: https://pypi.python.org/pypi/Pillow -.. _h5py: https://www.h5py.org/ -.. _packaging: https://packaging.pypa.io -.. _importlib-resources: https://importlib-resources.readthedocs.io/ - -.. Python imaging projects -.. _PyMVPA: http://www.pymvpa.org -.. _BrainVISA: http://brainvisa.info -.. _anatomist: http://brainvisa.info -.. _pydicom: http://www.pydicom.org/ - -.. Not so python imaging projects -.. _matlab: https://www.mathworks.com -.. _spm: http://www.fil.ion.ucl.ac.uk/spm -.. _spm8: http://www.fil.ion.ucl.ac.uk/spm/software/spm8 -.. _eeglab: http://sccn.ucsd.edu/eeglab -.. _AFNI: http://afni.nimh.nih.gov/afni -.. _FSL: http://www.fmrib.ox.ac.uk/fsl -.. _FreeSurfer: https://surfer.nmr.mgh.harvard.edu -.. _voxbo: https://www.nitrc.org/projects/voxbo/ -.. _mricron: http://www.mccauslandcenter.sc.edu/mricro/mricron/ -.. _slicer: http://www.slicer.org/ - -.. File formats -.. _DICOM: http://medical.nema.org/ -.. _`wikipedia DICOM`: https://en.wikipedia.org/wiki/Digital_Imaging_and_Communications_in_Medicine -.. _GDCM: http://gdcm.sourceforge.net/wiki/ -.. _DICOM standard : http://medical.nema.org/standard.html -.. _PS 3.1: http://medical.nema.org/Dicom/2011/11_01pu.pdf -.. _PS 3.2: http://medical.nema.org/Dicom/2011/11_02pu.pdf -.. _PS 3.3: http://medical.nema.org/Dicom/2011/11_03pu.pdf -.. _PS 3.4: http://medical.nema.org/Dicom/2011/11_04pu.pdf -.. _PS 3.5: http://medical.nema.org/Dicom/2011/11_05pu.pdf -.. _PS 3.6: http://medical.nema.org/Dicom/2011/11_06pu.pdf -.. _PS 3.7: http://medical.nema.org/Dicom/2011/11_07pu.pdf -.. _PS 3.8: http://medical.nema.org/Dicom/2011/11_08pu.pdf -.. _PS 3.10: http://medical.nema.org/Dicom/2011/11_10pu.pdf -.. _PS 3.11: http://medical.nema.org/Dicom/2011/11_11pu.pdf -.. _PS 3.12: http://medical.nema.org/Dicom/2011/11_12pu.pdf -.. _PS 3.14: http://medical.nema.org/Dicom/2011/11_14pu.pdf -.. _PS 3.15: http://medical.nema.org/Dicom/2011/11_15pu.pdf -.. _PS 3.16: http://medical.nema.org/Dicom/2011/11_16pu.pdf -.. _PS 3.17: http://medical.nema.org/Dicom/2011/11_17pu.pdf -.. _PS 3.18: http://medical.nema.org/Dicom/2011/11_18pu.pdf -.. _PS 3.19: http://medical.nema.org/Dicom/2011/11_19pu.pdf -.. _PS 3.20: http://medical.nema.org/Dicom/2011/11_20pu.pdf -.. _`DICOM specs`: ftp://medical.nema.org/medical/dicom/2011/ -.. _DICOM data structures: http://medical.nema.org/dicom/2011/11_05pu.pdf -.. _DICOM data dictionary: http://medical.nema.org/dicom/2011/11_05pu.pdf -.. _`DICOM object definitions`: ftp://medical.nema.org/medical/dicom/2011/11_03pu3.pdf - -.. _dcm2nii: http://www.cabiatl.com/mricro/mricron/dcm2nii.html -.. _`mricron install`: http://www.cabiatl.com/mricro/mricron/install.html -.. _dicom2nrrd: http://www.slicer.org/slicerWiki/index.php/Modules:DicomToNRRD-3.4 -.. _Nrrd: http://teem.sourceforge.net/nrrd/format.html - -.. General software -.. _gcc: https://gcc.gnu.org -.. _xcode: https://developer.apple.com/TOOLS/xcode -.. _mingw: http://www.mingw.org -.. _cygwin: https://cygwin.com -.. _macports: https://www.macports.org/ -.. _VTK: http://www.vtk.org/ -.. _ITK: http://www.itk.org/ -.. _swig: http://www.swig.org - -.. version control -.. _git: https://git-scm.com -.. _mercurial: https://mercurial.selenic.com -.. _bzr: http://bazaar.canonical.com -.. _subversion: https://subversion.apache.org - -.. Functional imaging labs -.. _`functional imaging laboratory`: http://www.fil.ion.ucl.ac.uk -.. _FMRIB: http://www.fmrib.ox.ac.uk - -.. Other organizations -.. _enthought: -.. _kitware: http://www.kitware.com -.. _NeuroDebian: http://neuro.debian.net -.. _nibabel NeuroDebian: http://neuro.debian.net/pkgs/python-nibabel.html -.. _nitrc: https://www.nitrc.org - -.. General information links -.. _`wikipedia FMRI`: https://en.wikipedia.org/wiki/Functional_magnetic_resonance_imaging -.. _`wikipedia PET`: https://en.wikipedia.org/wiki/Positron_emission_tomography -.. _ANALYZE: http://www.grahamwideman.com/gw/brain/analyze/formatdoc.htm -.. _NIfTI1: http://nifti.nimh.nih.gov/nifti-1/ -.. _NIfTI2: http://nifti.nimh.nih.gov/nifti-2/ -.. _MINC: https://www.mcgill.ca/bic/software/minc -.. _GIFTI: https://www.nitrc.org/projects/gifti -.. _MINC1: - https://en.wikibooks.org/wiki/MINC/Reference/MINC1_File_Format_Reference -.. _MINC2: - https://en.wikibooks.org/wiki/MINC/Reference/MINC2.0_File_Format_Reference - -.. Mathematical methods -.. _`wikipedia ICA`: https://en.wikipedia.org/wiki/Independent_component_analysis -.. _`wikipedia PCA`: https://en.wikipedia.org/wiki/Principal_component_analysis - -.. Mathematical ideas -.. _`wikipedia spherical coordinate system`: https://en.wikipedia.org/wiki/Spherical_coordinate_system -.. _`mathworld spherical coordinate system`: http://mathworld.wolfram.com/SphericalCoordinates.html -.. _`wikipedia affine`: https://en.wikipedia.org/wiki/Affine_transformation -.. _`wikipedia affine transform`: https://en.wikipedia.org/wiki/Affine_transformation -.. _`wikipedia linear transform`: https://en.wikipedia.org/wiki/Linear_transformation -.. _`wikipedia rotation matrix`: https://en.wikipedia.org/wiki/Rotation_matrix -.. _`wikipedia homogeneous coordinates`: https://en.wikipedia.org/wiki/Homogeneous_coordinates -.. _`wikipedia axis angle`: https://en.wikipedia.org/wiki/Axis_angle -.. _`wikipedia Euler angles`: https://en.wikipedia.org/wiki/Euler_angles -.. _`Mathworld Euler angles`: http://mathworld.wolfram.com/EulerAngles.html -.. _`wikipedia quaternion`: https://en.wikipedia.org/wiki/Quaternion -.. _`wikipedia shear matrix`: https://en.wikipedia.org/wiki/Shear_matrix -.. _`wikipedia reflection`: https://en.wikipedia.org/wiki/Reflection_(mathematics) -.. _`wikipedia direction cosine`: https://en.wikipedia.org/wiki/Direction_cosine -.. _`wikipedia aliasing`: https://en.wikipedia.org/wiki/Aliasing - -.. Programming ideas -.. _proxy: https://en.wikipedia.org/wiki/Proxy_pattern - -.. philosophy -.. _0SAGA: http://nipyworld.blogspot.com/2010/11/0saga-software-model.html - -.. People -.. _Matthew Brett: http://matthew.dynevor.org -.. _Yaroslav O. Halchenko: http://www.onerussian.com -.. _Michael Hanke: http://mih.voxindeserto.de -.. _Gaël Varoquaux: http://gael-varoquaux.info/ -.. _Stephan Gerhard: http://www.unidesign.ch -.. _Ben Cipollini: http://bcipolli.github.io/ -.. _Eric Larson: https://staff.washington.edu/larsoner -.. _Marc-Alexandre Côté: https://marccote.github.io -.. _Jarrod Millman: http://www.jarrodmillman.com/ -.. _Alexandre Gramfort: http://alexandre.gramfort.net -.. _Ariel Rokem: http://arokem.org -.. _Bertrand Thirion: https://team.inria.fr/parietal/bertrand-thirions-page -.. _Nolan Nichols: http://www.nolan-nichols.com -.. _Satrajit Ghosh: http://satra.cogitatum.org -.. _Chris Rorden: http://www.mccauslandcenter.sc.edu/crnl/chris-rorden - -.. Substitutions -.. |emdash| unicode:: U+02014 -.. |--| unicode:: U+02014 diff --git a/doc/source/make.bat b/doc/source/make.bat deleted file mode 100644 index aa5985eece..0000000000 --- a/doc/source/make.bat +++ /dev/null @@ -1,112 +0,0 @@ -@ECHO OFF - -REM Command file for Sphinx documentation - -set SPHINXBUILD=sphinx-build -set ALLSPHINXOPTS=-d _build/doctrees %SPHINXOPTS% . -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% -) - -if "%1" == "" goto help - -if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. changes to make an overview over all changed/added/deprecated items - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - goto end -) - -if "%1" == "clean" ( - for /d %%i in (_build\*) do rmdir /q /s %%i - del /q /s _build\* - goto end -) - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% _build/html - echo. - echo.Build finished. The HTML pages are in _build/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% _build/dirhtml - echo. - echo.Build finished. The HTML pages are in _build/dirhtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% _build/pickle - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% _build/json - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% _build/htmlhelp - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in _build/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% _build/qthelp - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in _build/qthelp, like this: - echo.^> qcollectiongenerator _build\qthelp\nipype.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile _build\qthelp\nipype.ghc - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% _build/latex - echo. - echo.Build finished; the LaTeX files are in _build/latex. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% _build/changes - echo. - echo.The overview file is in _build/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% _build/linkcheck - echo. - echo.Link check complete; look for any errors in the above output ^ -or in _build/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% _build/doctest - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in _build/doctest/output.txt. - goto end -) - -:end diff --git a/doc/source/notebooks/.gitignore b/doc/source/notebooks/.gitignore deleted file mode 100644 index 87620ac7e7..0000000000 --- a/doc/source/notebooks/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.ipynb_checkpoints/ diff --git a/doc/source/old/design.txt b/doc/source/old/design.txt deleted file mode 100644 index 35901977b5..0000000000 --- a/doc/source/old/design.txt +++ /dev/null @@ -1,257 +0,0 @@ -.. -*- mode: rst -*- - -================================= - Images, headers and code design -================================= - -In which we set out how we are thinking of medical image formats and -their commonalities. - -Headers -======= - -Headers contain two types of information: - -#. *howto* data: stuff to tell you how to read the image array data from - file. This must include the shape of the image array and the numeric - representation (float32, int16), as well as implicit or explicit - position of the data relative to the beginning of the data file - (offset). It can be complicated; ECAT - for example - can contain - more than one frame, and the datatype can be different for each - frame. -#. *whatis* data: metadata about the meaning of the image array on file. - We are interested in the relationship of the voxel positions in the - data array to space in the real world. In practice (SPM Analyze, - NIfTI, MINC) this can always be represented as an affine relating - voxel coordinates to real world coordinates. We may also be - interested in what the 'real world' is. Neither MINC (1.x) nor - Analyze stores this, but NIfTI can. - -Howto data ----------- - -In order to get data out of files, any image reader will need either - -the header itself, or selected fields from the header. - -Different images can make use of different parts of the header, because -the images will work with only a specified set of headers - as dictated -by the image itself. - -* in-file data numeric type - ``io_dtype``. This has no necessary - relation to the dtype of the data in memory, because scaling factors - may be applied. For reading, we may not need this as part of the - public interface, we can just use it internally to cast the read - memory to an array. Setting this attribute will change the output - dtype on writing. ECAT file format can have different dtypes per - frame; for reading, we just cast up to a dtype that can hold all the - frame dtypes; for writing, we may just write as one type, or disallow - writing altogether. -* array shape - ``shape``. -* byte offset - ``offset`` at which data starts. This is not relevant - for the way we currently read MINC files for example - and may not be - relevant for ECAT files, in the sense that it may be the offset to - only one of the frames in the image, and the frames are of different - length. - - -Images -====== - -We think of an image as being the association of: - -#. A data array, of at least three dimensions, where the first three - dimensions of the array are spatial. -#. A transformation mapping the spatial array (voxel) coordinates to some real - continuous space (real-world transform). -#. A definition of what this space *is* ('scanner', 'mni', etc). - -.. note:: - - Why are the first three dimensions spatial? - - For simplicity, we want the transformation (above) to be spatial. - Because the images are always at least 3D, and the transform is - spatial, this means that the transformation is always exactly 3D. We - have to know which of the N image dimensions are spatial. For - example, if we have a 4D (space and time) image, we need to know - which of the 4 dimensions are spatial. We could ask the image to - tell us, but the simplest thing is to assert which dimensions are - spatial by convention, and obey that convention with our image - readers. - - Right, but why the *first* three dimensions? - - Of course, it could be the last three dimensions. We chose to use - the first three dimensions because that is the convention encoded in - the NIfTI standard, at least implicitly, and it will be familiar to - users of packages like SPM. Users of Numpy will have a slight - preference for the first dimension of an array being the slowest - changing on disk, and the instinct that time, rather than space, will - usually be the slowest changing dimension on disk, but we didn't want - to break the NIfTI and SPM conventions, on the basis of this - instinct, because the instinct is difficult to explain to people who - don't have it. - -So, our image likely has:: - - img.data - img.affine - img.output_space - img.meta - img.format - -where meta is a dictionary and format is an object that implements the -image format API - see :ref:`image-formats` - -This immediately suggests the following interface:: - - img = Image(data, affine=None, output_space=None, - meta=None, format=None, filename=None) - -The output space is a string - - img.output_space == 'mni' - -When there is no known output space, it is ``None``. - -The ``format`` attribute is part of the bridge pattern. That is, the -``format`` object provides the implementation for things that an image -might want to do, or have done to it. The format will differ depending -on the input or output type. What might we want to do to an image? We -might imagine these methods:: - - img.load(filename, format=None) # class method - img.save(filename=None, format=None) - img.as_file(filemaker=None, format=None) - -and some things that formats generally support like:: - - img.write_header(filename=None) - img.write_data(data=None, filename=None, slicedef=None) - -``img.as_file`` returns the image as saved to disk; the image might -completely correspond to something on disk, in which case it may return -its own filename, or it might not correspond to something on disk, in -which case it saves itself to disk with the given format, and returns -the filename it has used. - -Data proxies - and lightweight images -------------------------------------- - -A particular use-case is where we want to part-load the image, but we do -not yet want all the data, as the data can be very large and slow to -load, or take up a lot of memory. - -For that case, the ``data`` attribute is a proxy object, subclassing -ndarray, that knows to load itself when the data is accessed. - -The proxy object implements at least ``.shape``, but otherwise defers to -the on-disk version of the array. - -The ``format`` object deals with this action. That is, the ``data`` -object will have a pointer to the ``format`` attribute in some form - -perhaps in the form of a ``format.get_data`` method. - -Of course, this is an optimization, and does not affect the interface -for the ``Image`` (although it might affect the interface for -``Format``. - - -Empty image ------------ - -This is a reminder of Souheil Inati's use-case - the iterative write. -Perhaps something like:: - - empty_image = Image.empty(shape=(64,64,30,150), affine=np.eye(4)) - empty_image.set_filespec('some_image.nii.gz') - empty_image.write_header() - for i in range(150): - slicer = (slice(None),)*3 + (i,) - data = np.random.normal(size=(64,64,30)) - empty_image.write_data(data, slice=slicer) - - -Images and files and filenames ------------------------------- - -Various image formats can have more than one filename per image. NIfTI -is the obvious example because it can be either a single file:: - - some_image.nii - -or a pair of files (like Analyze):: - - some_image.img - some_image.hdr - -SPM Analyze adds an optional extra data file in Matlab ``.mat`` format:: - - some_image.img - some_image.hdr - some_image.mat - -Of course there are rules / rules-of-thumb as to what extensions these -various filenames can be. - -We may want to associate an image with a filename or set of filenames. -But we may also want to be able to associate images with file-like -objects, such as open files, or anything else that implements a file -protocol. - -The image ``format`` will know what the ``image`` needs in terms of -files. For example, a single file NIfTI image will need a single -filename or single file-like object, whereas a NIfTI pair will need two -files and two file-like objects. - -Let's call a full specification of what the format needs a *filedef*. -For the moment, let's imagine that is a dictionary with keys ``image``, -``header``, and optional ``mat``. The values can be filenames or -file-like objects. A *filespec* is some argument or set of arguments -that allow us to fully specify a *filedef*. - -The simple case of a single-file NIfTI image:: - - img = Image(data, filespec='some_image.nii') - img.filedef == {'image': 'some_image.nii', - 'header': 'some_image.nii'} - -In this case, we haven't specified the format, and the Image constructor -tries to work out the format from the filespec. - -Consider:: - - img = Image(data, filespec='some_image.nii', - format=Nifti1SingleFormat) - -also OK. But:: - - img = Image(data, filespec='some_image.nii', format=AnalyzeFormat) - -might raise an error. - -For SPM analyze format: - - img = Image(data, filespec='some_image.img', format=AnalyzeFormat) - img.filedef == {'image': 'some_image.img', - 'header': 'some_image.hdr'} - -Now, for file-like objects:: - - fobj = open('some_image.nii') - img = Image(data, filespec=fobj) - img.filedef == {'image': fobj, - 'header': fobj} - -might work - although the Image constructor would have to be smart -enough to work out that this was ``Nifti1SingleFormat``. Or it might be -the default. - - img = Image(data, filespec=fobj, format=AnalyzeFormat) - -might raise an error, on the lines of:: - - FormatError('Need image and header file-like objects for Analyze') - -- or it might just assume that you mean for the image and the header to - be the same file. Perhaps that is too implicit. diff --git a/doc/source/old/examples.txt b/doc/source/old/examples.txt deleted file mode 100644 index dfbc2b4d9e..0000000000 --- a/doc/source/old/examples.txt +++ /dev/null @@ -1,153 +0,0 @@ -.. -*- mode: rst -*- -.. ex: set sts=4 ts=4 sw=4 et tw=79: - ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### - # - # See COPYING file distributed along with the NiBabel package for the - # copyright and license terms. - # - ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### - - -******** -Examples -******** - -The next sections contains some examples showing ways to use NiBabel to -read and write imaging data from within Python to be able to process it with -some random Python library. - -All examples assume that you have imported the NiBabel module by invoking: - - >>> import nibabel as nib - -and the ``os.path.join`` and ``os.path.split`` commands with: - - >>> from os.path import join as pjoin, split as psplit - -and we have made a temporary directory for the files we are going to write: - - >>> import tempfile - >>> tmpdir = tempfile.mkdtemp() - -and we've got the path to the nifti example data: - - >>> from nibabel.testing import data_path as example_data_path - -Loading and saving NIfTI files -============================== - -First we will open the tiny example NIfTI file that is included in the NiBabel -source tarball. No filename extension is necessary as libniftiio determines it -automatically: - - >>> example_4d_fname = pjoin(example_data_path, 'example4d.nii.gz') - >>> img = nib.load(example_4d_fname) - -If you want to save this image as an uncompressed image simply do: - - >>> # a filename in our temporary directory - >>> fname = pjoin(tmpdir, 'something.nii') - >>> nib.save(img, fname) - - -NIfTI files from array data -=========================== - -The next code snipped demonstrates how to create a 4d NIfTI image containing -gaussian noise. First we need to import the NumPy module - - >>> import numpy as np - -Now we generate the noise dataset. Let's generate noise for 100 volumes with 16 -slices and a 32x32 inplane matrix. - - >>> noise = np.random.randn(32, 32, 16, 100) - -The datatype of the array is by default ``float64``, which can be verified by: - - >>> noise.dtype - dtype('float64') - -Converting this dataset into a NIfTI image is done by invoking the -:class:`~nibabel.Nifti1Image` constructor with the noise dataset as argument: - - >>> nim = nib.Nifti1Image(noise, np.eye(4)) - -The relevant header information is extracted from the NumPy array. If you -query the header information about the dimensionality of the image, it returns -the desired values: - - >>> print nim.header['dim'] - [ 4 32 32 16 100 1 1 1] - -First value shows the number of dimensions in the dataset: 4 (good, that's what -we wanted). The following numbers are dataset size on the x, y, z, t, u, v, w -axis (NIfTI files can handle up to 7 dimensions). - -Also the datatype was set appropriately: - - >>> print nim.get_data_dtype() - float64 - -To save the noise file to disk, we can simply call the -:meth:`~nifti.image.NiftiImage.save` method: - - >>> # a filename in our temporary directory - >>> noise_fname = pjoin(tmpdir, 'noise.nii.gz') - >>> nib.save(nim, noise_fname) - - -Select ROIs -=========== - -Suppose you want to have the first ten volumes of the noise dataset we have -previously created in a separate file. First, we open the file: - - >>> nim = nib.load(noise_fname) - -Now we select the first ten volumes and store them to another file, while -preserving as much header information as possible - - >>> nim2 = nib.Nifti1Image(nim.get_fdata()[..., :10], - ... nim.get_affine(), - ... nim.header) - >>> print nim2.header['dim'] - [ 4 32 32 16 10 1 1 1] - >>> # a filename in our temporary directory - >>> fname = pjoin(tmpdir, 'part.hdr.gz') - >>> nib.save(nim2, fname) - -The :class:`~nifti.image.NiftiImage` constructor takes a dictionary with header -information as an optional argument. Settings that are not determined by the -array (e.g. size, datatype) are taken from the dictionary and stored to the -new NIfTI image. - - -Linear detrending of timeseries (SciPy module is required for this example) -=========================================================================== - -Let's load another 4d NIfTI file and perform a linear detrending, by fitting -a straight line to the timeseries of each voxel and subtract that fit from -the data. Although this might sound complicated at first, thanks to the -excellent SciPy module it is just a few lines of code. For this example we -will first create a NIfTI image with just a single voxel and 50 timepoints -(basically a linear function with some noise): - - >>> nim = nib.Nifti1Image( - ... (np.linspace(0,100) + np.random.randn(50)).reshape(1,1,1,50), - ... np.eye(4)) - >>> print nim.header['dim'] - [ 4 1 1 1 50 1 1 1] - -Remember that the array has the time axis as its first dimension (in contrast -to the NIfTI file where it is the 4th). - - >>> from scipy import signal - >>> data_detrended = signal.detrend(nim.get_fdata(), axis=0) - -Finally, create a new NIfTI image using header information from the original -source image. - - >>> nim_detrended = nib.Nifti1Image(data_detrended, - ... nim.get_affine(), - ... nim.header) diff --git a/doc/source/old/format_design.txt b/doc/source/old/format_design.txt deleted file mode 100644 index fdbf9419ba..0000000000 --- a/doc/source/old/format_design.txt +++ /dev/null @@ -1,109 +0,0 @@ -.. -*- rst -*- - -.. _image-formats: - -===================== - Images and formats -===================== - -The Image object contains (*has a*) Format object. - -The Image and the Format objects form a `bridge pattern -`_. In the `wikipedia -diagram -`_ the -Image class plays the role of the Abstraction, and the Format plays the -role of the implementer. - -The Format object provides an interface to the underlying file format. - -The Image has the following methods: - -* img.get_data() -* img.save(fname) - -It has attributes: - -* affine -* world -* io_dtype -* format - -We get the data with ``get_data()``, rather than via an attribute, to -reflect that fact that the data is read-only, and to flag the common -case where the data load is delayed until the data is used. The object -can decide what it does about data caching between calls of -``get_data()``. Another option is to make the data a cached property or -single-shot data descriptor; I prefer using the method call, for -simplicity, and to make clear that the data load may take a long time. - -Example code:: - - import numpy as np - from nibabel import Image - from nibabel.formats import Nifti1 - from nibabel.ref import mni - arr = np.arange(24).reshape(2,3,4) - img = Image(data = arr) - assert img.affine is None - assert img.world is None - img.affine = np.eye(4) - img.world = mni - data = img.get_data() - assert data.shape == (2,3,4) - assert np.all(data == arr) - # The format object is Nifti1 by default. It's also empty - assert img.format.fields == Nifti1().fields - img.save('some_file.nii') - - -Note the decoupling between the information carried by the format, and -the information in the ``img`` instance. The format instance, carries -the format, as instantiated by loading from disk, or object creation, -and is only updated on ``img.save(fname)``. This is to allow formats -that cannot encode either affine or world information. If you want to -manipulate fields or other information in the specific format, you -probably want to instantiate the format object directly (see below). - - -Format objects -============== - -The API of the format object encapsulates two things: - -* the shared interface to underlying image formats that is used by ``Image`` -* format-specific attributes and calls - -The API required by ``Image`` is: - -* fmt.get_affine() -* fmt.set_affine(aff) -* fmt.get_world() -* fmt.set_world(world) -* fmt.get_io_dtype() -* fmt.set_io_dtype(dtype) -* fmt.read_data() -* fmt.write_data(arr) -* fmt.to_filename(fname) -* fmt.from_filename() - -The last to save the format to the file(s) given by ``fname``. We may -also want the ability to write to sets of file objects, for testing, and -for abstraction of the base writing layer. - -* fmt.to_filemap(fmap) -* fmt.from_filemap(fmap) - -where ``fmap`` is a class, currently called ``FileTuple`` that contains -mappings of file meanings (like ``image`` or ``header``) to file -objects. - -With this model, we may often find ourselves using the Format object for -format-specific tasks:: - - from nibabel.formats import Nifti1 - fmt = Nifti1.from_filename('some_file.nii') - fmt.set_qform(np.eye(4)) - fmt.set_sform(np.eye(4) * 2) - fmt.fields['descrip'] = 'some information' - fmt.to_filename('another_file.nii') diff --git a/doc/source/old/orientation.txt b/doc/source/old/orientation.txt deleted file mode 100644 index b44a11e309..0000000000 --- a/doc/source/old/orientation.txt +++ /dev/null @@ -1,137 +0,0 @@ -.. _image-orientation: - -=================== - Image orientation -=================== - -Every image in ``nibabel`` has an orientation. The orientation is the -relationship between the voxels in the image array, and millimeters in -some space. - -Affines as orientation ----------------------- - -Orientations are expressed by 4 by 4 affine arrays. 4x4 affine arrays -give, in homogeneous coordinates, the relationship between the -coordinates in the voxel array, and millimeters. Let is say that I have -a simple affine like this: - ->>> import numpy as np ->>> aff = np.diag((2, 3, 4, 1)) ->>> aff[:3,3] = [10, 11, 12] - -And I have a voxel coordinate: - ->>> coord = np.array([3, 2, 1]) - -then the millimeter coordinate for that voxel is given by: - ->>> # add extra 1 for homogeneous coordinates ->>> homogenous_coord = np.concatenate((coord, [1])) ->>> mm_coord = np.dot(aff, homogenous_coord)[:3] ->>> mm_coord -array([16, 17, 16]) - -Affines and image formats -------------------------- - -Some image formats (such as nifti) allow storage of affine or -affine-like image orientation, and some do not (such as Analyze). Almost -all image formats allow you to save an image without any affine -information. Most image orientation problems arise for images that do -not have full affine information, and we have to guess. - -Making an affine when there is no stored affine -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If there is no affine information, we have to make some best-guess -affine for the image. In this case, the image is assumed to be saved -(fastest to slowest changing) in X, Y, Z dimension order, and we -construct a 4x4 affine ``aff`` where ``aff[:3,:3]`` is a diagonal matrix -with the X, Y, Z zooms (voxel sizes) as entries - ``diag(aff)``. The -translation part of the affine ``aff[:3, 3]`` is such that the central -voxel in the image is at 0, 0, 0 mm (this is not completely true for SPM -images, with may have encoded a particular voxel as the origiin using -the ``origin`` field of the SPM version of the Analyze header). - -The left-right orientation of the image in this case boils down to -whether the first voxel in the image (and in any x line) is the -left-most voxel or the right-most voxel. If it is the left-most, the -image is said to be in 'neurological' orientation, and if it is the -right-most, it's in 'radiological' orientation. These terms only -make sense in this case, where there is no affine, and we are assuming -X, Y, Z data storage on disk. - -If we deem the image to be 'neurological' then the guessed affine -above will be correct, as a transform from voxel coordinates to mm -coordinates. If it is 'radiological', then we need to multiply the -'X' zoom (``aff[0,0]``) by -1, and adjust the X translation -(``aff[0,3]``) accordingly. - -In ``nibabel`` we assume that any image without an affine has been -stored in radiological order on disk - and thus the guessed affine needs -a left-right flip. This is true for all Analyze-type image formats -(Analyze, SPM analyze, nifti). - -If you want to change this (please don't unless you are absolutely -sure what you are doing), the default is encoded in the -``default_x_flip`` class variable where True corresponds to -'radiological' and False corresponds to 'neurological'. - -If you want to load images that are in neurological disk format, I -strongly suggest that, instead of changing this default, you adjust -the affine after loading, as in:: - - img = nibabel.load('some_image.img') - aff = img.get_affine() - x_flipper = np.diag([-1,1,1,1]) - lr_img = nibabel.Nifti1Image(img.get_fdata(), np.dot(x_flipper, aff), img.header) - -Affines for Analyze, SPM analyze, and NIFTI -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Analyze images can't have an affine, so the above always applies to -Analyze images. - -SPM99 images are unpleasantly confusing, because they may have an -affine stored in a 'some_image.mat' file, in a matrix called 'M', but the -affine for the image, is given by (from the code above) -``np.dot(x_flipper, M)`` - that is - the affine gives the -transformations to be applied before any left-right flipping, where -left-right flipping is determined by the ``default_x_flip`` above. -Horrible. - -SPM2 images are a bit more straightforward, in that there may be an -affine, again stored in the 'some_image.mat' file, but, if the image has -been written in SPM2, or by us, in SPM2 format, then there should be a -'mat' matrix in that file, that has the full affine, which is -unaffected by the ``default_x_flip``. However, if we are loading -what appears to be an SPM99 image, that only has a mat file with an -'M' matrix, we apply the default flip as above. - -Whenever we save an SPM99 image, we save an SPM2-like ``.mat`` file, with -both the flip-specifying 'mat' matrix, and the pre-flip 'M' matrix, -because this is still backwards compatible, and might be less liable -to chaos if someone changes the default flip setting. - -Then, we have nifti, which can store two affines, the ``qform`` and -the ``sform``. If the ``sform`` is present, we load that, otherwise, -if the ``qform`` is present, we use that. Either of these affines -fully specifies orientation, that is, they ignore any settings of -``default_x_flip``. If the nifti has neither a ``qform`` or an -``sform``, we guess at the affine with the algorithm above, and the -``default_x_flip`` comes into play again. - -Note that, for nifti images without affines, we don't followw the nifti -standard. In the nifti standard, if an image does not have an affine, -then the affine is deemed to be ``diag([xs, ys, zs, 1])`` where ``xs, -ys, zs`` are the X, Y and Z zooms (voxel sizes) respectively. This -array has no concept of left-right flipping corresponding to -radiological orientation, and assumes the image origin (voxel -corresponding to 0, 0, 0 in millimeters) is the first voxel in the -image. ``nibabel`` differs from the nifti standard, for images without -affines, in using the center of the image as the origin, and flipping -left-right by default. We chose this break from the standard because -that is what SPM does with non-affine niftis, and because it seemed more -sensible, and because it's more consistent with what we do with SPM -non-nifti images (not surprisingly). diff --git a/doc/source/scripts/.gitignore b/doc/source/scripts/.gitignore deleted file mode 100644 index 090a7c32d6..0000000000 --- a/doc/source/scripts/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.png -*.nii diff --git a/doc/source/scripts/README.txt b/doc/source/scripts/README.txt deleted file mode 100644 index ef3abe881b..0000000000 --- a/doc/source/scripts/README.txt +++ /dev/null @@ -1,7 +0,0 @@ -########################## -Scripts for making figures -########################## - -Directory contains scripts for making tutorial figures. - -.. vim: ft=rst diff --git a/doc/source/scripts/make_coord_examples.py b/doc/source/scripts/make_coord_examples.py deleted file mode 100644 index aa83fbcd84..0000000000 --- a/doc/source/scripts/make_coord_examples.py +++ /dev/null @@ -1,199 +0,0 @@ -#!/usr/bin/env python -"""Make graphics and example image for coordinate tutorial - -Expects MNI nonlinear template t1 and t2 images in directory of script - -specifically these files: - -* mni_icbm152_t1_tal_nlin_asym_09a.nii -* mni_icbm152_t2_tal_nlin_asym_09a.nii - -Requires nipy and matplotlib. - -Executing this script generates the following files in the current directory: - -* localizer.png (pretend localizer sagittal image) -* someones_epi.nii.gz (pretend single EPI volume) -* someones_anatomy.nii.gz (pretend single subject structural) -""" - -import math - -import matplotlib.pyplot as plt -import nipy -import nipy.algorithms.resample as rsm -import nipy.core.api as nca -import numpy as np -import numpy.linalg as npl - -import nibabel.eulerangles as euler - -T1_IMG = 'mni_icbm152_t1_tal_nlin_asym_09a.nii' -T2_IMG = 'mni_icbm152_t2_tal_nlin_asym_09a.nii' - -imgs = [] -for img_fname in (T1_IMG, T2_IMG): - img = nipy.load_image(img_fname) - # Set affine as for FOV, not AC - RZS = img.affine[:3, :3] - vox_fov_center = -(np.array(img.shape) - 1) / 2.0 - T = RZS.dot(vox_fov_center) - img.affine[:3, 3] = T - # Take stuff off the top of the full image, to emphasize FOV - img_z_shave = 10 - # Take stuff off left and right to save disk space - img_x_shave = 20 - img = img[img_x_shave:-img_x_shave, :, :-img_z_shave] - imgs.append(img) - -t1_img, t2_img = imgs - -# Make fake localizer -data = t1_img.get_fdata() -n_x, n_y, n_z = img.shape -mid_x = round(n_x / 2) - -sagittal = data[mid_x, :, :].T - -# EPI bounding box -# 3 points on a not-completely-rectangular box. The box is to give a by-eye -# estimate, then we work out the box side lengths and make a rectangular box -# from those, using the origin point -epi_bl = np.array((20, 15)) * 2 -epi_br = np.array((92, 70)) * 2 -epi_tl = np.array((7, 63)) * 2 -# Find lengths of sides -epi_y_len = np.sqrt((np.subtract(epi_bl, epi_tl) ** 2).sum()) -epi_x_len = np.sqrt((np.subtract(epi_bl, epi_br) ** 2).sum()) -x, y = 0, 1 -# Make a rectangular box with these sides - - -def make_ortho_box(bl, x_len, y_len): - """Make a box with sides parallel to the axes""" - return np.array( - (bl, [bl[x] + x_len, bl[y]], [bl[x], bl[y] + y_len], [bl[x] + x_len, bl[y] + y_len]) - ) - - -orth_epi_box = make_ortho_box(epi_bl, epi_x_len, epi_y_len) - -# Structural bounding box -anat_bl = (25, 3) -anat_x_len = 185 -anat_y_len = 155 -anat_box = make_ortho_box(anat_bl, anat_x_len, anat_y_len) - - -def plot_line(pt1, pt2, fmt='r-', label=None): - plt.plot([pt1[0], pt2[0]], [pt1[1], pt2[1]], fmt, label=label) - - -def plot_box(box_def, fmt='r-', label=None): - bl, br, tl, tr = box_def - plot_line(bl, br, fmt, label=label) - plot_line(bl, tl, fmt) - plot_line(br, tr, fmt) - plot_line(tl, tr, fmt) - - -def rotate_box(box_def, angle, origin): - origin = np.atleast_2d(origin) - box_def_zeroed = box_def - origin - cost = math.cos(angle) - sint = math.sin(angle) - rot_array = np.array([[cost, -sint], [sint, cost]]) - box_def_zeroed = np.dot(rot_array, box_def_zeroed.T).T - return box_def_zeroed + origin - - -def labeled_point(pt, marker, text, markersize=10, color='k'): - plt.plot(pt[0], pt[1], marker, markersize=markersize) - plt.text(pt[0] + markersize / 2, pt[1] - markersize / 2, text, color=color) - - -def plot_localizer(): - plt.imshow(sagittal, cmap='gray', origin='lower', extent=sag_extents) - plt.xlabel('mm from isocenter') - plt.ylabel('mm from isocenter') - - -def save_plot(): - # Plot using global variables - plot_localizer() - - def vx2mm(pts): - return pts - iso_center - - plot_box(vx2mm(rot_box), label='EPI bounding box') - plot_box(vx2mm(anat_box), 'b-', label='Structural bounding box') - labeled_point(vx2mm(epi_center), 'ro', 'EPI FOV center') - labeled_point(vx2mm(anat_center), 'bo', 'Structural FOV center') - labeled_point(vx2mm(iso_center), 'g^', 'Magnet isocenter') - plt.axis('tight') - plt.legend(loc='lower right') - plt.title('Scanner localizer image') - plt.savefig('localizer.png') - - -angle = 0.3 -rot_box = rotate_box(orth_epi_box, angle, orth_epi_box[0]) -epi_center = np.mean(rot_box, axis=0) -anat_center = np.mean(anat_box, axis=0) -# y axis on the plot is first axis of image -sag_y, sag_x = sagittal.shape -iso_center = (np.array([sag_x, sag_y]) - 1) / 2.0 -sag_extents = [-iso_center[0], iso_center[0], -iso_center[1], iso_center[1]] - -# Back to image coordinates -br_img = np.array([0, rot_box[0, 0], rot_box[0, 1]]) -epi_trans = np.eye(4) -epi_trans[:3, 3] = -br_img -rot = np.eye(4) -rot[:3, :3] = euler.euler2mat(0, 0, -angle) -# downsample to make smaller output image -downsamp = 1 / 3 -epi_scale = np.diag([downsamp, downsamp, downsamp, 1]) -# template voxels to epi box image voxels -vox2epi_vox = epi_scale.dot(rot.dot(epi_trans)) -# epi image voxels to mm -epi_vox2mm = t2_img.affine.dot(npl.inv(vox2epi_vox)) -# downsampled image shape -epi_vox_shape = np.array([data.shape[0], epi_x_len, epi_y_len]) * downsamp -# Make sure dimensions are odd by rounding up or down -# This makes the voxel center an integer index, which is convenient -epi_vox_shape = [np.floor(d) if np.floor(d) % 2 else np.ceil(d) for d in epi_vox_shape] -# resample, preserving affine -epi_cmap = nca.vox2mni(epi_vox2mm) -epi = rsm.resample(t2_img, epi_cmap, np.eye(4), epi_vox_shape) -epi_data = epi.get_fdata() -# Do the same kind of thing for the anatomical scan -anat_vox_sizes = [2.75, 2.75, 2.75] -anat_scale = npl.inv(np.diag(anat_vox_sizes + [1])) -anat_trans = np.eye(4) -anat_trans[:3, 3] = -np.array([0, anat_box[0, 0], anat_box[0, 1]]) -vox2anat_vox = anat_scale.dot(anat_trans) -anat_vox2mm = t1_img.affine.dot(npl.inv(vox2anat_vox)) -anat_vox_shape = np.round(np.divide([data.shape[0], anat_x_len, anat_y_len], anat_vox_sizes)) -anat_cmap = nca.vox2mni(anat_vox2mm) -anat = rsm.resample(t1_img, anat_cmap, np.eye(4), anat_vox_shape) -anat_data = anat.get_fdata() - -save_plot() -nipy.save_image(epi, 'someones_epi.nii.gz', dtype_from='uint8') -nipy.save_image(anat, 'someones_anatomy.nii.gz', dtype_from='uint8') - -# Do progressive transforms -epi2_vox = make_ortho_box((0, 0), epi_vox_shape[1], epi_vox_shape[2]) -epi_vox_sizes = np.sqrt(np.sum(epi_vox2mm[:3, :3] ** 2, axis=0)) -epi2_scaled = np.diag(epi_vox_sizes[1:]).dot(epi2_vox.T).T -epi2_rotted = rotate_box(epi2_scaled, angle, (0, 0)) -epi2_pulled = epi2_rotted + epi_vox2mm[1:3, 3] -plt.figure() -plot_localizer() -plot_box(epi2_vox, 'k', label='voxels') -plot_box(epi2_scaled, 'g', label='scaled') -plot_box(epi2_rotted, 'y', label='scaled, rotated') -plot_box(epi2_pulled, 'r', label='scaled, rotated, translated') -plt.legend(loc='upper left') -plt.title('Anatomy of an affine transform') -plt.savefig('illustrating_affine.png') diff --git a/doc/tools/LICENSE.txt b/doc/tools/LICENSE.txt deleted file mode 100644 index 50431cd88e..0000000000 --- a/doc/tools/LICENSE.txt +++ /dev/null @@ -1,6 +0,0 @@ -These files were obtained from - -https://www.mail-archive.com/sphinx-dev@googlegroups.com/msg02472.html - -and were released under a BSD/MIT license by Fernando Perez, Matthew Brett and -the PyMVPA folks. Further cleanups by the scikit-image crew. diff --git a/doc/tools/apigen.py b/doc/tools/apigen.py deleted file mode 100644 index 336c81d8d8..0000000000 --- a/doc/tools/apigen.py +++ /dev/null @@ -1,504 +0,0 @@ -""" -Attempt to generate templates for module reference with Sphinx - -To include extension modules, first identify them as valid in the -``_uri2path`` method, then handle them in the ``_parse_module_with_import`` -script. - -Notes ------ -This parsing is based on import and introspection of modules. -Previously functions and classes were found by parsing the text of .py files. - -Extension modules should be discovered and included as well. - -This is a modified version of a script originally shipped with the PyMVPA -project, then adapted for use first in NIPY and then in skimage. PyMVPA -is an MIT-licensed project. -""" - -# Stdlib imports -import os -import re -from inspect import getmodule -from types import BuiltinFunctionType, FunctionType - -# suppress print statements (warnings for empty files) -DEBUG = True - - -class ApiDocWriter: - """Class for automatic detection and parsing of API docs - to Sphinx-parsable reST format""" - - # only separating first two levels - rst_section_levels = ['*', '=', '-', '~', '^'] - - def __init__( - self, - package_name, - rst_extension='.txt', - package_skip_patterns=None, - module_skip_patterns=None, - other_defines=True, - ): - r"""Initialize package for parsing - - Parameters - ---------- - package_name : string - Name of the top-level package. *package_name* must be the - name of an importable package - rst_extension : string, optional - Extension for reST files, default '.rst' - package_skip_patterns : None or sequence of {strings, regexps} - Sequence of strings giving URIs of packages to be excluded - Operates on the package path, starting at (including) the - first dot in the package path, after *package_name* - so, - if *package_name* is ``sphinx``, then ``sphinx.util`` will - result in ``.util`` being passed for searching by these - regexps. If is None, gives default. Default is: - ['\.tests$'] - module_skip_patterns : None or sequence - Sequence of strings giving URIs of modules to be excluded - Operates on the module name including preceding URI path, - back to the first dot after *package_name*. For example - ``sphinx.util.console`` results in the string to search of - ``.util.console`` - If is None, gives default. Default is: - ['\.setup$', '\._'] - other_defines : {True, False}, optional - Whether to include classes and functions that are imported in a - particular module but not defined there. - """ - if package_skip_patterns is None: - package_skip_patterns = ['\\.tests$'] - if module_skip_patterns is None: - module_skip_patterns = ['\\.setup$', '\\._'] - self.package_name = package_name - self.rst_extension = rst_extension - self.package_skip_patterns = package_skip_patterns - self.module_skip_patterns = module_skip_patterns - self.other_defines = other_defines - - def get_package_name(self): - return self._package_name - - def set_package_name(self, package_name): - """Set package_name - - >>> docwriter = ApiDocWriter('sphinx') - >>> import sphinx - >>> docwriter.root_path == sphinx.__path__[0] - True - >>> docwriter.package_name = 'docutils' - >>> import docutils - >>> docwriter.root_path == docutils.__path__[0] - True - """ - # It's also possible to imagine caching the module parsing here - self._package_name = package_name - root_module = self._import(package_name) - self.root_path = root_module.__path__[-1] - self.written_modules = None - - package_name = property(get_package_name, set_package_name, None, 'get/set package_name') - - def _import(self, name): - """Import namespace package""" - mod = __import__(name) - components = name.split('.') - for comp in components[1:]: - mod = getattr(mod, comp) - return mod - - def _get_object_name(self, line): - """Get second token in line - >>> docwriter = ApiDocWriter('sphinx') - >>> docwriter._get_object_name(" def func(): ") - 'func' - >>> docwriter._get_object_name(" class Klass: ") - 'Klass' - >>> docwriter._get_object_name(" class Klass: ") - 'Klass' - """ - name = line.split()[1].split('(')[0].strip() - # in case we have classes which are not derived from object - # ie. old style classes - return name.rstrip(':') - - def _uri2path(self, uri): - """Convert uri to absolute filepath - - Parameters - ---------- - uri : string - URI of python module to return path for - - Returns - ------- - path : None or string - Returns None if there is no valid path for this URI - Otherwise returns absolute file system path for URI - - Examples - -------- - >>> docwriter = ApiDocWriter('sphinx') - >>> import sphinx - >>> modpath = sphinx.__path__[0] - >>> res = docwriter._uri2path('sphinx.builder') - >>> res == os.path.join(modpath, 'builder.py') - True - >>> res = docwriter._uri2path('sphinx') - >>> res == os.path.join(modpath, '__init__.py') - True - >>> docwriter._uri2path('sphinx.does_not_exist') - - """ - if uri == self.package_name: - return os.path.join(self.root_path, '__init__.py') - path = uri.replace(self.package_name + '.', '') - path = path.replace('.', os.path.sep) - path = os.path.join(self.root_path, path) - # XXX maybe check for extensions as well? - if os.path.exists(path + '.py'): # file - path += '.py' - elif os.path.exists(os.path.join(path, '__init__.py')): - path = os.path.join(path, '__init__.py') - else: - return None - return path - - def _path2uri(self, dirpath): - """Convert directory path to uri""" - package_dir = self.package_name.replace('.', os.path.sep) - relpath = dirpath.replace(self.root_path, package_dir) - if relpath.startswith(os.path.sep): - relpath = relpath[1:] - return relpath.replace(os.path.sep, '.') - - def _parse_module(self, uri): - """Parse module defined in *uri*""" - filename = self._uri2path(uri) - if filename is None: - print(filename, 'erk') - # nothing that we could handle here. - return ([], []) - - f = open(filename, 'rt') - functions, classes = self._parse_lines(f) - f.close() - return functions, classes - - def _parse_module_with_import(self, uri): - """Look for functions and classes in an importable module. - - Parameters - ---------- - uri : str - The name of the module to be parsed. This module needs to be - importable. - - Returns - ------- - functions : list of str - A list of (public) function names in the module. - classes : list of str - A list of (public) class names in the module. - """ - mod = __import__(uri, fromlist=[uri.split('.')[-1]]) - # find all public objects in the module. - obj_strs = [obj for obj in dir(mod) if not obj.startswith('_')] - functions = [] - classes = [] - for obj_str in obj_strs: - # find the actual object from its string representation - if obj_str not in mod.__dict__: - continue - obj = mod.__dict__[obj_str] - # Check if function / class defined in module - if not self.other_defines and not getmodule(obj) == mod: - continue - # figure out if obj is a function or class - if isinstance(obj, (FunctionType, BuiltinFunctionType)): - functions.append(obj_str) - else: - try: - issubclass(obj, object) - classes.append(obj_str) - except TypeError: - # not a function or class - pass - return functions, classes - - def _parse_lines(self, linesource): - """Parse lines of text for functions and classes""" - functions = [] - classes = [] - for line in linesource: - if line.startswith('def ') and line.count('('): - # exclude private stuff - name = self._get_object_name(line) - if not name.startswith('_'): - functions.append(name) - elif line.startswith('class '): - # exclude private stuff - name = self._get_object_name(line) - if not name.startswith('_'): - classes.append(name) - else: - pass - functions.sort() - classes.sort() - return functions, classes - - def generate_api_doc(self, uri): - """Make autodoc documentation template string for a module - - Parameters - ---------- - uri : string - python location of module - e.g 'sphinx.builder' - - Returns - ------- - head : string - Module name, table of contents. - body : string - Function and class docstrings. - """ - # get the names of all classes and functions - functions, classes = self._parse_module_with_import(uri) - if not len(functions) and not len(classes) and DEBUG: - print('WARNING: Empty -', uri) # dbg - - # Make a shorter version of the uri that omits the package name for - # titles - uri_short = re.sub(r'^%s\.' % self.package_name, '', uri) - - head = '.. AUTO-GENERATED FILE -- DO NOT EDIT!\n\n' - body = '' - - # Set the chapter title to read 'module' for all modules except for the - # main packages - if '.' in uri_short: - title = 'Module: :mod:`' + uri_short + '`' - head += title + '\n' + self.rst_section_levels[2] * len(title) - else: - title = ':mod:`' + uri_short + '`' - head += title + '\n' + self.rst_section_levels[1] * len(title) - - head += '\n.. automodule:: ' + uri + '\n' - head += '\n.. currentmodule:: ' + uri + '\n' - body += '\n.. currentmodule:: ' + uri + '\n\n' - for c in classes: - body += '\n:class:`' + c + '`\n' + self.rst_section_levels[3] * (len(c) + 9) + '\n\n' - body += '\n.. autoclass:: ' + c + '\n' - # must NOT exclude from index to keep cross-refs working - body += ( - ' :members:\n' - ' :undoc-members:\n' - ' :show-inheritance:\n' - '\n' - ' .. automethod:: __init__\n\n' - ) - head += '.. autosummary::\n\n' - for f in classes + functions: - head += ' ' + f + '\n' - head += '\n' - - for f in functions: - # must NOT exclude from index to keep cross-refs working - body += f + '\n' - body += self.rst_section_levels[3] * len(f) + '\n' - body += '\n.. autofunction:: ' + f + '\n\n' - - return head, body - - def _survives_exclude(self, matchstr, match_type): - """Returns True if *matchstr* does not match patterns - - ``self.package_name`` removed from front of string if present - - Examples - -------- - >>> dw = ApiDocWriter('sphinx') - >>> dw._survives_exclude('sphinx.okpkg', 'package') - True - >>> dw.package_skip_patterns.append('^\\.badpkg$') - >>> dw._survives_exclude('sphinx.badpkg', 'package') - False - >>> dw._survives_exclude('sphinx.badpkg', 'module') - True - >>> dw._survives_exclude('sphinx.badmod', 'module') - True - >>> dw.module_skip_patterns.append('^\\.badmod$') - >>> dw._survives_exclude('sphinx.badmod', 'module') - False - """ - if match_type == 'module': - patterns = self.module_skip_patterns - elif match_type == 'package': - patterns = self.package_skip_patterns - else: - raise ValueError(f'Cannot interpret match type "{match_type}"') - # Match to URI without package name - L = len(self.package_name) - if matchstr[:L] == self.package_name: - matchstr = matchstr[L:] - for pat in patterns: - try: - pat.search - except AttributeError: - pat = re.compile(pat) - if pat.search(matchstr): - return False - - return True - - def discover_modules(self): - r"""Return module sequence discovered from ``self.package_name`` - - - Parameters - ---------- - None - - Returns - ------- - mods : sequence - Sequence of module names within ``self.package_name`` - - Examples - -------- - >>> dw = ApiDocWriter('sphinx') - >>> mods = dw.discover_modules() - >>> 'sphinx.util' in mods - True - >>> dw.package_skip_patterns.append('\.util$') - >>> 'sphinx.util' in dw.discover_modules() - False - >>> - """ - modules = [self.package_name] - # raw directory parsing - for dirpath, dirnames, filenames in os.walk(self.root_path): - # Check directory names for packages - root_uri = self._path2uri(os.path.join(self.root_path, dirpath)) - - # Normally, we'd only iterate over dirnames, but since - # dipy does not import a whole bunch of modules we'll - # include those here as well (the *.py filenames). - filenames = [ - f[:-3] for f in filenames if f.endswith('.py') and not f.startswith('__init__') - ] - for filename in filenames: - package_uri = '/'.join((dirpath, filename)) - - for subpkg_name in dirnames + filenames: - package_uri = '.'.join((root_uri, subpkg_name)) - package_path = self._uri2path(package_uri) - if package_path and self._survives_exclude(package_uri, 'package'): - modules.append(package_uri) - - return sorted(modules) - - def write_modules_api(self, modules, outdir): - # upper-level modules - ulms = ['.'.join(m.split('.')[:2]) for m in modules] - - from collections import OrderedDict - - module_by_ulm = OrderedDict() - - for v, k in zip(modules, ulms): - if k in module_by_ulm: - module_by_ulm[k].append(v) - else: - module_by_ulm[k] = [v] - - written_modules = [] - - for ulm, mods in module_by_ulm.items(): - print(f'Generating docs for {ulm}:') - document_head = [] - document_body = [] - - for m in mods: - print(' -> ' + m) - head, body = self.generate_api_doc(m) - - document_head.append(head) - document_body.append(body) - - out_module = ulm + self.rst_extension - outfile = os.path.join(outdir, out_module) - fileobj = open(outfile, 'wt') - - fileobj.writelines(document_head + document_body) - fileobj.close() - written_modules.append(out_module) - - self.written_modules = written_modules - - def write_api_docs(self, outdir): - """Generate API reST files. - - Parameters - ---------- - outdir : string - Directory name in which to store files - We create automatic filenames for each module - - Returns - ------- - None - - Notes - ----- - Sets self.written_modules to list of written modules - """ - if not os.path.exists(outdir): - os.mkdir(outdir) - # compose list of modules - modules = self.discover_modules() - self.write_modules_api(modules, outdir) - - def write_index(self, outdir, froot='gen', relative_to=None): - """Make a reST API index file from written files - - Parameters - ---------- - path : string - Filename to write index to - outdir : string - Directory to which to write generated index file - froot : string, optional - root (filename without extension) of filename to write to - Defaults to 'gen'. We add ``self.rst_extension``. - relative_to : string - path to which written filenames are relative. This - component of the written file path will be removed from - outdir, in the generated index. Default is None, meaning, - leave path as it is. - """ - if self.written_modules is None: - raise ValueError('No modules written') - # Get full filename path - path = os.path.join(outdir, froot + self.rst_extension) - # Path written into index is relative to rootpath - if relative_to is not None: - relpath = (outdir + os.path.sep).replace(relative_to + os.path.sep, '') - else: - relpath = outdir - idx = open(path, 'wt') - w = idx.write - w('.. AUTO-GENERATED FILE -- DO NOT EDIT!\n\n') - - title = 'API Reference' - w(title + '\n') - w('=' * len(title) + '\n\n') - w('.. toctree::\n\n') - for f in self.written_modules: - w(f' {os.path.join(relpath, f)}\n') - idx.close() diff --git a/doc/tools/build_modref_templates.py b/doc/tools/build_modref_templates.py deleted file mode 100755 index 76cf9cdf39..0000000000 --- a/doc/tools/build_modref_templates.py +++ /dev/null @@ -1,97 +0,0 @@ -#!/usr/bin/env python -"""Script to auto-generate our API docs. -""" - -import os -import re - -# stdlib imports -import sys - -# version comparison -from packaging.version import Version as V -from os.path import join as pjoin - -# local imports -from apigen import ApiDocWriter - -# ***************************************************************************** - - -def abort(error): - print(f'*WARNING* API documentation not generated: {error}') - exit(1) - - -if __name__ == '__main__': - package = sys.argv[1] - outdir = sys.argv[2] - try: - other_defines = sys.argv[3] - except IndexError: - other_defines = True - else: - other_defines = other_defines in ('True', 'true', '1') - - # Check that the package is available. If not, the API documentation is not - # (re)generated and existing API documentation sources will be used. - - try: - __import__(package) - except ImportError: - abort('Can not import ' + package) - - module = sys.modules[package] - - # Check that the source version is equal to the installed - # version. If the versions mismatch the API documentation sources - # are not (re)generated. This avoids automatic generation of documentation - # for older or newer versions if such versions are installed on the system. - - installed_version = V(module.__version__) - - version_file = pjoin('..', package, '_version.py') - source_version = None - if os.path.exists(version_file): - # Versioneer - from runpy import run_path - - try: - source_version = run_path(version_file)['version'] - except (FileNotFoundError, KeyError): - pass - if source_version == '0+unknown': - source_version = None - if source_version is None: - # Legacy fall-back - info_file = pjoin('..', package, 'info.py') - info_lines = open(info_file).readlines() - source_version = '.'.join( - [ - v.split('=')[1].strip(" '\n.") - for v in info_lines - if re.match('^_version_(major|minor|micro|extra)', v) - ] - ) - - source_version = V(source_version) - print('***', source_version) - - if source_version != installed_version: - abort('Installed version does not match source version') - - docwriter = ApiDocWriter(package, rst_extension='.rst', other_defines=other_defines) - docwriter.package_skip_patterns += [ - r'\.fixes$', - r'\.fixes.*$', - r'\.externals$', - r'\.externals.*$', - r'.*test.*$', - r'\.info.*$', - r'\.pkg_info.*$', - r'\.py3k.*$', - r'\._version.*$', - ] - docwriter.write_api_docs(outdir) - docwriter.write_index(outdir, 'index', relative_to=outdir) - print('%d files written' % len(docwriter.written_modules)) diff --git a/genindex.html b/genindex.html new file mode 100644 index 0000000000..832856c6e5 --- /dev/null +++ b/genindex.html @@ -0,0 +1,4173 @@ + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + + +

    Index

    + +
    + _ + | A + | B + | C + | D + | E + | F + | G + | H + | I + | J + | K + | L + | M + | N + | O + | P + | Q + | R + | S + | T + | U + | V + | W + | X + | Z + +
    +

    _

    + + +
    + +

    A

    + + + +
    + +

    B

    + + + +
    + +

    C

    + + + +
    + +

    D

    + + + +
    + +

    E

    + + + +
    + +

    F

    + + + +
    + +

    G

    + + + +
    + +

    H

    + + + +
    + +

    I

    + + + +
    + +

    J

    + + +
    + +

    K

    + + + +
    + +

    L

    + + + +
    + +

    M

    + + + +
    + +

    N

    + + + +
    + +

    O

    + + + +
    + +

    P

    + + + +
    + +

    Q

    + + + +
    + +

    R

    + + + +
    + +

    S

    + + + +
    + +

    T

    + + + +
    + +

    U

    + + + +
    + +

    V

    + + + +
    + +

    W

    + + + +
    + +

    X

    + + + +
    + +

    Z

    + + +
    + + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/gettingstarted.html b/gettingstarted.html new file mode 100644 index 0000000000..da13af888d --- /dev/null +++ b/gettingstarted.html @@ -0,0 +1,209 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    Getting Started

    +

    NiBabel supports an ever growing collection of neuroimaging file formats. Every +file format format has its own features and peculiarities that need to be taken +care of to get the most out of it. To this end, NiBabel offers both high-level +format-independent access to neuroimages, as well as an API with various levels +of format-specific access to all available information in a particular file +format. The following examples show some of NiBabel’s capabilities and give +you an idea of the API.

    +

    For more detail on the API, see Nibabel images.

    +

    When loading an image, NiBabel tries to figure out the image format from the +filename. An image in a known format can easily be loaded by simply passing its +filename to the load function.

    +

    To start the code examples, we load some useful libraries:

    +
    >>> import os
    +>>> import numpy as np
    +
    +
    +

    Then we fine the nibabel directory containing the example data:

    +
    >>> from nibabel.testing import data_path
    +
    +
    +

    There is a NIfTI file in this directory called example4d.nii.gz:

    +
    >>> example_filename = os.path.join(data_path, 'example4d.nii.gz')
    +
    +
    +

    Now we can import nibabel and load the image:

    +
    >>> import nibabel as nib
    +>>> img = nib.load(example_filename)
    +
    +
    +

    A NiBabel image knows about its shape:

    +
    >>> img.shape
    +(128, 96, 24, 2)
    +
    +
    +

    It also records the data type of the data as stored on disk. In this case the +data on disk are 16 bit signed integers:

    +
    >>> img.get_data_dtype() == np.dtype(np.int16)
    +True
    +
    +
    +

    The image has an affine transformation that determines the world-coordinates of +the image elements (see Coordinate systems and affines):

    +
    >>> img.affine.shape
    +(4, 4)
    +
    +
    +

    This information is available without the need to load anything of the main +image data into the memory. Of course there is also access to the image data as +a NumPy array

    +
    >>> data = img.get_fdata()
    +>>> data.shape
    +(128, 96, 24, 2)
    +>>> type(data)
    +<... 'numpy.ndarray'>
    +
    +
    +

    The complete information embedded in an image header is available via a +format-specific header object.

    +
    >>> hdr = img.header
    +
    +
    +

    In case of this NIfTI file it allows accessing all NIfTI-specific information, +e.g.

    +
    >>> hdr.get_xyzt_units()
    +('mm', 'sec')
    +
    +
    +

    Corresponding “setter” methods allow modifying a header, while ensuring its +compliance with the file format specifications.

    +

    In some situations we need even more flexibility and, for those with great +courage, NiBabel also offers access to the raw header information

    +
    >>> raw = hdr.structarr
    +>>> raw['xyzt_units']
    +array(10, dtype=uint8)
    +
    +
    +

    This lowest level of the API is designed for people who know the file format +well enough to work with its internal data, and comes without any safety-net.

    +

    Creating a new image in some file format is also easy. At a minimum it only +needs some image data and an image coordinate transformation (affine):

    +
    >>> import numpy as np
    +>>> data = np.ones((32, 32, 15, 100), dtype=np.int16)
    +>>> img = nib.Nifti1Image(data, np.eye(4))
    +>>> img.get_data_dtype() == np.dtype(np.int16)
    +True
    +>>> img.header.get_xyzt_units()
    +('unknown', 'unknown')
    +
    +
    +

    In this case, we used the identity matrix as the affine transformation. The +image header is initialized from the provided data array (i.e. shape, dtype) +and all other values are set to reasonable defaults.

    +

    Saving this new image to a file is trivial:

    +
    >>> nib.save(img, os.path.join('build', 'test4d.nii.gz'))  
    +
    +
    +

    This short introduction only gave a quick overview of NiBabel’s capabilities. +Please have a look at the API Documentation for more details about supported file +formats and their features.

    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/gitwash/configure_git.html b/gitwash/configure_git.html new file mode 100644 index 0000000000..18c64d2f18 --- /dev/null +++ b/gitwash/configure_git.html @@ -0,0 +1,272 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    Configure git

    +
    +

    Overview

    +

    Your personal git configurations are saved in the .gitconfig file in +your home directory.

    +

    Here is an example .gitconfig file:

    +
    [user]
    +        name = Your Name
    +        email = you@yourdomain.example.com
    +
    +[alias]
    +        ci = commit -a
    +        co = checkout
    +        st = status
    +        stat = status
    +        br = branch
    +        wdiff = diff --color-words
    +
    +[core]
    +        editor = vim
    +
    +[merge]
    +        summary = true
    +
    +
    +

    You can edit this file directly or you can use the git config --global +command:

    +
    git config --global user.name "Your Name"
    +git config --global user.email you@yourdomain.example.com
    +git config --global alias.ci "commit -a"
    +git config --global alias.co checkout
    +git config --global alias.st "status -a"
    +git config --global alias.stat "status -a"
    +git config --global alias.br branch
    +git config --global alias.wdiff "diff --color-words"
    +git config --global core.editor vim
    +git config --global merge.summary true
    +
    +
    +

    To set up on another computer, you can copy your ~/.gitconfig file, +or run the commands above.

    +
    +
    +

    In detail

    +
    +

    user.name and user.email

    +

    It is good practice to tell git who you are, for labeling any changes +you make to the code. The simplest way to do this is from the command +line:

    +
    git config --global user.name "Your Name"
    +git config --global user.email you@yourdomain.example.com
    +
    +
    +

    This will write the settings into your git configuration file, which +should now contain a user section with your name and email:

    +
    [user]
    +      name = Your Name
    +      email = you@yourdomain.example.com
    +
    +
    +

    Of course you’ll need to replace Your Name and you@yourdomain.example.com +with your actual name and email address.

    +
    +
    +

    Aliases

    +

    You might well benefit from some aliases to common commands.

    +

    For example, you might well want to be able to shorten git checkout +to git co. Or you may want to alias git diff --color-words +(which gives a nicely formatted output of the diff) to git wdiff

    +

    The following git config --global commands:

    +
    git config --global alias.ci "commit -a"
    +git config --global alias.co checkout
    +git config --global alias.st "status -a"
    +git config --global alias.stat "status -a"
    +git config --global alias.br branch
    +git config --global alias.wdiff "diff --color-words"
    +
    +
    +

    will create an alias section in your .gitconfig file with contents +like this:

    +
    [alias]
    +        ci = commit -a
    +        co = checkout
    +        st = status -a
    +        stat = status -a
    +        br = branch
    +        wdiff = diff --color-words
    +
    +
    +
    +
    +

    Editor

    +

    You may also want to make sure that your editor of choice is used

    +
    git config --global core.editor vim
    +
    +
    +
    +
    +

    Merging

    +

    To enforce summaries when doing merges (~/.gitconfig file again):

    +
    [merge]
    +   log = true
    +
    +
    +

    Or from the command line:

    +
    git config --global merge.log true
    +
    +
    +
    +
    +

    Fancy log output

    +

    This is a very nice alias to get a fancy log output; it should go in the +alias section of your .gitconfig file:

    +
    lg = log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)[%an]%Creset' --abbrev-commit --date=relative
    +
    +
    +

    You use the alias with:

    +
    git lg
    +
    +
    +

    and it gives graph / text output something like this (but with color!):

    +
    * 6d8e1ee - (HEAD, origin/my-fancy-feature, my-fancy-feature) NF - a fancy file (45 minutes ago) [Matthew Brett]
    +*   d304a73 - (origin/placeholder, placeholder) Merge pull request #48 from hhuuggoo/master (2 weeks ago) [Jonathan Terhorst]
    +|\
    +| * 4aff2a8 - fixed bug 35, and added a test in test_bugfixes (2 weeks ago) [Hugo]
    +|/
    +* a7ff2e5 - Added notes on discussion/proposal made during Data Array Summit. (2 weeks ago) [Corran Webster]
    +* 68f6752 - Initial implementation of AxisIndexer - uses 'index_by' which needs to be changed to a call on an Axes object - this is all very sketchy right now. (2 weeks ago) [Corr
    +*   376adbd - Merge pull request #46 from terhorst/master (2 weeks ago) [Jonathan Terhorst]
    +|\
    +| * b605216 - updated joshu example to current api (3 weeks ago) [Jonathan Terhorst]
    +| * 2e991e8 - add testing for outer ufunc (3 weeks ago) [Jonathan Terhorst]
    +| * 7beda5a - prevent axis from throwing an exception if testing equality with non-axis object (3 weeks ago) [Jonathan Terhorst]
    +| * 65af65e - convert unit testing code to assertions (3 weeks ago) [Jonathan Terhorst]
    +| *   956fbab - Merge remote-tracking branch 'upstream/master' (3 weeks ago) [Jonathan Terhorst]
    +| |\
    +| |/
    +
    +
    +

    Thanks to Yury V. Zaytsev for posting it.

    +
    +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/gitwash/development_workflow.html b/gitwash/development_workflow.html new file mode 100644 index 0000000000..042e30935b --- /dev/null +++ b/gitwash/development_workflow.html @@ -0,0 +1,506 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    Development workflow

    +

    You already have your own forked copy of the nibabel repository, by +following Making your own copy (fork) of nibabel. You have Set up your fork. You have configured +git by following Configure git. Now you are ready for some real work.

    +
    +

    Workflow summary

    +

    In what follows we’ll refer to the upstream nibabel master branch, as +“trunk”.

    +
      +
    • Don’t use your master branch for anything. Consider deleting it.

    • +
    • When you are starting a new set of changes, fetch any changes from trunk, +and start a new feature branch from that.

    • +
    • Make a new branch for each separable set of changes — “one task, one +branch” (ipython git workflow).

    • +
    • Name your branch for the purpose of the changes - e.g. +bugfix-for-issue-14 or refactor-database-code.

    • +
    • If you can possibly avoid it, avoid merging trunk or any other branches into +your feature branch while you are working.

    • +
    • If you do find yourself merging from trunk, consider Rebasing on trunk

    • +
    • Ask on the nibabel mailing list if you get stuck.

    • +
    • Ask for code review!

    • +
    +

    This way of working helps to keep work well organized, with readable history. +This in turn makes it easier for project maintainers (that might be you) to see +what you’ve done, and why you did it.

    +

    See linux git workflow and ipython git workflow for some explanation.

    +
    +
    +

    Consider deleting your master branch

    +

    It may sound strange, but deleting your own master branch can help reduce +confusion about which branch you are on. See deleting master on github for +details.

    +
    +
    +

    Update the mirror of trunk

    +

    First make sure you have done Linking your repository to the upstream repo.

    +

    From time to time you should fetch the upstream (trunk) changes from github:

    +
    git fetch upstream
    +
    +
    +

    This will pull down any commits you don’t have, and set the remote branches to +point to the right commit. For example, ‘trunk’ is the branch referred to by +(remote/branchname) upstream/master - and if there have been commits since +you last checked, upstream/master will change after you do the fetch.

    +
    +
    +

    Make a new feature branch

    +

    When you are ready to make some changes to the code, you should start a new +branch. Branches that are for a collection of related edits are often called +‘feature branches’.

    +

    Making an new branch for each set of related changes will make it easier for +someone reviewing your branch to see what you are doing.

    +

    Choose an informative name for the branch to remind yourself and the rest of us +what the changes in the branch are for. For example add-ability-to-fly, or +buxfix-for-issue-42.

    +
    # Update the mirror of trunk
    +git fetch upstream
    +# Make new feature branch starting at current trunk
    +git branch my-new-feature upstream/master
    +git checkout my-new-feature
    +
    +
    +

    Generally, you will want to keep your feature branches on your public github +fork of nibabel. To do this, you git push this new branch up to your +github repo. Generally (if you followed the instructions in these pages, and by +default), git will have a link to your github repo, called origin. You push +up to your own repo on github with:

    +
    git push origin my-new-feature
    +
    +
    +

    In git >= 1.7 you can ensure that the link is correctly set by using the +--set-upstream option:

    +
    git push --set-upstream origin my-new-feature
    +
    +
    +

    From now on git will know that my-new-feature is related to the +my-new-feature branch in the github repo.

    +
    +
    +

    The editing workflow

    +
    +

    Overview

    +
    # hack hack
    +git add my_new_file
    +git commit -am 'NF - some message'
    +git push
    +
    +
    +
    +
    +

    In more detail

    +
      +
    1. Make some changes

    2. +
    3. See which files have changed with git status (see git status). +You’ll see a listing like this one:

      +
      # On branch ny-new-feature
      +# Changed but not updated:
      +#   (use "git add <file>..." to update what will be committed)
      +#   (use "git checkout -- <file>..." to discard changes in working directory)
      +#
      +#  modified:   README
      +#
      +# Untracked files:
      +#   (use "git add <file>..." to include in what will be committed)
      +#
      +#  INSTALL
      +no changes added to commit (use "git add" and/or "git commit -a")
      +
      +
      +
    4. +
    5. Check what the actual changes are with git diff (git diff).

    6. +
    7. Add any new files to version control git add new_file_name (see +git add).

    8. +
    9. To commit all modified files into the local copy of your repo,, do +git commit -am 'A commit message'. Note the -am options to +commit. The m flag just signals that you’re going to type a +message on the command line. The a flag — you can just take on +faith — or see why the -a flag? — and the helpful use-case +description in the tangled working copy problem. The git commit manual +page might also be useful.

    10. +
    11. To push the changes up to your forked repo on github, do a git +push (see git push).

    12. +
    +
    +
    +
    +

    Ask for your changes to be reviewed or merged

    +

    When you are ready to ask for someone to review your code and consider a merge:

    +
      +
    1. Go to the URL of your forked repo, say +https://github.com/your-user-name/nibabel.

    2. +
    3. Use the ‘Switch Branches’ dropdown menu near the top left of the page to +select the branch with your changes:

      +../_images/branch_dropdown.png +
    4. +
    5. Click on the ‘Pull request’ button:

      +../_images/pull_button.png +

      Enter a title for the set of changes, and some explanation of what you’ve +done. Say if there is anything you’d like particular attention for - like a +complicated change or some code you are not happy with.

      +

      If you don’t think your request is ready to be merged, just say so in your +pull request message. This is still a good way of getting some preliminary +code review.

      +
    6. +
    +
    +
    +

    Some other things you might want to do

    +
    +

    Delete a branch on github

    +
    git checkout master
    +# delete branch locally
    +git branch -D my-unwanted-branch
    +# delete branch on github
    +git push origin :my-unwanted-branch
    +
    +
    +

    (Note the colon : before test-branch. See also: +https://github.com/guides/remove-a-remote-branch

    +
    +
    +

    Several people sharing a single repository

    +

    If you want to work on some stuff with other people, where you are all +committing into the same repository, or even the same branch, then just +share it via github.

    +

    First fork nibabel into your account, as from Making your own copy (fork) of nibabel.

    +

    Then, go to your forked repository github page, say +https://github.com/your-user-name/nibabel

    +

    Click on the ‘Admin’ button, and add anyone else to the repo as a +collaborator:

    +
    +
    ../_images/pull_button.png +
    +

    Now all those people can do:

    +
    git clone git@githhub.com:your-user-name/nibabel.git
    +
    +
    +

    Remember that links starting with git@ use the ssh protocol and are +read-write; links starting with git:// are read-only.

    +

    Your collaborators can then commit directly into that repo with the +usual:

    +
    git commit -am 'ENH - much better code'
    +git push origin master # pushes directly into your repo
    +
    +
    +
    +
    +

    Explore your repository

    +

    To see a graphical representation of the repository branches and +commits:

    +
    gitk --all
    +
    +
    +

    To see a linear list of commits for this branch:

    +
    git log
    +
    +
    +

    You can also look at the network graph visualizer for your github +repo.

    +

    Finally the Fancy log output lg alias will give you a reasonable text-based +graph of the repository.

    +
    +
    +

    Rebasing on trunk

    +

    Let’s say you thought of some work you’d like to do. You +Update the mirror of trunk and Make a new feature branch called +cool-feature. At this stage trunk is at some commit, let’s call it E. Now +you make some new commits on your cool-feature branch, let’s call them A, B, +C. Maybe your changes take a while, or you come back to them after a while. In +the meantime, trunk has progressed from commit E to commit (say) G:

    +
          A---B---C cool-feature
    +     /
    +D---E---F---G trunk
    +
    +
    +

    At this stage you consider merging trunk into your feature branch, and you +remember that this here page sternly advises you not to do that, because the +history will get messy. Most of the time you can just ask for a review, and not +worry that trunk has got a little ahead. But sometimes, the changes in trunk +might affect your changes, and you need to harmonize them. In this situation +you may prefer to do a rebase.

    +

    rebase takes your changes (A, B, C) and replays them as if they had been made to +the current state of trunk. In other words, in this case, it takes the +changes represented by A, B, C and replays them on top of G. After the rebase, +your history will look like this:

    +
                  A'--B'--C' cool-feature
    +             /
    +D---E---F---G trunk
    +
    +
    +

    See rebase without tears for more detail.

    +

    To do a rebase on trunk:

    +
    # Update the mirror of trunk
    +git fetch upstream
    +# go to the feature branch
    +git checkout cool-feature
    +# make a backup in case you mess up
    +git branch tmp cool-feature
    +# rebase cool-feature onto trunk
    +git rebase --onto upstream/master upstream/master cool-feature
    +
    +
    +

    In this situation, where you are already on branch cool-feature, the last +command can be written more succinctly as:

    +
    git rebase upstream/master
    +
    +
    +

    When all looks good you can delete your backup branch:

    +
    git branch -D tmp
    +
    +
    +

    If it doesn’t look good you may need to have a look at +Recovering from mess-ups.

    +

    If you have made changes to files that have also changed in trunk, this may +generate merge conflicts that you need to resolve - see the git rebase man +page for some instructions at the end of the “Description” section. There is +some related help on merging in the git user manual - see resolving a merge.

    +
    +
    +

    Recovering from mess-ups

    +

    Sometimes, you mess up merges or rebases. Luckily, in git it is +relatively straightforward to recover from such mistakes.

    +

    If you mess up during a rebase:

    +
    git rebase --abort
    +
    +
    +

    If you notice you messed up after the rebase:

    +
    # reset branch back to the saved point
    +git reset --hard tmp
    +
    +
    +

    If you forgot to make a backup branch:

    +
    # look at the reflog of the branch
    +git reflog show cool-feature
    +
    +8630830 cool-feature@{0}: commit: BUG: io: close file handles immediately
    +278dd2a cool-feature@{1}: rebase finished: refs/heads/my-feature-branch onto 11ee694744f2552d
    +26aa21a cool-feature@{2}: commit: BUG: lib: make seek_gzip_factory not leak gzip obj
    +...
    +
    +# reset the branch to where it was before the botched rebase
    +git reset --hard cool-feature@{2}
    +
    +
    +
    +
    +

    Rewriting commit history

    +
    +

    Note

    +

    Do this only for your own feature branches.

    +
    +

    There’s an embarrassing typo in a commit you made? Or perhaps the you +made several false starts you would like the posterity not to see.

    +

    This can be done via interactive rebasing.

    +

    Suppose that the commit history looks like this:

    +
    git log --oneline
    +eadc391 Fix some remaining bugs
    +a815645 Modify it so that it works
    +2dec1ac Fix a few bugs + disable
    +13d7934 First implementation
    +6ad92e5 * masked is now an instance of a new object, MaskedConstant
    +29001ed Add pre-nep for a copule of structured_array_extensions.
    +...
    +
    +
    +

    and 6ad92e5 is the last commit in the cool-feature branch. Suppose we +want to make the following changes:

    +
      +
    • Rewrite the commit message for 13d7934 to something more sensible.

    • +
    • Combine the commits 2dec1ac, a815645, eadc391 into a single one.

    • +
    +

    We do as follows:

    +
    # make a backup of the current state
    +git branch tmp HEAD
    +# interactive rebase
    +git rebase -i 6ad92e5
    +
    +
    +

    This will open an editor with the following text in it:

    +
    pick 13d7934 First implementation
    +pick 2dec1ac Fix a few bugs + disable
    +pick a815645 Modify it so that it works
    +pick eadc391 Fix some remaining bugs
    +
    +# Rebase 6ad92e5..eadc391 onto 6ad92e5
    +#
    +# Commands:
    +#  p, pick = use commit
    +#  r, reword = use commit, but edit the commit message
    +#  e, edit = use commit, but stop for amending
    +#  s, squash = use commit, but meld into previous commit
    +#  f, fixup = like "squash", but discard this commit's log message
    +#
    +# If you remove a line here THAT COMMIT WILL BE LOST.
    +# However, if you remove everything, the rebase will be aborted.
    +#
    +
    +
    +

    To achieve what we want, we will make the following changes to it:

    +
    r 13d7934 First implementation
    +pick 2dec1ac Fix a few bugs + disable
    +f a815645 Modify it so that it works
    +f eadc391 Fix some remaining bugs
    +
    +
    +

    This means that (i) we want to edit the commit message for +13d7934, and (ii) collapse the last three commits into one. Now we +save and quit the editor.

    +

    Git will then immediately bring up an editor for editing the commit +message. After revising it, we get the output:

    +
    [detached HEAD 721fc64] FOO: First implementation
    + 2 files changed, 199 insertions(+), 66 deletions(-)
    +[detached HEAD 0f22701] Fix a few bugs + disable
    + 1 files changed, 79 insertions(+), 61 deletions(-)
    +Successfully rebased and updated refs/heads/my-feature-branch.
    +
    +
    +

    and the history looks now like this:

    +
    0f22701 Fix a few bugs + disable
    +721fc64 ENH: Sophisticated feature
    +6ad92e5 * masked is now an instance of a new object, MaskedConstant
    +
    +
    +

    If it went wrong, recovery is again possible as explained above.

    +
    +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/gitwash/following_latest.html b/gitwash/following_latest.html new file mode 100644 index 0000000000..6b88d555ec --- /dev/null +++ b/gitwash/following_latest.html @@ -0,0 +1,149 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    Following the latest source

    +

    These are the instructions if you just want to follow the latest +nibabel source, but you don’t need to do any development for now.

    +

    The steps are:

    +
      +
    • Install git

    • +
    • get local copy of the git repository from github

    • +
    • update local copy from time to time

    • +
    +
    +

    Get the local copy of the code

    +

    From the command line:

    +
    git clone git://github.com/nipy/nibabel.git
    +
    +
    +

    You now have a copy of the code tree in the new nibabel directory.

    +
    +
    +

    Updating the code

    +

    From time to time you may want to pull down the latest code. Do this with:

    +
    cd nibabel
    +git pull
    +
    +
    +

    The tree in nibabel will now have the latest changes from the initial +repository.

    +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/gitwash/forking_hell.html b/gitwash/forking_hell.html new file mode 100644 index 0000000000..5cb11d09f6 --- /dev/null +++ b/gitwash/forking_hell.html @@ -0,0 +1,146 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    Making your own copy (fork) of nibabel

    +

    You need to do this only once. The instructions here are very similar +to the instructions at https://help.github.com/articles/fork-a-repo/ — +please see that page for more detail. We’re repeating some of it here just to +give the specifics for the nibabel project, and to suggest some default names.

    +
    +

    Set up and configure a github account

    +

    If you don’t have a github account, go to the github page, and make one.

    +

    You then need to configure your account to allow write access — see +the Generating SSH keys help on github help.

    +
    +
    +

    Create your own forked copy of nibabel

    +
      +
    1. Log into your github account.

    2. +
    3. Go to the nibabel github home at nibabel github.

    4. +
    5. Click on the fork button:

      +../_images/forking_button.png +

      Now, after a short pause and some ‘Hardcore forking action’, you +should find yourself at the home page for your own forked copy of nibabel.

      +
    6. +
    +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/gitwash/git_development.html b/gitwash/git_development.html new file mode 100644 index 0000000000..fd52d4dd04 --- /dev/null +++ b/gitwash/git_development.html @@ -0,0 +1,146 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/gitwash/git_install.html b/gitwash/git_install.html new file mode 100644 index 0000000000..e7f34cb525 --- /dev/null +++ b/gitwash/git_install.html @@ -0,0 +1,148 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    Install git

    +
    +

    Overview

    + + + + + + + + + + + + + + + +

    Debian / Ubuntu

    sudo apt-get install git-core

    Fedora

    sudo yum install git-core

    Windows

    Download and install msysGit

    OS X

    Use the git-osx-installer

    +
    +
    +

    In detail

    +

    See the git page for the most recent information.

    +

    Have a look at the github install help pages available from github help

    +

    There are good instructions here: https://git-scm.com/book/en/v2/Getting-Started-Installing-Git

    +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/gitwash/git_intro.html b/gitwash/git_intro.html new file mode 100644 index 0000000000..7563a6ddcb --- /dev/null +++ b/gitwash/git_intro.html @@ -0,0 +1,121 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    Introduction

    +

    These pages describe a git and github workflow for the nibabel +project.

    +

    There are several different workflows here, for different ways of +working with nibabel.

    +

    This is not a comprehensive git reference, it’s just a workflow for our +own project. It’s tailored to the github hosting service. You may well +find better or quicker ways of getting stuff done with git, but these +should get you started.

    +

    For general resources for learning git, see git resources.

    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/gitwash/git_resources.html b/gitwash/git_resources.html new file mode 100644 index 0000000000..c2a14fa7f1 --- /dev/null +++ b/gitwash/git_resources.html @@ -0,0 +1,177 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    git resources

    +
    +

    Tutorials and summaries

    + +
    +
    +

    Advanced git workflow

    +

    There are many ways of working with git; here are some posts on the +rules of thumb that other projects have come up with:

    +
      +
    • Linus Torvalds on git management

    • +
    • Linus Torvalds on linux git workflow . Summary; use the git tools +to make the history of your edits as clean as possible; merge from +upstream edits as little as possible in branches where you are doing +active development.

    • +
    +
    +
    +

    Manual pages online

    +

    You can get these on your own machine with (e.g) git help push or +(same thing) git push --help, but, for convenience, here are the +online manual pages for some common commands:

    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/gitwash/index.html b/gitwash/index.html new file mode 100644 index 0000000000..072c16288b --- /dev/null +++ b/gitwash/index.html @@ -0,0 +1,146 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/gitwash/maintainer_workflow.html b/gitwash/maintainer_workflow.html new file mode 100644 index 0000000000..3ec44106f1 --- /dev/null +++ b/gitwash/maintainer_workflow.html @@ -0,0 +1,206 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    Maintainer workflow

    +

    This page is for maintainers — those of us who merge our own or other +peoples’ changes into the upstream repository.

    +

    Being as how you’re a maintainer, you are completely on top of the basic stuff +in Development workflow.

    +

    The instructions in Linking your repository to the upstream repo add a remote that has read-only +access to the upstream repo. Being a maintainer, you’ve got read-write access.

    +

    It’s good to have your upstream remote have a scary name, to remind you that +it’s a read-write remote:

    +
    git remote add upstream-rw git@github.com:nipy/nibabel.git
    +git fetch upstream-rw
    +
    +
    +
    +

    Integrating changes

    +

    Let’s say you have some changes that need to go into trunk +(upstream-rw/master).

    +

    The changes are in some branch that you are currently on. For example, you are +looking at someone’s changes like this:

    +
    git remote add someone git://github.com/someone/nibabel.git
    +git fetch someone
    +git branch cool-feature --track someone/cool-feature
    +git checkout cool-feature
    +
    +
    +

    So now you are on the branch with the changes to be incorporated upstream. The +rest of this section assumes you are on this branch.

    +
    +

    A few commits

    +

    If there are only a few commits, consider rebasing to upstream:

    +
    # Fetch upstream changes
    +git fetch upstream-rw
    +# rebase
    +git rebase upstream-rw/master
    +
    +
    +

    Remember that, if you do a rebase, and push that, you’ll have to close any +github pull requests manually, because github will not be able to detect the +changes have already been merged.

    +
    +
    +

    A long series of commits

    +

    If there are a longer series of related commits, consider a merge instead:

    +
    git fetch upstream-rw
    +git merge --no-ff upstream-rw/master
    +
    +
    +

    The merge will be detected by github, and should close any related pull requests +automatically.

    +

    Note the --no-ff above. This forces git to make a merge commit, rather than +doing a fast-forward, so that these set of commits branch off trunk then rejoin +the main history with a merge, rather than appearing to have been made directly +on top of trunk.

    +
    +
    +

    Check the history

    +

    Now, in either case, you should check that the history is sensible and you have +the right commits:

    +
    git log --oneline --graph
    +git log -p upstream-rw/master..
    +
    +
    +

    The first line above just shows the history in a compact way, with a text +representation of the history graph. The second line shows the log of commits +excluding those that can be reached from trunk (upstream-rw/master), and +including those that can be reached from current HEAD (implied with the .. +at the end). So, it shows the commits unique to this branch compared to trunk. +The -p option shows the diff for these commits in patch form.

    +
    +
    +

    Push to trunk

    +
    git push upstream-rw my-new-feature:master
    +
    +
    +

    This pushes the my-new-feature branch in this repository to the master +branch in the upstream-rw repository.

    +
    +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/gitwash/patching.html b/gitwash/patching.html new file mode 100644 index 0000000000..842ac60a3e --- /dev/null +++ b/gitwash/patching.html @@ -0,0 +1,252 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    Making a patch

    +

    You’ve discovered a bug or something else you want to change +in nibabel .. — excellent!

    +

    You’ve worked out a way to fix it — even better!

    +

    You want to tell us about it — best of all!

    +

    The easiest way is to make a patch or set of patches. Here +we explain how. Making a patch is the simplest and quickest, +but if you’re going to be doing anything more than simple +quick things, please consider following the +Git for development model instead.

    +
    +

    Making patches

    +
    +

    Overview

    +
    # tell git who you are
    +git config --global user.email you@yourdomain.example.com
    +git config --global user.name "Your Name Comes Here"
    +# get the repository if you don't have it
    +git clone git://github.com/nipy/nibabel.git
    +# make a branch for your patching
    +cd nibabel
    +git branch the-fix-im-thinking-of
    +git checkout the-fix-im-thinking-of
    +# hack, hack, hack
    +# Tell git about any new files you've made
    +git add somewhere/tests/test_my_bug.py
    +# commit work in progress as you go
    +git commit -am 'BF - added tests for Funny bug'
    +# hack hack, hack
    +git commit -am 'BF - added fix for Funny bug'
    +# make the patch files
    +git format-patch -M -C master
    +
    +
    +

    Then, send the generated patch files to the nibabel +mailing list — where we will thank you warmly.

    +
    +
    +

    In detail

    +
      +
    1. Tell git who you are so it can label the commits you’ve +made:

      +
      git config --global user.email you@yourdomain.example.com
      +git config --global user.name "Your Name Comes Here"
      +
      +
      +
    2. +
    3. If you don’t already have one, clone a copy of the +nibabel repository:

      +
      git clone git://github.com/nipy/nibabel.git
      +cd nibabel
      +
      +
      +
    4. +
    5. Make a ‘feature branch’. This will be where you work on +your bug fix. It’s nice and safe and leaves you with +access to an unmodified copy of the code in the main +branch:

      +
      git branch the-fix-im-thinking-of
      +git checkout the-fix-im-thinking-of
      +
      +
      +
    6. +
    7. Do some edits, and commit them as you go:

      +
      # hack, hack, hack
      +# Tell git about any new files you've made
      +git add somewhere/tests/test_my_bug.py
      +# commit work in progress as you go
      +git commit -am 'BF - added tests for Funny bug'
      +# hack hack, hack
      +git commit -am 'BF - added fix for Funny bug'
      +
      +
      +

      Note the -am options to commit. The m flag just +signals that you’re going to type a message on the command +line. The a flag — you can just take on faith — +or see why the -a flag?.

      +
    8. +
    9. When you have finished, check you have committed all your +changes:

      +
      git status
      +
      +
      +
    10. +
    11. Finally, make your commits into patches. You want all the +commits since you branched from the master branch:

      +
      git format-patch -M -C master
      +
      +
      +

      You will now have several files named for the commits:

      +
      0001-BF-added-tests-for-Funny-bug.patch
      +0002-BF-added-fix-for-Funny-bug.patch
      +
      +
      +

      Send these files to the nibabel mailing list.

      +
    12. +
    +

    When you are done, to switch back to the main copy of the +code, just return to the master branch:

    +
    git checkout master
    +
    +
    +
    +
    +
    +

    Moving from patching to development

    +

    If you find you have done some patches, and you have one or +more feature branches, you will probably want to switch to +development mode. You can do this with the repository you +have.

    +

    Fork the nibabel repository on github — Making your own copy (fork) of nibabel. +Then:

    +
    # checkout and refresh master branch from main repo
    +git checkout master
    +git pull origin master
    +# rename pointer to main repository to 'upstream'
    +git remote rename origin upstream
    +# point your repo to default read / write to your fork on github
    +git remote add origin git@github.com:your-user-name/nibabel.git
    +# push up any branches you've made and want to keep
    +git push origin the-fix-im-thinking-of
    +
    +
    +

    Then you can, if you want, follow the +Development workflow.

    +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/gitwash/set_up_fork.html b/gitwash/set_up_fork.html new file mode 100644 index 0000000000..c4f30b4856 --- /dev/null +++ b/gitwash/set_up_fork.html @@ -0,0 +1,182 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    Set up your fork

    +

    First you follow the instructions for Making your own copy (fork) of nibabel.

    +
    +

    Overview

    +
    git clone git@github.com:your-user-name/nibabel.git
    +cd nibabel
    +git remote add upstream git://github.com/nipy/nibabel.git
    +
    +
    +
    +
    +

    In detail

    +
    +

    Clone your fork

    +
      +
    1. Clone your fork to the local computer with git clone +git@github.com:your-user-name/nibabel.git

    2. +
    3. Investigate. Change directory to your new repo: cd nibabel. Then +git branch -a to show you all branches. You’ll get something +like:

      +
      * master
      +remotes/origin/master
      +
      +
      +

      This tells you that you are currently on the master branch, and +that you also have a remote connection to origin/master. +What remote repository is remote/origin? Try git remote -v to +see the URLs for the remote. They will point to your github fork.

      +

      Now you want to connect to the upstream nibabel github repository, so +you can merge in changes from trunk.

      +
    4. +
    +
    +
    +

    Linking your repository to the upstream repo

    +
    cd nibabel
    +git remote add upstream git://github.com/nipy/nibabel.git
    +
    +
    +

    upstream here is just the arbitrary name we’re using to refer to the +main nibabel repository at nibabel github.

    +

    Note that we’ve used git:// for the URL rather than git@. The +git:// URL is read only. This means we that we can’t accidentally +(or deliberately) write to the upstream repo, and we are only going to +use it to merge into our own code.

    +

    Just for your own satisfaction, show yourself that you now have a new +‘remote’, with git remote -v show, giving you something like:

    +
    upstream     git://github.com/nipy/nibabel.git (fetch)
    +upstream     git://github.com/nipy/nibabel.git (push)
    +origin       git@github.com:your-user-name/nibabel.git (fetch)
    +origin       git@github.com:your-user-name/nibabel.git (push)
    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/image_orientation.html b/image_orientation.html new file mode 100644 index 0000000000..f573d2017a --- /dev/null +++ b/image_orientation.html @@ -0,0 +1,188 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    Image voxel orientation

    +

    It is sometimes useful to know the approximate world-space orientations of the +image voxel axes.

    +

    See Coordinate systems and affines for background on voxel and world axes.

    +

    For example, let’s say we had an image with an identity affine:

    +
    >>> import numpy as np
    +>>> import nibabel as nib
    +>>> affine = np.eye(4)  # identity affine
    +>>> voxel_data = np.random.normal(size=(10, 11, 12))
    +>>> img = nib.Nifti1Image(voxel_data, affine)
    +
    +
    +

    Because the affine is an identity affine, the voxel axes align with the world +axes. By convention, nibabel world axes are always in RAS+ orientation (left +to Right, posterior to Anterior, inferior to Superior).

    +

    Let’s say we took a single line of voxels along the first voxel axis:

    +
    >>> single_line_axis_0 = voxel_data[:, 0, 0]
    +
    +
    +

    The first voxel axis is aligned to the left to Right world axes. This means +that the first voxel is towards the left of the world, and the last voxel is +towards the right of the world.

    +

    Here is a single line in the second axis:

    +
    >>> single_line_axis_1 = voxel_data[0, :, 0]
    +
    +
    +

    The first voxel in this line is towards the posterior of the world, and the +last towards the anterior.

    +
    >>> single_line_axis_2 = voxel_data[0, 0, :]
    +
    +
    +

    The first voxel in this line is towards the inferior of the world, and the +last towards the superior.

    +

    This image therefore has RAS+ voxel axes.

    +

    In other cases, it is not so obvious what the orientations of the axes are. +For example, here is our example NIfTI 1 file again:

    +
    >>> import os
    +>>> from nibabel.testing import data_path
    +>>> example_file = os.path.join(data_path, 'example4d.nii.gz')
    +>>> img = nib.load(example_file)
    +
    +
    +

    Here is the affine (to two digits decimal precision):

    +
    >>> np.set_printoptions(precision=2, suppress=True)
    +>>> img.affine
    +array([[ -2.  ,   0.  ,   0.  , 117.86],
    +       [ -0.  ,   1.97,  -0.36, -35.72],
    +       [  0.  ,   0.32,   2.17,  -7.25],
    +       [  0.  ,   0.  ,   0.  ,   1.  ]])
    +
    +
    +

    What are the orientations of the voxel axes here?

    +

    Nibabel has a routine to tell you, called aff2axcodes.

    +
    >>> nib.aff2axcodes(img.affine)
    +('L', 'A', 'S')
    +
    +
    +

    The voxel orientations are nearest to:

    +
      +
    1. First voxel axis goes from right to Left;

    2. +
    3. Second voxel axis goes from posterior to Anterior;

    4. +
    5. Third voxel axis goes from inferior to Superior.

    6. +
    +

    Sometimes you may want to rearrange the image voxel axes to make them as close +as possible to RAS+ orientation. We refer to this voxel orientation as +canonical voxel orientation, because RAS+ is our canonical world +orientation. Rearranging the voxel axes means reversing and / or reordering +the voxel axes.

    +

    You can do the arrangement with as_closest_canonical:

    +
    >>> canonical_img = nib.as_closest_canonical(img)
    +>>> canonical_img.affine
    +array([[   2.  ,    0.  ,    0.  , -136.14],
    +       [   0.  ,    1.97,   -0.36,  -35.72],
    +       [  -0.  ,    0.32,    2.17,   -7.25],
    +       [   0.  ,    0.  ,    0.  ,    1.  ]])
    +>>> nib.aff2axcodes(canonical_img.affine)
    +('R', 'A', 'S')
    +
    +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/images_and_memory.html b/images_and_memory.html new file mode 100644 index 0000000000..d3d3a052ed --- /dev/null +++ b/images_and_memory.html @@ -0,0 +1,333 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    Images and memory

    +

    We saw in Nibabel images that images loaded from disk are usually +proxy images. Proxy images are images that have a dataobj property that +is not a numpy array, but an array proxy that can fetch the array data from +disk.

    +
    >>> import os
    +>>> import numpy as np
    +>>> from nibabel.testing import data_path
    +>>> example_file = os.path.join(data_path, 'example4d.nii.gz')
    +
    +
    +
    >>> import nibabel as nib
    +>>> img = nib.load(example_file)
    +>>> img.dataobj
    +<nibabel.arrayproxy.ArrayProxy object at ...>
    +
    +
    +

    Nibabel does not load the image array from the proxy when you load the +image. It waits until you ask for the array data. The standard way to ask +for the array data is to call the get_fdata() method:

    +
    >>> data = img.get_fdata()
    +>>> data.shape
    +(128, 96, 24, 2)
    +
    +
    +

    We also saw in Proxies and caching that this call to get_fdata() will +(by default) load the array data into an internal image cache. The image +returns the cached copy on the next call to get_fdata():

    +
    >>> data_again = img.get_fdata()
    +>>> data is data_again
    +True
    +
    +
    +

    This behavior is convenient if you want quick and repeated access to the image +array data. The down-side is that the image keeps a reference to the image +data array, so the array can’t be cleared from memory until the image object +gets deleted. You might prefer to keep loading the array from disk instead of +keeping the cached copy in the image.

    +

    This page describes ways of using the image array proxies to save memory and +time.

    +
    +

    Using in_memory to check the state of the cache

    +

    You can use the in_memory property to check if the image has cached the +array.

    +

    The in_memory property is always True for array images, because the image +data is always an array in memory:

    +
    >>> array_data = np.arange(24, dtype=np.int16).reshape((2, 3, 4))
    +>>> affine = np.diag([1, 2, 3, 1])
    +>>> array_img = nib.Nifti1Image(array_data, affine)
    +>>> array_img.in_memory
    +True
    +
    +
    +

    For a proxy image, the in_memory property is False when the array is not +in cache, and True when it is in cache:

    +
    >>> img = nib.load(example_file)
    +>>> img.in_memory
    +False
    +>>> data = img.get_fdata()
    +>>> img.in_memory
    +True
    +
    +
    +
    +
    +

    Using uncache

    +

    As y’all know, the proxy image has the array in cache, get_fdata() returns +the cached array:

    +
    >>> data_again = img.get_fdata()
    +>>> data_again is data  # same array returned from cache
    +True
    +
    +
    +

    You can uncache a proxy image with the uncache() method:

    +
    >>> img.uncache()
    +>>> img.in_memory
    +False
    +>>> data_once_more = img.get_fdata()
    +>>> data_once_more is data  # a new copy read from disk
    +False
    +
    +
    +

    uncache() has no effect if the image is an array image, or if the cache is +already empty.

    +

    You need to be careful when you modify arrays returned by get_fdata() on +proxy images, because uncache will then change the result you get back +from get_fdata():

    +
    >>> proxy_img = nib.load(example_file)
    +>>> data = proxy_img.get_fdata()  # array cached and returned
    +>>> data[0, 0, 0, 0]
    +0.0
    +>>> data[0, 0, 0, 0] = 99  # modify returned array
    +>>> data_again = proxy_img.get_fdata()  # return cached array
    +>>> data_again[0, 0, 0, 0]  # cached array modified
    +99.0
    +
    +
    +

    So far the proxy image behaves the same as an array image. uncache() has +no effect on an array image, but it does have an effect on the returned array +of a proxy image:

    +
    >>> proxy_img.uncache()  # cached array discarded from proxy image
    +>>> data_once_more = proxy_img.get_fdata()  # new copy of array loaded
    +>>> data_once_more[0, 0, 0, 0]  # array modifications discarded
    +0.0
    +
    +
    +
    +
    +

    Saving memory

    +
    +

    Uncache the array

    +

    If you do not want the image to keep the array in its internal cache, you can +use the uncache() method:

    +
    >>> img.uncache()
    +
    +
    +
    +
    +

    Use the array proxy instead of get_fdata()

    +

    The dataobj property of a proxy image is an array proxy. We can ask the +proxy to return the array directly by passing dataobj to the numpy +asarray function:

    +
    >>> proxy_img = nib.load(example_file)
    +>>> data_array = np.asarray(proxy_img.dataobj)
    +>>> type(data_array)
    +<... 'numpy.ndarray'>
    +
    +
    +

    This also works for array images, because np.asarray returns the array:

    +
    >>> array_img = nib.Nifti1Image(array_data, affine)
    +>>> data_array = np.asarray(array_img.dataobj)
    +>>> type(data_array)
    +<... 'numpy.ndarray'>
    +
    +
    +

    If you want to avoid caching you can avoid get_fdata() and always use +np.asarray(img.dataobj).

    +
    +
    +

    Use the caching keyword to get_fdata()

    +

    The default behavior of the get_fdata() function is to always fill the +cache, if it is empty. This corresponds to the default 'fill' value +to the caching keyword. So, this:

    +
    >>> proxy_img = nib.load(example_file)
    +>>> data = proxy_img.get_fdata()  # default caching='fill'
    +>>> proxy_img.in_memory
    +True
    +
    +
    +

    is the same as this:

    +
    >>> proxy_img = nib.load(example_file)
    +>>> data = proxy_img.get_fdata(caching='fill')
    +>>> proxy_img.in_memory
    +True
    +
    +
    +

    Sometimes you may want to avoid filling the cache, if it is empty. In this +case, you can use caching='unchanged':

    +
    >>> proxy_img = nib.load(example_file)
    +>>> data = proxy_img.get_fdata(caching='unchanged')
    +>>> proxy_img.in_memory
    +False
    +
    +
    +

    caching='unchanged' will leave the cache full if it is already full.

    +
    >>> data = proxy_img.get_fdata(caching='fill')
    +>>> proxy_img.in_memory
    +True
    +>>> data = proxy_img.get_fdata(caching='unchanged')
    +>>> proxy_img.in_memory
    +True
    +
    +
    +

    See the get_fdata() docstring for more detail.

    +
    +
    +
    +

    Saving time and memory

    +

    You can use the array proxy to get slices of data from disk in an efficient +way.

    +

    The array proxy API allows you to do slicing on the proxy. In most cases this +will mean that you only load the data from disk that you actually need, often +saving both time and memory.

    +

    For example, let us say you only wanted the second volume from the example +dataset. You could do this:

    +
    >>> proxy_img = nib.load(example_file)
    +>>> data = proxy_img.get_fdata()
    +>>> data.shape
    +(128, 96, 24, 2)
    +>>> vol1 = data[..., 1]
    +>>> vol1.shape
    +(128, 96, 24)
    +
    +
    +

    The problem is that you had to load the whole data array into memory before +throwing away the first volume and keeping the second.

    +

    You can use array proxy slicing to do this more efficiently:

    +
    >>> proxy_img = nib.load(example_file)
    +>>> vol1 = proxy_img.dataobj[..., 1]
    +>>> vol1.shape
    +(128, 96, 24)
    +
    +
    +

    The slicing call in proxy_img.dataobj[..., 1] will only load the data from +disk that you need to fill the memory of vol1.

    +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000000..c6d7fbbdf0 --- /dev/null +++ b/index.html @@ -0,0 +1,347 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    NiBabel

    +

    Read and write access to common neuroimaging file formats, including: +ANALYZE (plain, SPM99, SPM2 and later), GIFTI, NIfTI1, NIfTI2, CIFTI-2, +MINC1, MINC2, AFNI BRIK/HEAD, ECAT and Philips PAR/REC. +In addition, NiBabel also supports FreeSurfer’s MGH, geometry, annotation and +morphometry files, and provides some limited support for DICOM.

    +

    NiBabel’s API gives full or selective access to header information (metadata), +and image data is made available via NumPy arrays. For more information, see +NiBabel’s documentation site and API reference.

    +
    +

    Installation

    +

    To install NiBabel’s current release with pip, run:

    +
    pip install nibabel
    +
    +
    +

    To install the latest development version, run:

    +
    pip install git+https://github.com/nipy/nibabel
    +
    +
    +

    When working on NiBabel itself, it may be useful to install in “editable” mode:

    +
    git clone https://github.com/nipy/nibabel.git
    +pip install -e ./nibabel
    +
    +
    +

    For more information on previous releases, see the release archive or +development changelog.

    +
    +
    +

    Testing

    +

    During development, we recommend using tox to run nibabel tests:

    +
    git clone https://github.com/nipy/nibabel.git
    +cd nibabel
    +tox
    +
    +
    +

    To test an installed version of nibabel, install the test dependencies +and run pytest_:

    +
    pip install nibabel[test]
    +pytest --pyargs nibabel
    +
    +
    +

    For more information, consult the developer guidelines.

    +
    +
    +

    Mailing List

    +

    Please send any questions or suggestions to the neuroimaging mailing list.

    +
    +
    +

    License

    +

    NiBabel is licensed under the terms of the MIT license. +Some code included with NiBabel is licensed under the BSD license. +For more information, please see the COPYING file.

    +
    +
    +

    Citation

    +

    NiBabel releases have a Zenodo Digital Object Identifier (DOI) badge at +the top of the release notes. Click on the badge for more information.

    +
    +
    +

    Documentation

    + +

    See also the Developer documentation page for development +discussions, release procedure and more.

    +
    +
    +

    Authors and Contributors

    +

    Most work on NiBabel so far has been by Matthew Brett, Chris Markiewicz, +Michael Hanke, Marc-Alexandre Côté, Ben Cipollini, Paul McCarthy and +Chris Cheng. The authors are grateful to the following people who have +contributed code and discussion (in rough order of appearance):

    +
      +
    • Yaroslav O. Halchenko

    • +
    • Chris Burns

    • +
    • Gaël Varoquaux

    • +
    • Ian Nimmo-Smith

    • +
    • Jarrod Millman

    • +
    • Bertrand Thirion

    • +
    • Thomas Ballinger

    • +
    • Cindee Madison

    • +
    • Valentin Haenel

    • +
    • Alexandre Gramfort

    • +
    • Christian Haselgrove

    • +
    • Krish Subramaniam

    • +
    • Yannick Schwartz

    • +
    • Bago Amirbekian

    • +
    • Brendan Moloney

    • +
    • Félix C. Morency

    • +
    • JB Poline

    • +
    • Basile Pinsard

    • +
    • Satrajit Ghosh

    • +
    • Eric Larson

    • +
    • Nolan Nichols

    • +
    • Ly Nguyen

    • +
    • Philippe Gervais

    • +
    • Demian Wassermann

    • +
    • Justin Lecher

    • +
    • Oliver P. Hinds

    • +
    • Nikolaas N. Oosterhof

    • +
    • Kevin S. Hahn

    • +
    • Michiel Cottaar

    • +
    • Erik Kastman

    • +
    • Github user freec84

    • +
    • Peter Fischer

    • +
    • Clemens C. C. Bauer

    • +
    • Samuel St-Jean

    • +
    • Gregory R. Lee

    • +
    • Eric M. Baker

    • +
    • Ariel Rokem

    • +
    • Eleftherios Garyfallidis

    • +
    • Jaakko Leppäkangas

    • +
    • Syam Gadde

    • +
    • Robert D. Vincent

    • +
    • Ivan Gonzalez

    • +
    • Demian Wassermann

    • +
    • Paul McCarthy

    • +
    • Fernando Pérez García

    • +
    • Venky Reddy

    • +
    • Mark Hymers

    • +
    • Jasper J.F. van den Bosch

    • +
    • Bennet Fauber

    • +
    • Kesshi Jordan

    • +
    • Jon Stutters

    • +
    • Serge Koudoro

    • +
    • Christopher P. Cheng

    • +
    • Mathias Goncalves

    • +
    • Jakub Kaczmarzyk

    • +
    • Dimitri Papadopoulos Orfanos

    • +
    • Ross Markello

    • +
    • Miguel Estevan Moreno

    • +
    • Thomas Roos

    • +
    • Igor Solovey

    • +
    • Jon Haitz Legarreta Gorroño

    • +
    • Katrin Leinweber

    • +
    • Soichi Hayashi

    • +
    • Samir Reddigari

    • +
    • Konstantinos Raktivan

    • +
    • Matt Cieslak

    • +
    • Egor Panfilov

    • +
    • Jath Palasubramaniam

    • +
    • Henry Braun

    • +
    • Oscar Esteban

    • +
    • Cameron Riddell

    • +
    • Hao-Ting Wang

    • +
    • Dorota Jarecka

    • +
    • Chris Gorgolewski

    • +
    • Benjamin C Darwin

    • +
    • Zvi Baratz

    • +
    • Roberto Guidotti

    • +
    • Or Duek

    • +
    • Anibal Sólon

    • +
    • Jonathan Daniel

    • +
    • Markéta Calábková

    • +
    • Carl Gauthier

    • +
    • Julian Klug

    • +
    • Lea Waller

    • +
    • Tomáš Hrnčiar

    • +
    • Andrew Van

    • +
    • Jérôme Dockès

    • +
    • Jacob Roberts

    • +
    • Horea Christian

    • +
    • Fabian Perez

    • +
    • Mathieu Scheltienne

    • +
    • Reinder Vos de Wael

    • +
    • Peter Suter

    • +
    • Blake Dewey

    • +
    • Guillaume Becq

    • +
    • Joshua Newton

    • +
    • Sandro from the Fedora Project

    • +
    +
    +
    +

    License reprise

    +

    NiBabel is free-software (beer and speech) and covered by the MIT License. +This applies to all source code, documentation, examples and snippets inside +the source distribution (including this website). Please see the +appendix of the manual for the copyright statement and the +full text of the license.

    +
    +
    +

    Download and Installation

    +

    Please find detailed download and installation instructions in the manual.

    +
    +
    +

    Support

    +

    If you have problems installing the software or questions about usage, +documentation or anything else related to NiBabel, you can post to the NiPy +mailing list.

    +
    +
    Mailing list:
    +

    neuroimaging@python.org [subscription, archive]

    +
    +
    +

    We recommend that anyone using NiBabel subscribes to the mailing list. The +mailing list is the preferred way to announce changes and additions to the +project. You can also search the mailing list archive using the mailing list +archive search located in the sidebar of the NiBabel home page.

    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/installation.html b/installation.html new file mode 100644 index 0000000000..ed305ba273 --- /dev/null +++ b/installation.html @@ -0,0 +1,235 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    Installation

    +

    NiBabel is a pure Python package, +and it should be easy to get NiBabel running on any system. +For the most popular platforms and operating systems +there should be packages in the respective native packaging format +(DEB, RPM or installers). +On other systems you can install NiBabel using pip.

    +
    +

    Installer and packages

    +
    +

    pip and the Python package index

    +

    If you are not using a Linux package manager, then best way to install NiBabel +is via pip. If you don’t have pip already, follow the pip install +instructions.

    +

    Then open a terminal (Terminal.app on OSX, cmd or Powershell on +Windows), and type:

    +
    pip install nibabel
    +
    +
    +

    This will download and install NiBabel.

    +

    If you really like doing stuff manually, you can install NiBabel by downloading +the source from NiBabel pypi . Go to the pypi page and select the source +distribution you want. Download the distribution, unpack it, and then, from +the unpacked directory, run:

    +
    pip install .
    +
    +
    +

    If you get permission errors, this may be because pip is trying to install +to the system directories. You can solve this error by using sudo, but we +strongly suggest you either do an install into your “user” directories, like +this:

    +
    pip install --user .
    +
    +
    +

    or you work inside a virtualenv.

    +
    +
    +

    Debian/Ubuntu

    +

    Our friends at NeuroDebian have packaged NiBabel at NiBabel NeuroDebian. +Please follow the instructions on the NeuroDebian website on how to access +their repositories. Once this is done, installing NiBabel is:

    +
    apt-get update
    +apt-get install python-nibabel
    +
    +
    +
    +
    +
    +

    Install a development version

    +

    If you want to test the latest development version of nibabel, or you’d like to +help by contributing bug-fixes or new features (excellent!), then this section +is for you.

    +
    +

    Requirements

    +
      +
    • Python 3.9 or greater

    • +
    • NumPy 1.22 or greater

    • +
    • Packaging 20.0 or greater

    • +
    • importlib-resources 5.12 or greater (or Python 3.12+)

    • +
    • SciPy 1.8 or greater (optional, for full SPM-ANALYZE support)

    • +
    • h5py 3.5 or greater (optional, for MINC2 support)

    • +
    • PyDICOM 2.3.0 or greater (optional, for DICOM support)

    • +
    • Python Imaging Library 8.4 or greater (optional, for PNG conversion in DICOMFS)

    • +
    • pytest (optional, to run the tests)

    • +
    • sphinx (optional, to build the documentation)

    • +
    +
    +
    +

    Get the development sources

    +

    You can download a tarball of the latest development snapshot (i.e. the current +state of the master branch of the NiBabel source code repository) from the +NiBabel github page.

    +

    If you want to have access to the full NiBabel history and the latest +development code, do a full clone (AKA checkout) of the NiBabel +repository:

    +
    git clone https://github.com/nipy/nibabel.git
    +
    +
    +
    +
    +

    Installation

    +

    Just install the modules by invoking:

    +
    pip install .
    +
    +
    +

    See pip and the Python package index for advice on what to do for permission errors.

    +
    +
    +

    Validating your install

    +

    For a basic test of your installation, fire up Python and try importing the +module to see if everything is fine. It should look something like this:

    +
    Python 3.8.5 (default, Sep  4 2020, 07:30:14)
    +[GCC 7.3.0] :: Anaconda, Inc. on linux
    +Type "help", "copyright", "credits" or "license" for more information.
    +>>> import nibabel
    +>>>
    +
    +
    +

    To run the nibabel test suite, from the terminal run +pytest --pyargs nibabel or +python -c "import nibabel; nibabel.test().

    +

    To run an extended test suite that validates nibabel for long-running and +resource-intensive cases, please see Advanced Testing.

    +
    +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/legal.html b/legal.html new file mode 100644 index 0000000000..c165ac2098 --- /dev/null +++ b/legal.html @@ -0,0 +1,323 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + + + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/lgtm.yml b/lgtm.yml deleted file mode 100644 index 575aa0fd98..0000000000 --- a/lgtm.yml +++ /dev/null @@ -1,3 +0,0 @@ -# https://lgtm.com/rules/1510014536001/ -queries: - - exclude: py/clear-text-logging-sensitive-data diff --git a/manual.html b/manual.html new file mode 100644 index 0000000000..28ee3d14dc --- /dev/null +++ b/manual.html @@ -0,0 +1,409 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    NiBabel Manual

    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/min-requirements.txt b/min-requirements.txt deleted file mode 100644 index 455c6c8c62..0000000000 --- a/min-requirements.txt +++ /dev/null @@ -1,16 +0,0 @@ -# This file was autogenerated by uv via the following command: -# uv pip compile --resolution lowest-direct --python 3.9 -o min-requirements.txt pyproject.toml -importlib-resources==5.12.0 - # via nibabel (pyproject.toml) -numpy==1.22.0 - # via nibabel (pyproject.toml) -packaging==20.0 - # via nibabel (pyproject.toml) -pyparsing==3.2.0 - # via packaging -six==1.16.0 - # via packaging -typing-extensions==4.6.0 - # via nibabel (pyproject.toml) -zipp==3.20.2 - # via importlib-resources diff --git a/neuro_radio_conventions.html b/neuro_radio_conventions.html new file mode 100644 index 0000000000..7d658c9f1c --- /dev/null +++ b/neuro_radio_conventions.html @@ -0,0 +1,254 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    Radiological vs neurological conventions

    +

    It is relatively common to talk about images being in “radiological” compared +to “neurological” convention, but the terms can be used in different and +confusing ways.

    +

    See Coordinate systems and affines for background on voxel space, reference space +and affines.

    +
    +

    Neurological and radiological display convention

    +

    Radiologists like looking at their images with the patient’s left on the right +of the image. If they are looking at a brain image, it is as if they were +looking at the brain slice from the point of view of the patient’s feet. +Neurologists like looking at brain images with the patient’s right on the +right of the image. This perspective is as if the neurologist is looking at +the slice from the top of the patient’s head. The convention is one of image +display. The image can have any voxel arrangement on disk or memory, and any +output reference space; it is only necessary for the software displaying the +image to know the reference space and the (probably affine) mapping between +voxel space and reference space; then the software can work out which voxels +are on the left or right of the subject and flip the images to the taste of +the viewer. We could unpack these uses as neurological display convention +and radiological display convention.

    +

    Here is a very nice graphic by Chris Rorden showing these display +conventions, where the 3D rendering behind the sections shows the directions +that the neurologist and radiologist are thinking of:

    +_images/rorden_radio_neuro.jpg +

    In the image above, the subject has a stroke in left temporal lobe, causing a +dark area on the MRI.

    +
    +
    +

    Alignment of world and voxel axes

    +

    As we will see in the next section, radiological and neurological are +sometimes used to refer to particular alignments of the voxel input axes to +scanner RAS+ output axes. If we look at the affine mapping between voxel space +and scanner RAS+, we may find that moving along the first voxel axis by one +unit results in a equivalent scanner RAS+ movement that is mainly left to +right. This can happen with a diagonal 3x3 part of the affine mapping to +scanner RAS+ (see Coordinate systems and affines):

    +
    >>> import numpy as np
    +>>> from nibabel.affines import apply_affine
    +>>> diag_affine = np.array([[3., 0,  0,  0],
    +...                         [0,  3., 0,  0],
    +...                         [0,  0, 4.5, 0],
    +...                         [0,  0,  0,  1]])
    +>>> ijk = [1, 0, 0] # moving one unit on the first voxel axis
    +>>> apply_affine(diag_affine, ijk)
    +array([3., 0., 0.])
    +
    +
    +

    In this case the voxel axes are aligned to the output axes, in the sense that +moving in a positive direction on the first voxel axis results in increasing +values on the “R+” output axis, and similarly for the second voxel axis with +output “A+” and the third voxel axis with output “S+”.

    +

    Some people therefore refer to this alignment of voxel and RAS+ axes as +RAS voxel axes.

    +
    +
    +

    Neurological / radiological voxel layout

    +

    Very confusingly, some people refer to images with RAS voxel axes as having +“neurological” voxel layout. This is because the simplest way to display +slices from this voxel array will result in the left of the subject appearing +towards the left hand side of the screen and therefore neurological display +convention. If we take a slice \(k\) over the third axis of the image data array +(img_data[:, :, k]), the resulting slice will have a first array axis +going from left to right in terms of spatial position and the second array +axis going from posterior to anterior. If we display this image with the +first axis going from left to right on screen and the second from bottom to +top, it will have the subject’s right towards the right of the screen, and +anterior towards the top of the screen, as neurologists like it. Here we are +showing the middle slice of an image with RAS voxel axes:

    +
    >>> import nibabel as nib
    +>>> import matplotlib.pyplot as plt
    +>>> img = nib.load('downloads/someones_anatomy.nii.gz')
    +>>> # The 3x3 part of the affine is diagonal with all +ve values
    +>>> img.affine
    +array([[  2.75,   0.  ,   0.  , -78.  ],
    +       [  0.  ,   2.75,   0.  , -91.  ],
    +       [  0.  ,   0.  ,   2.75, -91.  ],
    +       [  0.  ,   0.  ,   0.  ,   1.  ]])
    +>>> img_data = img.get_fdata()
    +>>> a_slice = img_data[:, :, 28]
    +>>> # Need transpose to put first axis left-right, second bottom-top
    +>>> plt.imshow(a_slice.T, cmap="gray", origin="lower")  
    +
    +
    +
    +_images/neuro_radio_conventions-2_00.png +
    +

    (png, hires.png, pdf)

    +
    +
    +
    +_images/neuro_radio_conventions-2_01.png +
    +

    (png, hires.png, pdf)

    +
    +
    +

    This slice does have the voxels from the right of isocenter towards the right +of the screen, neurology style.

    +

    Similarly, an “LAS” alignment of voxel axes to RAS+ axes would result in an +image with the left of the subject towards the right of the screen, as +radiologists like it. “LAS” voxel axes can also be called “radiological” +voxel layout for this reason [1].

    +

    Over time it has become more common for the scanner to generate images with +almost any orientation of the voxel axes relative to the reference axes. +Maybe for this reason, the terms “radiological” and “neurological” are less +commonly used as applied to voxel layout. We nipyers try to avoid the +terms neurological or radiological for voxel layout because they can make it +harder to separate the idea of voxel and reference space axes and the affine +as a mapping between them.

    +

    Footnotes

    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/nibabel-data/README.rst b/nibabel-data/README.rst deleted file mode 100644 index c8fa9f3a92..0000000000 --- a/nibabel-data/README.rst +++ /dev/null @@ -1,17 +0,0 @@ -############ -Nibabel data -############ - -This subdirectory contains data repositories for testing. - -The data repositories should not be included in source or binary -distributions. - -A some point we might remove this directory from the source distribution and -make the data packages available with a more formal data package format. - -For the moment the tests can find this data path by: - -* Using the contents of the ``NIBABEL_DATA_DIR`` environment variable; -* Looking for this ``nibabel-data`` directory in the directory above (closer - to the root directory) the directory containing the ``nibabel`` package. diff --git a/nibabel-data/dcm_qa_xa30 b/nibabel-data/dcm_qa_xa30 deleted file mode 160000 index 89b2509218..0000000000 --- a/nibabel-data/dcm_qa_xa30 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 89b2509218a6dd021c5d40ddaf2a017ac1bacafc diff --git a/nibabel-data/nipy-ecattest b/nibabel-data/nipy-ecattest deleted file mode 160000 index 9a0a592057..0000000000 --- a/nibabel-data/nipy-ecattest +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9a0a592057bc16894c20c77b03ea1ebb5f8ca8f9 diff --git a/nibabel-data/nitest-balls1 b/nibabel-data/nitest-balls1 deleted file mode 160000 index 2cd07d86e2..0000000000 --- a/nibabel-data/nitest-balls1 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2cd07d86e2cc2d3c612d5d4d659daccd7a58f126 diff --git a/nibabel-data/nitest-cifti2 b/nibabel-data/nitest-cifti2 deleted file mode 160000 index 26b7cb95d7..0000000000 --- a/nibabel-data/nitest-cifti2 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 26b7cb95d76066b93fd2ee65121b9bdb7e034713 diff --git a/nibabel-data/nitest-dicom b/nibabel-data/nitest-dicom deleted file mode 160000 index 2246c92726..0000000000 --- a/nibabel-data/nitest-dicom +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2246c9272658693c02810836bdf820c1c6607624 diff --git a/nibabel-data/nitest-freesurfer b/nibabel-data/nitest-freesurfer deleted file mode 160000 index 0d30786570..0000000000 --- a/nibabel-data/nitest-freesurfer +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0d307865704df71c3b2248139714806aad47139d diff --git a/nibabel-data/nitest-minc2 b/nibabel-data/nitest-minc2 deleted file mode 160000 index c835bd43f4..0000000000 --- a/nibabel-data/nitest-minc2 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c835bd43f40069d542f75386551ed0fd2377462a diff --git a/nibabel-data/parrec_oblique b/nibabel-data/parrec_oblique deleted file mode 160000 index 45d4d44e17..0000000000 --- a/nibabel-data/parrec_oblique +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 45d4d44e1783a814cc50990c2a0cca2fd38b245c diff --git a/nibabel/.gitignore b/nibabel/.gitignore deleted file mode 100644 index a89322fea3..0000000000 --- a/nibabel/.gitignore +++ /dev/null @@ -1,16 +0,0 @@ -# Packages # -############ -# it's better to unpack these files and commit the raw source -# git has its own built in compression methods -*.7z -*.bz2 -*.bzip2 -*.dmg -*.gz -*.iso -*.jar -*.rar -*.tar -*.tbz2 -*.tgz -*.zip diff --git a/nibabel/__init__.py b/nibabel/__init__.py deleted file mode 100644 index c389c603fc..0000000000 --- a/nibabel/__init__.py +++ /dev/null @@ -1,182 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## - -import os - -from .info import long_description as __doc__ -from .pkg_info import __version__ - -__doc__ += """ -Quickstart -========== - -:: - - import nibabel as nib - - img1 = nib.load('my_file.nii') - img2 = nib.load('other_file.nii.gz') - img3 = nib.load('spm_file.img') - - data = img1.get_fdata() - affine = img1.affine - - print(img1) - - nib.save(img1, 'my_file_copy.nii.gz') - - new_image = nib.Nifti1Image(data, affine) - nib.save(new_image, 'new_image.nii.gz') - -For more detailed information see the :ref:`manual`. -""" - -# module imports -from . import analyze as ana -from . import ecat, imagestats, mriutils, orientations, streamlines, viewers -from . import nifti1 as ni1 -from . import spm2analyze as spm2 -from . import spm99analyze as spm99 - -# isort: split - -# object imports -from .analyze import AnalyzeHeader, AnalyzeImage -from .arrayproxy import is_proxy -from .cifti2 import Cifti2Header, Cifti2Image -from .fileholders import FileHolder, FileHolderError -from .freesurfer import MGHImage -from .funcs import as_closest_canonical, concat_images, four_to_three, squeeze_image -from .gifti import GiftiImage -from .imageclasses import all_image_classes -from .loadsave import load, save -from .minc1 import Minc1Image -from .minc2 import Minc2Image -from .nifti1 import Nifti1Header, Nifti1Image, Nifti1Pair -from .nifti2 import Nifti2Header, Nifti2Image, Nifti2Pair -from .orientations import ( - OrientationError, - aff2axcodes, - apply_orientation, - flip_axis, - io_orientation, -) -from .spm2analyze import Spm2AnalyzeHeader, Spm2AnalyzeImage -from .spm99analyze import Spm99AnalyzeHeader, Spm99AnalyzeImage - -# isort: split - -from .pkg_info import get_pkg_info as _get_pkg_info - - -def get_info(): - return _get_pkg_info(os.path.dirname(__file__)) - - -def test( - label=None, - verbose=1, - extra_argv=None, - doctests=False, - coverage=False, - raise_warnings=None, - timer=False, -): - """ - Run tests for nibabel using pytest - - The protocol mimics the ``numpy.testing.NoseTester.test()``. - Not all features are currently implemented. - - Parameters - ---------- - label : None - Unused. - verbose: int, optional - Verbosity value for test outputs. Positive values increase verbosity, and - negative values decrease it. Default is 1. - extra_argv : list, optional - List with any extra arguments to pass to pytest. - doctests: bool, optional - If True, run doctests in module. Default is False. - coverage: bool, optional - If True, report coverage of NumPy code. Default is False. - (This requires the - `coverage module `_). - raise_warnings : None - Unused. - timer : False - Unused. - - Returns - ------- - code : ExitCode - Returns the result of running the tests as a ``pytest.ExitCode`` enum - """ - import pytest - - args = [] - - if label is not None: - raise NotImplementedError('Labels cannot be set at present') - - verbose = int(verbose) - if verbose > 0: - args.append('-' + 'v' * verbose) - elif verbose < 0: - args.append('-' + 'q' * -verbose) - - if extra_argv: - args.extend(extra_argv) - if doctests: - args.append('--doctest-modules') - if coverage: - args.extend(['--cov', 'nibabel']) - if raise_warnings is not None: - raise NotImplementedError('Warning filters are not implemented') - if timer: - raise NotImplementedError('Timing is not implemented') - - args.extend(['--pyargs', 'nibabel']) - - return pytest.main(args=args) - - -def bench(label=None, verbose=1, extra_argv=None): - """ - Run benchmarks for nibabel using pytest - - The protocol mimics the ``numpy.testing.NoseTester.bench()``. - Not all features are currently implemented. - - Parameters - ---------- - label : None - Unused. - verbose: int, optional - Verbosity value for test outputs. Positive values increase verbosity, and - negative values decrease it. Default is 1. - extra_argv : list, optional - List with any extra arguments to pass to pytest. - - Returns - ------- - code : ExitCode - Returns the result of running the tests as a ``pytest.ExitCode`` enum - """ - from importlib.resources import as_file, files - - args = [] - if extra_argv is not None: - args.extend(extra_argv) - - config_path = files('nibabel') / 'benchmarks/pytest.benchmark.ini' - with as_file(config_path) as config: - args.extend(['-c', str(config)]) - return test(label, verbose, extra_argv=args) diff --git a/nibabel/_compression.py b/nibabel/_compression.py deleted file mode 100644 index 871be2629f..0000000000 --- a/nibabel/_compression.py +++ /dev/null @@ -1,51 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Constants and types for dealing transparently with compression""" - -from __future__ import annotations - -import bz2 -import gzip -import typing as ty - -from .optpkg import optional_package - -if ty.TYPE_CHECKING: - import io - - import indexed_gzip # type: ignore[import] - import pyzstd - - HAVE_INDEXED_GZIP = True - HAVE_ZSTD = True -else: - indexed_gzip, HAVE_INDEXED_GZIP, _ = optional_package('indexed_gzip') - pyzstd, HAVE_ZSTD, _ = optional_package('pyzstd') - - -# Collections of types for isinstance or exception matching -COMPRESSED_FILE_LIKES: tuple[type[io.IOBase], ...] = ( - bz2.BZ2File, - gzip.GzipFile, -) -COMPRESSION_ERRORS: tuple[type[BaseException], ...] = ( - OSError, # BZ2File - gzip.BadGzipFile, -) - -if HAVE_INDEXED_GZIP: - COMPRESSED_FILE_LIKES += (indexed_gzip.IndexedGzipFile,) - COMPRESSION_ERRORS += (indexed_gzip.ZranError,) - from indexed_gzip import IndexedGzipFile # type: ignore[import-not-found] -else: - IndexedGzipFile = gzip.GzipFile - -if HAVE_ZSTD: - COMPRESSED_FILE_LIKES += (pyzstd.ZstdFile,) - COMPRESSION_ERRORS += (pyzstd.ZstdError,) diff --git a/nibabel/_typing.py b/nibabel/_typing.py deleted file mode 100644 index 8b62031810..0000000000 --- a/nibabel/_typing.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Helpers for typing compatibility across Python versions""" - -import sys - -if sys.version_info < (3, 10): - from typing_extensions import ParamSpec -else: - from typing import ParamSpec - -if sys.version_info < (3, 11): - from typing_extensions import Self -else: - from typing import Self - -if sys.version_info < (3, 13): - from typing_extensions import TypeVar -else: - from typing import TypeVar - - -__all__ = [ - 'ParamSpec', - 'Self', - 'TypeVar', -] diff --git a/nibabel/_version.pyi b/nibabel/_version.pyi deleted file mode 100644 index f3c1fd305e..0000000000 --- a/nibabel/_version.pyi +++ /dev/null @@ -1,4 +0,0 @@ -__version__: str -__version_tuple__: tuple[str, ...] -version: str -version_tuple: tuple[str, ...] diff --git a/nibabel/affines.py b/nibabel/affines.py deleted file mode 100644 index 4b6001dec0..0000000000 --- a/nibabel/affines.py +++ /dev/null @@ -1,378 +0,0 @@ -# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -"""Utility routines for working with points and affine transforms""" - -from functools import reduce - -import numpy as np - - -class AffineError(ValueError): - """Errors in calculating or using affines""" - - # Inherits from ValueError to keep compatibility with ValueError previously - # raised in append_diag - pass - - -def apply_affine(aff, pts, inplace=False): - """Apply affine matrix `aff` to points `pts` - - Returns result of application of `aff` to the *right* of `pts`. The - coordinate dimension of `pts` should be the last. - - For the 3D case, `aff` will be shape (4,4) and `pts` will have final axis - length 3 - maybe it will just be N by 3. The return value is the - transformed points, in this case:: - - res = np.dot(aff[:3,:3], pts.T) + aff[:3,3:4] - transformed_pts = res.T - - This routine is more general than 3D, in that `aff` can have any shape - (N,N), and `pts` can have any shape, as long as the last dimension is for - the coordinates, and is therefore length N-1. - - Parameters - ---------- - aff : (N, N) array-like - Homogeneous affine, for 3D points, will be 4 by 4. Contrary to first - appearance, the affine will be applied on the left of `pts`. - pts : (..., N-1) array-like - Points, where the last dimension contains the coordinates of each - point. For 3D, the last dimension will be length 3. - inplace : bool, optional - If True, attempt to apply the affine directly to ``pts``. - If False, or in-place application fails, a freshly allocated - array will be returned. - - Returns - ------- - transformed_pts : (..., N-1) array - transformed points - - Examples - -------- - >>> aff = np.array([[0,2,0,10],[3,0,0,11],[0,0,4,12],[0,0,0,1]]) - >>> pts = np.array([[1,2,3],[2,3,4],[4,5,6],[6,7,8]]) - >>> apply_affine(aff, pts) #doctest: +ELLIPSIS - array([[14, 14, 24], - [16, 17, 28], - [20, 23, 36], - [24, 29, 44]]...) - - Just to show that in the simple 3D case, it is equivalent to: - - >>> (np.dot(aff[:3,:3], pts.T) + aff[:3,3:4]).T #doctest: +ELLIPSIS - array([[14, 14, 24], - [16, 17, 28], - [20, 23, 36], - [24, 29, 44]]...) - - But `pts` can be a more complicated shape: - - >>> pts = pts.reshape((2,2,3)) - >>> apply_affine(aff, pts) #doctest: +ELLIPSIS - array([[[14, 14, 24], - [16, 17, 28]], - - [[20, 23, 36], - [24, 29, 44]]]...) - """ - aff = np.asarray(aff) - pts = np.asarray(pts) - shape = pts.shape - pts = pts.reshape((-1, shape[-1])) - # rzs == rotations, zooms, shears - rzs = aff[:-1, :-1] - trans = aff[:-1, -1] - - if inplace: - try: - np.dot(pts, rzs.T, out=pts) - except ValueError: - inplace = False - else: - pts += trans[None, :] - if not inplace: - pts = pts @ rzs.T + trans[None, :] - - return pts.reshape(shape) - - -def to_matvec(transform): - """Split a transform into its matrix and vector components - - The transformation must be represented in homogeneous coordinates and is - split into its rotation matrix and translation vector components. - - Parameters - ---------- - transform : array-like - NxM transform matrix in homogeneous coordinates representing an affine - transformation from an (N-1)-dimensional space to an (M-1)-dimensional - space. An example is a 4x4 transform representing rotations and - translations in 3 dimensions. A 4x3 matrix can represent a - 2-dimensional plane embedded in 3 dimensional space. - - Returns - ------- - matrix : (N-1, M-1) array - Matrix component of `transform` - vector : (M-1,) array - Vector component of `transform` - - See Also - -------- - from_matvec - - Examples - -------- - >>> aff = np.diag([2, 3, 4, 1]) - >>> aff[:3,3] = [9, 10, 11] - >>> to_matvec(aff) - (array([[2, 0, 0], - [0, 3, 0], - [0, 0, 4]]), array([ 9, 10, 11])) - """ - transform = np.asarray(transform) - ndimin = transform.shape[0] - 1 - ndimout = transform.shape[1] - 1 - matrix = transform[0:ndimin, 0:ndimout] - vector = transform[0:ndimin, ndimout] - return matrix, vector - - -def from_matvec(matrix, vector=None): - """Combine a matrix and vector into an homogeneous affine - - Combine a rotation / scaling / shearing matrix and translation vector into - a transform in homogeneous coordinates. - - Parameters - ---------- - matrix : array-like - An NxM array representing the the linear part of the transform. - A transform from an M-dimensional space to an N-dimensional space. - vector : None or array-like, optional - None or an (N,) array representing the translation. None corresponds to - an (N,) array of zeros. - - Returns - ------- - xform : array - An (N+1, M+1) homogeneous transform matrix. - - See Also - -------- - to_matvec - - Examples - -------- - >>> from_matvec(np.diag([2, 3, 4]), [9, 10, 11]) - array([[ 2, 0, 0, 9], - [ 0, 3, 0, 10], - [ 0, 0, 4, 11], - [ 0, 0, 0, 1]]) - - The `vector` argument is optional: - - >>> from_matvec(np.diag([2, 3, 4])) - array([[2, 0, 0, 0], - [0, 3, 0, 0], - [0, 0, 4, 0], - [0, 0, 0, 1]]) - """ - matrix = np.asarray(matrix) - nin, nout = matrix.shape - t = np.zeros((nin + 1, nout + 1), matrix.dtype) - t[0:nin, 0:nout] = matrix - t[nin, nout] = 1.0 - if vector is not None: - t[0:nin, nout] = vector - return t - - -def append_diag(aff, steps, starts=()): - """Add diagonal elements `steps` and translations `starts` to affine - - Typical use is in expanding 4x4 affines to larger dimensions. Nipy is the - main consumer because it uses NxM affines, whereas we generally only use - 4x4 affines; the routine is here for convenience. - - Parameters - ---------- - aff : 2D array - N by M affine matrix - steps : scalar or sequence - diagonal elements to append. - starts : scalar or sequence - elements to append to last column of `aff`, representing translations - corresponding to the `steps`. If empty, expands to a vector of zeros - of the same length as `steps` - - Returns - ------- - aff_plus : 2D array - Now P by Q where L = ``len(steps)`` and P == N+L, Q=N+L - - Examples - -------- - >>> aff = np.eye(4) - >>> aff[:3,:3] = np.arange(9).reshape((3,3)) - >>> append_diag(aff, [9, 10], [99,100]) - array([[ 0., 1., 2., 0., 0., 0.], - [ 3., 4., 5., 0., 0., 0.], - [ 6., 7., 8., 0., 0., 0.], - [ 0., 0., 0., 9., 0., 99.], - [ 0., 0., 0., 0., 10., 100.], - [ 0., 0., 0., 0., 0., 1.]]) - """ - aff = np.asarray(aff) - steps = np.atleast_1d(steps) - starts = np.atleast_1d(starts) - n_steps = len(steps) - if len(starts) == 0: - starts = np.zeros(n_steps, dtype=steps.dtype) - elif len(starts) != n_steps: - raise AffineError('Steps should have same length as starts') - old_n_out, old_n_in = aff.shape[0] - 1, aff.shape[1] - 1 - # make new affine - aff_plus = np.zeros((old_n_out + n_steps + 1, old_n_in + n_steps + 1), dtype=aff.dtype) - # Get stuff from old affine - aff_plus[:old_n_out, :old_n_in] = aff[:old_n_out, :old_n_in] - aff_plus[:old_n_out, -1] = aff[:old_n_out, -1] - # Add new diagonal elements - for i, el in enumerate(steps): - aff_plus[old_n_out + i, old_n_in + i] = el - # Add translations for new affine, plus last 1 - aff_plus[old_n_out:, -1] = list(starts) + [1] - return aff_plus - - -def dot_reduce(*args): - r"""Apply numpy dot product function from right to left on arrays - - For passed arrays :math:`A, B, C, ... Z` returns :math:`A \dot B \dot C ... - \dot Z` where "." is the numpy array dot product. - - Parameters - ---------- - \*\*args : arrays - Arrays that can be passed to numpy ``dot`` function - - Returns - ------- - dot_product : array - If there are N arguments, result of ``arg[0].dot(arg[1].dot(arg[2].dot - ... arg[N-2].dot(arg[N-1])))...`` - """ - return reduce(lambda x, y: np.dot(y, x), args[::-1]) - - -def voxel_sizes(affine): - r"""Return voxel size for each input axis given `affine` - - The `affine` is the mapping between array (voxel) coordinates and mm - (world) coordinates. - - The voxel size for the first voxel (array) axis is the distance moved in - world coordinates when moving one unit along the first voxel (array) axis. - This is the distance between the world coordinate of voxel (0, 0, 0) and - the world coordinate of voxel (1, 0, 0). The world coordinate vector of - voxel coordinate vector (0, 0, 0) is given by ``v0 = affine.dot((0, 0, 0, - 1)[:3]``. The world coordinate vector of voxel vector (1, 0, 0) is - ``v1_ax1 = affine.dot((1, 0, 0, 1))[:3]``. The final 1 in the voxel - vectors and the ``[:3]`` at the end are because the affine works on - homogeneous coordinates. The translations part of the affine is ``trans = - affine[:3, 3]``, and the rotations, zooms and shearing part of the affine - is ``rzs = affine[:3, :3]``. Because of the final 1 in the input voxel - vector, ``v0 == rzs.dot((0, 0, 0)) + trans``, and ``v1_ax1 == rzs.dot((1, - 0, 0)) + trans``, and the difference vector is ``rzs.dot((0, 0, 0)) - - rzs.dot((1, 0, 0)) == rzs.dot((1, 0, 0)) == rzs[:, 0]``. The distance - vectors in world coordinates between (0, 0, 0) and (1, 0, 0), (0, 1, 0), - (0, 0, 1) are given by ``rzs.dot(np.eye(3)) = rzs``. The voxel sizes are - the Euclidean lengths of the distance vectors. So, the voxel sizes are - the Euclidean lengths of the columns of the affine (excluding the last row - and column of the affine). - - Parameters - ---------- - affine : 2D array-like - Affine transformation array. Usually shape (4, 4), but can be any 2D - array. - - Returns - ------- - vox_sizes : 1D array - Voxel sizes for each input axis of affine. Usually 1D array length 3, - but in general has length (N-1) where input `affine` is shape (M, N). - """ - top_left = affine[:-1, :-1] - return np.sqrt(np.sum(top_left**2, axis=0)) - - -def obliquity(affine): - r"""Estimate the *obliquity* an affine's axes represent - - The term *obliquity* is defined here as the rotation of those axes with - respect to the cardinal axes. - This implementation is inspired by `AFNI's implementation - `_. - For further details about *obliquity*, check `AFNI's documentation - `_. - - Parameters - ---------- - affine : 2D array-like - Affine transformation array. Usually shape (4, 4), but can be any 2D - array. - - Returns - ------- - angles : 1D array-like - The *obliquity* of each axis with respect to the cardinal axes, in radians. - - """ - vs = voxel_sizes(affine) - best_cosines = np.abs(affine[:-1, :-1] / vs).max(axis=1) - return np.arccos(best_cosines) - - -def rescale_affine(affine, shape, zooms, new_shape=None): - """Return a new affine matrix with updated voxel sizes (zooms) - - This function preserves the rotations and shears of the original - affine, as well as the RAS location of the central voxel of the - image. - - Parameters - ---------- - affine : (N, N) array-like - NxN transform matrix in homogeneous coordinates representing an affine - transformation from an (N-1)-dimensional space to an (N-1)-dimensional - space. An example is a 4x4 transform representing rotations and - translations in 3 dimensions. - shape : (N-1,) array-like - The extent of the (N-1) dimensions of the original space - zooms : (N-1,) array-like - The size of voxels of the output affine - new_shape : (N-1,) array-like, optional - The extent of the (N-1) dimensions of the space described by the - new affine. If ``None``, use ``shape``. - - Returns - ------- - affine : (N, N) array - A new affine transform with the specified voxel sizes - - """ - shape = np.asarray(shape) - new_shape = np.array(new_shape if new_shape is not None else shape) - - s = voxel_sizes(affine) - rzs_out = affine[:3, :3] * zooms / s - - # Using xyz = A @ ijk, determine translation - centroid = apply_affine(affine, (shape - 1) // 2) - t_out = centroid - rzs_out @ ((new_shape - 1) // 2) - return from_matvec(rzs_out, t_out) diff --git a/nibabel/analyze.py b/nibabel/analyze.py deleted file mode 100644 index d02363c792..0000000000 --- a/nibabel/analyze.py +++ /dev/null @@ -1,1070 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Read / write access to the basic Mayo Analyze format - -=========================== - The Analyze header format -=========================== - -This is a binary header format and inherits from ``WrapStruct`` - -Apart from the attributes and methods of WrapStruct: - -Class attributes are:: - - .default_x_flip - -with methods:: - - .get/set_data_shape - .get/set_data_dtype - .get/set_zooms - .get/set_data_offset - .get_base_affine() - .get_best_affine() - .data_to_fileobj - .data_from_fileobj - -and class methods:: - - .from_header(hdr) - -More sophisticated headers can add more methods and attributes. - -Notes ------ - -This - basic - analyze header cannot encode full affines (only -diagonal affines), and cannot do integer scaling. - -The inability to store affines means that we have to guess what orientation the -image has. Most Analyze images are stored on disk in (fastest-changing to -slowest-changing) R->L, P->A and I->S order. That is, the first voxel is the -rightmost, most posterior and most inferior voxel location in the image, and -the next voxel is one voxel towards the left of the image. - -Most people refer to this disk storage format as 'radiological', on the basis -that, if you load up the data as an array ``img_arr`` where the first axis is -the fastest changing, then take a slice in the I->S axis - ``img_arr[:,:,10]`` -- then the right part of the brain will be on the left of your displayed slice. -Radiologists like looking at images where the left of the brain is on the right -side of the image. - -Conversely, if the image has the voxels stored with the left voxels first - -L->R, P->A, I->S, then this would be 'neurological' format. Neurologists like -looking at images where the left side of the brain is on the left of the image. - -When we are guessing at an affine for Analyze, this translates to the problem -of whether the affine should consider proceeding within the data down an X line -as being from left to right, or right to left. - -By default we assume that the image is stored in R->L format. We encode this -choice in the ``default_x_flip`` flag that can be True or False. True means -assume radiological. - -If the image is 3D, and the X, Y and Z zooms are x, y, and z, then:: - - if default_x_flip is True:: - affine = np.diag((-x,y,z,1)) - else: - affine = np.diag((x,y,z,1)) - -In our implementation, there is no way of saving this assumed flip into the -header. One way of doing this, that we have not used, is to allow negative -zooms, in particular, negative X zooms. We did not do this because the image -can be loaded with and without a default flip, so the saved zoom will not -constrain the affine. -""" - -from __future__ import annotations - -import numpy as np - -from .arrayproxy import ArrayProxy -from .arraywriters import ArrayWriter, WriterError, get_slope_inter, make_array_writer -from .batteryrunners import Report -from .fileholders import copy_file_map -from .spatialimages import HeaderDataError, HeaderTypeError, SpatialHeader, SpatialImage -from .volumeutils import ( - apply_read_scaling, - array_from_file, - make_dt_codes, - native_code, - seek_tell, - shape_zoom_affine, - swapped_code, -) -from .wrapstruct import LabeledWrapStruct - -# Sub-parts of standard analyze header from -# Mayo dbh.h file -header_key_dtd = [ - ('sizeof_hdr', 'i4'), - ('data_type', 'S10'), - ('db_name', 'S18'), - ('extents', 'i4'), - ('session_error', 'i2'), - ('regular', 'S1'), - ('hkey_un0', 'S1'), -] -image_dimension_dtd = [ - ('dim', 'i2', (8,)), - ('vox_units', 'S4'), - ('cal_units', 'S8'), - ('unused1', 'i2'), - ('datatype', 'i2'), - ('bitpix', 'i2'), - ('dim_un0', 'i2'), - ('pixdim', 'f4', (8,)), - ('vox_offset', 'f4'), - ('funused1', 'f4'), - ('funused2', 'f4'), - ('funused3', 'f4'), - ('cal_max', 'f4'), - ('cal_min', 'f4'), - ('compressed', 'i4'), - ('verified', 'i4'), - ('glmax', 'i4'), - ('glmin', 'i4'), -] -data_history_dtd: list[tuple[str, str] | tuple[str, str, tuple[int, ...]]] = [ - ('descrip', 'S80'), - ('aux_file', 'S24'), - ('orient', 'S1'), - ('originator', 'S10'), - ('generated', 'S10'), - ('scannum', 'S10'), - ('patient_id', 'S10'), - ('exp_date', 'S10'), - ('exp_time', 'S10'), - ('hist_un0', 'S3'), - ('views', 'i4'), - ('vols_added', 'i4'), - ('start_field', 'i4'), - ('field_skip', 'i4'), - ('omax', 'i4'), - ('omin', 'i4'), - ('smax', 'i4'), - ('smin', 'i4'), -] - -# Full header numpy dtype combined across sub-fields -header_dtype = np.dtype(header_key_dtd + image_dimension_dtd + data_history_dtd) - -_dtdefs = ( # code, conversion function, equivalent dtype, aliases - (0, 'none', np.void), - (1, 'binary', np.void), # 1 bit per voxel, needs thought - (2, 'uint8', np.uint8), - (4, 'int16', np.int16), - (8, 'int32', np.int32), - (16, 'float32', np.float32), - (32, 'complex64', np.complex64), # numpy complex format? - (64, 'float64', np.float64), - (128, 'RGB', np.dtype([('R', 'u1'), ('G', 'u1'), ('B', 'u1')])), - (255, 'all', np.void), -) - -# Make full code alias bank, including dtype column -data_type_codes = make_dt_codes(_dtdefs) - - -class AnalyzeHeader(LabeledWrapStruct, SpatialHeader): - """Class for basic analyze header - - Implements zoom-only setting of affine transform, and no image - scaling - """ - - # Copies of module-level definitions - template_dtype = header_dtype - _data_type_codes = data_type_codes - # fields with recoders for their values - _field_recoders = {'datatype': data_type_codes} - # default x flip - default_x_flip = True - - # data scaling capabilities - has_data_slope = False - has_data_intercept = False - - sizeof_hdr = 348 - - def __init__(self, binaryblock=None, endianness=None, check=True): - """Initialize header from binary data block - - Parameters - ---------- - binaryblock : {None, string} optional - binary block to set into header. By default, None, in - which case we insert the default empty header block - endianness : {None, '<','>', other endian code} string, optional - endianness of the binaryblock. If None, guess endianness - from the data. - check : bool, optional - Whether to check content of header in initialization. - Default is True. - - Examples - -------- - >>> hdr1 = AnalyzeHeader() # an empty header - >>> hdr1.endianness == native_code - True - >>> hdr1.get_data_shape() - (0,) - >>> hdr1.set_data_shape((1,2,3)) # now with some content - >>> hdr1.get_data_shape() - (1, 2, 3) - - We can set the binary block directly via this initialization. - Here we get it from the header we have just made - - >>> binblock2 = hdr1.binaryblock - >>> hdr2 = AnalyzeHeader(binblock2) - >>> hdr2.get_data_shape() - (1, 2, 3) - - Empty headers are native endian by default - - >>> hdr2.endianness == native_code - True - - You can pass valid opposite endian headers with the - ``endianness`` parameter. Even empty headers can have - endianness - - >>> hdr3 = AnalyzeHeader(endianness=swapped_code) - >>> hdr3.endianness == swapped_code - True - - If you do not pass an endianness, and you pass some data, we - will try to guess from the passed data. - - >>> binblock3 = hdr3.binaryblock - >>> hdr4 = AnalyzeHeader(binblock3) - >>> hdr4.endianness == swapped_code - True - """ - super().__init__(binaryblock, endianness, check) - - @classmethod - def guessed_endian(klass, hdr): - """Guess intended endianness from mapping-like ``hdr`` - - Parameters - ---------- - hdr : mapping-like - hdr for which to guess endianness - - Returns - ------- - endianness : {'<', '>'} - Guessed endianness of header - - Examples - -------- - Zeros header, no information, guess native - - >>> hdr = AnalyzeHeader() - >>> hdr_data = np.zeros((), dtype=header_dtype) - >>> AnalyzeHeader.guessed_endian(hdr_data) == native_code - True - - A valid native header is guessed native - - >>> hdr_data = hdr.structarr.copy() - >>> AnalyzeHeader.guessed_endian(hdr_data) == native_code - True - - And, when swapped, is guessed as swapped - - >>> sw_hdr_data = hdr_data.byteswap(swapped_code) - >>> AnalyzeHeader.guessed_endian(sw_hdr_data) == swapped_code - True - - The algorithm is as follows: - - First, look at the first value in the ``dim`` field; this - should be between 0 and 7. If it is between 1 and 7, then - this must be a native endian header. - - >>> hdr_data = np.zeros((), dtype=header_dtype) # blank binary data - >>> hdr_data['dim'][0] = 1 - >>> AnalyzeHeader.guessed_endian(hdr_data) == native_code - True - >>> hdr_data['dim'][0] = 6 - >>> AnalyzeHeader.guessed_endian(hdr_data) == native_code - True - >>> hdr_data['dim'][0] = -1 - >>> AnalyzeHeader.guessed_endian(hdr_data) == swapped_code - True - - If the first ``dim`` value is zeros, we need a tie breaker. - In that case we check the ``sizeof_hdr`` field. This should - be 348. If it looks like the byteswapped value of 348, - assumed swapped. Otherwise assume native. - - >>> hdr_data = np.zeros((), dtype=header_dtype) # blank binary data - >>> AnalyzeHeader.guessed_endian(hdr_data) == native_code - True - >>> hdr_data['sizeof_hdr'] = 1543569408 - >>> AnalyzeHeader.guessed_endian(hdr_data) == swapped_code - True - >>> hdr_data['sizeof_hdr'] = -1 - >>> AnalyzeHeader.guessed_endian(hdr_data) == native_code - True - - This is overridden by the ``dim[0]`` value though: - - >>> hdr_data['sizeof_hdr'] = 1543569408 - >>> hdr_data['dim'][0] = 1 - >>> AnalyzeHeader.guessed_endian(hdr_data) == native_code - True - """ - dim0 = int(hdr['dim'][0]) - if dim0 == 0: - if hdr['sizeof_hdr'].byteswap() == klass.sizeof_hdr: - return swapped_code - return native_code - elif 1 <= dim0 <= 7: - return native_code - return swapped_code - - @classmethod - def default_structarr(klass, endianness=None): - """Return header data for empty header with given endianness""" - hdr_data = super().default_structarr(endianness) - hdr_data['sizeof_hdr'] = klass.sizeof_hdr - hdr_data['dim'] = 1 - hdr_data['dim'][0] = 0 - hdr_data['pixdim'] = 1 - hdr_data['datatype'] = 16 # float32 - hdr_data['bitpix'] = 32 - return hdr_data - - @classmethod - def from_header(klass, header=None, check=True): - """Class method to create header from another header - - Parameters - ---------- - header : ``Header`` instance or mapping - a header of this class, or another class of header for - conversion to this type - check : {True, False} - whether to check header for integrity - - Returns - ------- - hdr : header instance - fresh header instance of our own class - """ - # own type, return copy - if type(header) == klass: - obj = header.copy() - if check: - obj.check_fix() - return obj - # not own type, make fresh header instance - obj = klass(check=check) - if header is None: - return obj - if hasattr(header, 'as_analyze_map'): - # header is convertible from a field mapping - mapping = header.as_analyze_map() - for key in mapping: - try: - obj[key] = mapping[key] - except (ValueError, KeyError): - # the presence of the mapping certifies the fields as being - # of the same meaning as for Analyze types, so we can - # safely discard fields with names not known to this header - # type on the basis they are from the wrong Analyze dialect - pass - # set any fields etc that are specific to this format (overridden by - # sub-classes) - obj._clean_after_mapping() - # Fallback basic conversion always done. - # More specific warning for unsupported datatypes - orig_code = header.get_data_dtype() - try: - obj.set_data_dtype(orig_code) - except HeaderDataError: - raise HeaderDataError( - f'Input header {header.__class__} has datatype ' - f'{header.get_value_label("datatype")} ' - f'but output header {klass} does not support it' - ) - obj.set_data_dtype(header.get_data_dtype()) - obj.set_data_shape(header.get_data_shape()) - obj.set_zooms(header.get_zooms()) - if check: - obj.check_fix() - return obj - - def _clean_after_mapping(self): - """Set format-specific stuff after converting header from mapping - - This routine cleans up Analyze-type headers that have had their fields - set from an Analyze map returned by the ``as_analyze_map`` method. - Nifti 1 / 2, SPM Analyze, Analyze are all Analyze-type headers. - Because this map can set fields that are illegal for particular - subtypes of the Analyze header, this routine cleans these up before the - resulting header is checked and returned. - - For example, a Nifti1 single (``.nii``) header has magic "n+1". - Passing the nifti single header for conversion to a Nifti1Pair header - using the ``as_analyze_map`` method will by default set the header - magic to "n+1", when it should be "ni1" for the pair header. This - method is for that kind of case - so the specific header can set fields - like magic correctly, even though the mapping has given a wrong value. - """ - # All current Nifti etc fields that are present in the Analyze header - # have the same meaning as they do for Analyze. - pass - - def raw_data_from_fileobj(self, fileobj): - """Read unscaled data array from `fileobj` - - Parameters - ---------- - fileobj : file-like - Must be open, and implement ``read`` and ``seek`` methods - - Returns - ------- - arr : ndarray - unscaled data array - """ - dtype = self.get_data_dtype() - shape = self.get_data_shape() - offset = self.get_data_offset() - return array_from_file(shape, dtype, fileobj, offset) - - def data_from_fileobj(self, fileobj): - """Read scaled data array from `fileobj` - - Use this routine to get the scaled image data from an image file - `fileobj`, given a header `self`. "Scaled" means, with any header - scaling factors applied to the raw data in the file. Use - `raw_data_from_fileobj` to get the raw data. - - Parameters - ---------- - fileobj : file-like - Must be open, and implement ``read`` and ``seek`` methods - - Returns - ------- - arr : ndarray - scaled data array - - Notes - ----- - We use the header to get any scale or intercept values to apply to the - data. Raw Analyze files don't have scale factors or intercepts, but - this routine also works with formats based on Analyze, that do have - scaling, such as SPM analyze formats and NIfTI. - """ - # read unscaled data - data = self.raw_data_from_fileobj(fileobj) - # get scalings from header. Value of None means not present in header - slope, inter = self.get_slope_inter() - slope = 1.0 if slope is None else slope - inter = 0.0 if inter is None else inter - # Upcast as necessary for big slopes, intercepts - return apply_read_scaling(data, slope, inter) - - def data_to_fileobj(self, data, fileobj, rescale=True): - """Write `data` to `fileobj`, maybe rescaling data, modifying `self` - - In writing the data, we match the header to the written data, by - setting the header scaling factors, iff `rescale` is True. Thus we - modify `self` in the process of writing the data. - - Parameters - ---------- - data : array-like - data to write; should match header defined shape - fileobj : file-like object - Object with file interface, implementing ``write`` and - ``seek`` - rescale : {True, False}, optional - Whether to try and rescale data to match output dtype specified by - header. If True and scaling needed and header cannot scale, then - raise ``HeaderTypeError``. - - Examples - -------- - >>> from nibabel.analyze import AnalyzeHeader - >>> hdr = AnalyzeHeader() - >>> hdr.set_data_shape((1, 2, 3)) - >>> hdr.set_data_dtype(np.float64) - >>> from io import BytesIO - >>> str_io = BytesIO() - >>> data = np.arange(6).reshape(1,2,3) - >>> hdr.data_to_fileobj(data, str_io) - >>> data.astype(np.float64).tobytes('F') == str_io.getvalue() - True - """ - data = np.asanyarray(data) - shape = self.get_data_shape() - if data.shape != shape: - raise HeaderDataError( - 'Data should be shape ({})'.format(', '.join(str(s) for s in shape)) - ) - out_dtype = self.get_data_dtype() - if rescale: - try: - arr_writer = make_array_writer( - data, out_dtype, self.has_data_slope, self.has_data_intercept - ) - except WriterError as e: - raise HeaderTypeError(str(e)) - else: - arr_writer = ArrayWriter(data, out_dtype, check_scaling=False) - seek_tell(fileobj, self.get_data_offset()) - arr_writer.to_fileobj(fileobj) - self.set_slope_inter(*get_slope_inter(arr_writer)) - - def get_data_dtype(self): - """Get numpy dtype for data - - For examples see ``set_data_dtype`` - """ - code = int(self._structarr['datatype']) - dtype = self._data_type_codes.dtype[code] - return dtype.newbyteorder(self.endianness) - - def set_data_dtype(self, datatype): - """Set numpy dtype for data from code or dtype or type - - Examples - -------- - >>> hdr = AnalyzeHeader() - >>> hdr.set_data_dtype(np.uint8) - >>> hdr.get_data_dtype() - dtype('uint8') - >>> hdr.set_data_dtype(np.dtype(np.uint8)) - >>> hdr.get_data_dtype() - dtype('uint8') - >>> hdr.set_data_dtype('implausible') #doctest: +IGNORE_EXCEPTION_DETAIL - Traceback (most recent call last): - ... - HeaderDataError: data dtype "implausible" not recognized - >>> hdr.set_data_dtype('none') #doctest: +IGNORE_EXCEPTION_DETAIL - Traceback (most recent call last): - ... - HeaderDataError: data dtype "none" known but not supported - >>> hdr.set_data_dtype(np.void) #doctest: +IGNORE_EXCEPTION_DETAIL - Traceback (most recent call last): - ... - HeaderDataError: data dtype "" known but not supported - """ - dt = datatype - if dt not in self._data_type_codes: - try: - dt = np.dtype(dt) - except TypeError: - raise HeaderDataError(f'data dtype "{datatype}" not recognized') - if dt not in self._data_type_codes: - raise HeaderDataError(f'data dtype "{datatype}" not supported') - code = self._data_type_codes[dt] - dtype = self._data_type_codes.dtype[code] - # test for void, being careful of user-defined types - if dtype.type is np.void and not dtype.fields: - raise HeaderDataError(f'data dtype "{datatype}" known but not supported') - self._structarr['datatype'] = code - self._structarr['bitpix'] = dtype.itemsize * 8 - - def get_data_shape(self): - """Get shape of data - - Examples - -------- - >>> hdr = AnalyzeHeader() - >>> hdr.get_data_shape() - (0,) - >>> hdr.set_data_shape((1,2,3)) - >>> hdr.get_data_shape() - (1, 2, 3) - - Expanding number of dimensions gets default zooms - - >>> hdr.get_zooms() - (1.0, 1.0, 1.0) - """ - dims = self._structarr['dim'] - ndims = dims[0] - if ndims == 0: - return (0,) - return tuple(int(d) for d in dims[1 : ndims + 1]) - - def set_data_shape(self, shape): - """Set shape of data - - If ``ndims == len(shape)`` then we set zooms for dimensions higher than - ``ndims`` to 1.0 - - Parameters - ---------- - shape : sequence - sequence of integers specifying data array shape - """ - dims = self._structarr['dim'] - ndims = len(shape) - dims[:] = 1 - dims[0] = ndims - try: - dims[1 : ndims + 1] = shape - except (ValueError, OverflowError): - # numpy 1.4.1 at least generates a ValueError from trying to set a - # python long into an int64 array (dims are int64 for nifti2) - values_fit = False - else: - values_fit = np.all(dims[1 : ndims + 1] == shape) - # Error if we did not succeed setting dimensions - if not values_fit: - raise HeaderDataError(f'shape {shape} does not fit in dim datatype') - self._structarr['pixdim'][ndims + 1 :] = 1.0 - - def get_base_affine(self): - """Get affine from basic (shared) header fields - - Note that we get the translations from the center of the - image. - - Examples - -------- - >>> hdr = AnalyzeHeader() - >>> hdr.set_data_shape((3, 5, 7)) - >>> hdr.set_zooms((3, 2, 1)) - >>> hdr.default_x_flip - True - >>> hdr.get_base_affine() # from center of image - array([[-3., 0., 0., 3.], - [ 0., 2., 0., -4.], - [ 0., 0., 1., -3.], - [ 0., 0., 0., 1.]]) - """ - hdr = self._structarr - dims = hdr['dim'] - ndim = dims[0] - return shape_zoom_affine( - hdr['dim'][1 : ndim + 1], hdr['pixdim'][1 : ndim + 1], self.default_x_flip - ) - - get_best_affine = get_base_affine - - def get_zooms(self): - """Get zooms from header - - Returns - ------- - z : tuple - tuple of header zoom values - - Examples - -------- - >>> hdr = AnalyzeHeader() - >>> hdr.get_zooms() - (1.0,) - >>> hdr.set_data_shape((1,2)) - >>> hdr.get_zooms() - (1.0, 1.0) - >>> hdr.set_zooms((3, 4)) - >>> hdr.get_zooms() - (3.0, 4.0) - """ - hdr = self._structarr - dims = hdr['dim'] - ndim = dims[0] - if ndim == 0: - return (1.0,) - pixdims = hdr['pixdim'] - return tuple(pixdims[1 : ndim + 1]) - - def set_zooms(self, zooms): - """Set zooms into header fields - - See docstring for ``get_zooms`` for examples - """ - hdr = self._structarr - dims = hdr['dim'] - ndim = dims[0] - zooms = np.asarray(zooms) - if len(zooms) != ndim: - raise HeaderDataError(f'Expecting {ndim} zoom values for ndim {ndim}') - if np.any(zooms < 0): - raise HeaderDataError('zooms must be positive') - pixdims = hdr['pixdim'] - pixdims[1 : ndim + 1] = zooms[:] - - def as_analyze_map(self): - """Return header as mapping for conversion to Analyze types - - Collect data from custom header type to fill in fields for Analyze and - derived header types (such as Nifti1 and Nifti2). - - When Analyze types convert another header type to their own type, they - call this this method to check if there are other Analyze / Nifti - fields that the source header would like to set. - - Returns - ------- - analyze_map : mapping - Object that can be used as a mapping thus:: - - for key in analyze_map: - value = analyze_map[key] - - where ``key`` is the name of a field that can be set in an Analyze - header type, such as Nifti1, and ``value`` is a value for the - field. For example, `analyze_map` might be a something like - ``dict(regular='y', slice_duration=0.3)`` where ``regular`` is a - field present in both Analyze and Nifti1, and ``slice_duration`` is - a field restricted to Nifti1 and Nifti2. If a particular Analyze - header type does not recognize the field name, it will throw away - the value without error. See :meth:`Analyze.from_header`. - - Notes - ----- - You can also return a Nifti header with the relevant fields set. - - Your header still needs methods ``get_data_dtype``, ``get_data_shape`` - and ``get_zooms``, for the conversion, and these get called *after* - using the analyze map, so the methods will override values set in the - map. - """ - # In the case of Analyze types, the header is already such a mapping - return self - - def set_data_offset(self, offset): - """Set offset into data file to read data""" - self._structarr['vox_offset'] = offset - - def get_data_offset(self): - """Return offset into data file to read data - - Examples - -------- - >>> hdr = AnalyzeHeader() - >>> hdr.get_data_offset() - 0 - >>> hdr['vox_offset'] = 12 - >>> hdr.get_data_offset() - 12 - """ - return int(self._structarr['vox_offset']) - - def get_slope_inter(self): - """Get scalefactor and intercept - - These are not implemented for basic Analyze - """ - return None, None - - def set_slope_inter(self, slope, inter=None): - """Set slope and / or intercept into header - - Set slope and intercept for image data, such that, if the image - data is ``arr``, then the scaled image data will be ``(arr * - slope) + inter`` - - In this case, for Analyze images, we can't store the slope or the - intercept, so this method only checks that `slope` is None or NaN or - 1.0, and that `inter` is None or NaN or 0. - - Parameters - ---------- - slope : None or float - If float, value must be NaN or 1.0 or we raise a ``HeaderTypeError`` - inter : None or float, optional - If float, value must be 0.0 or we raise a ``HeaderTypeError`` - """ - if (slope in (None, 1) or np.isnan(slope)) and (inter in (None, 0) or np.isnan(inter)): - return - raise HeaderTypeError('Cannot set slope != 1 or intercept != 0 for Analyze headers') - - @classmethod - def _get_checks(klass): - """Return sequence of check functions for this class""" - return (klass._chk_sizeof_hdr, klass._chk_datatype, klass._chk_bitpix, klass._chk_pixdims) - - """ Check functions in format expected by BatteryRunner class """ - - @classmethod - def _chk_sizeof_hdr(klass, hdr, fix=False): - rep = Report(HeaderDataError) - if hdr['sizeof_hdr'] == klass.sizeof_hdr: - return hdr, rep - rep.problem_level = 30 - rep.problem_msg = 'sizeof_hdr should be ' + str(klass.sizeof_hdr) - if fix: - hdr['sizeof_hdr'] = klass.sizeof_hdr - rep.fix_msg = 'set sizeof_hdr to ' + str(klass.sizeof_hdr) - return hdr, rep - - @classmethod - def _chk_datatype(klass, hdr, fix=False): - rep = Report(HeaderDataError) - code = int(hdr['datatype']) - try: - dtype = klass._data_type_codes.dtype[code] - except KeyError: - rep.problem_level = 40 - rep.problem_msg = f'data code {code} not recognized' - else: - if dtype.itemsize == 0: - rep.problem_level = 40 - rep.problem_msg = f'data code {code} not supported' - else: - return hdr, rep - if fix: - rep.fix_msg = 'not attempting fix' - return hdr, rep - - @classmethod - def _chk_bitpix(klass, hdr, fix=False): - rep = Report(HeaderDataError) - code = int(hdr['datatype']) - try: - dt = klass._data_type_codes.dtype[code] - except KeyError: - rep.problem_level = 10 - rep.problem_msg = 'no valid datatype to fix bitpix' - if fix: - rep.fix_msg = 'no way to fix bitpix' - return hdr, rep - bitpix = dt.itemsize * 8 - if bitpix == hdr['bitpix']: - return hdr, rep - rep.problem_level = 10 - rep.problem_msg = 'bitpix does not match datatype' - if fix: - hdr['bitpix'] = bitpix # inplace modification - rep.fix_msg = 'setting bitpix to match datatype' - return hdr, rep - - @staticmethod - def _chk_pixdims(hdr, fix=False): - rep = Report(HeaderDataError) - pixdims = hdr['pixdim'] - spat_dims = pixdims[1:4] - if not np.any(spat_dims <= 0): - return hdr, rep - neg_dims = spat_dims < 0 - zero_dims = spat_dims == 0 - pmsgs = [] - fmsgs = [] - if np.any(zero_dims): - level = 30 - pmsgs.append('pixdim[1,2,3] should be non-zero') - if fix: - spat_dims[zero_dims] = 1 - fmsgs.append('setting 0 dims to 1') - if np.any(neg_dims): - level = 35 - pmsgs.append('pixdim[1,2,3] should be positive') - if fix: - spat_dims = np.abs(spat_dims) - fmsgs.append('setting to abs of pixdim values') - rep.problem_level = level - rep.problem_msg = ' and '.join(pmsgs) - if fix: - pixdims[1:4] = spat_dims - rep.fix_msg = ' and '.join(fmsgs) - return hdr, rep - - @classmethod - def may_contain_header(klass, binaryblock): - if len(binaryblock) < klass.sizeof_hdr: - return False - - hdr_struct = np.ndarray( - shape=(), dtype=header_dtype, buffer=binaryblock[: klass.sizeof_hdr] - ) - bs_hdr_struct = hdr_struct.byteswap() - return 348 in (hdr_struct['sizeof_hdr'], bs_hdr_struct['sizeof_hdr']) - - -class AnalyzeImage(SpatialImage): - """Class for basic Analyze format image""" - - header_class: type[AnalyzeHeader] = AnalyzeHeader - header: AnalyzeHeader - _meta_sniff_len = header_class.sizeof_hdr - 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 - - ImageArrayProxy = ArrayProxy - - def __init__(self, dataobj, affine, header=None, extra=None, file_map=None, dtype=None): - super().__init__(dataobj, affine, header, extra, file_map) - # Reset consumable values - self._header.set_data_offset(0) - self._header.set_slope_inter(None, None) - - if dtype is not None: - self.set_data_dtype(dtype) - - __init__.__doc__ = SpatialImage.__init__.__doc__ - - def get_data_dtype(self): - return self._header.get_data_dtype() - - def set_data_dtype(self, dtype): - self._header.set_data_dtype(dtype) - - @classmethod - def from_file_map(klass, file_map, *, mmap=True, keep_file_open=None): - """Class method to create image from mapping in ``file_map`` - - Parameters - ---------- - file_map : dict - Mapping with (key, value) pairs of (``file_type``, FileHolder - instance giving file-likes for each file needed for this image - type. - mmap : {True, False, 'c', 'r'}, optional, keyword only - `mmap` controls the use of numpy memory mapping for reading image - array data. If False, do not try numpy ``memmap`` for data array. - If one of {'c', 'r'}, try numpy memmap with ``mode=mmap``. A - `mmap` value of True gives the same behavior as ``mmap='c'``. If - image data file cannot be memory-mapped, ignore `mmap` value and - read array from file. - keep_file_open : { None, True, False }, optional, keyword only - `keep_file_open` controls whether a new file handle is created - every time the image is accessed, or a single file handle is - created and used for the lifetime of this ``ArrayProxy``. If - ``True``, a single file handle is created and used. If ``False``, - a new file handle is created every time the image is accessed. - If ``file_map`` refers to an open file handle, this setting has no - effect. The default value (``None``) will result in the value of - ``nibabel.arrayproxy.KEEP_FILE_OPEN_DEFAULT`` being used. - - Returns - ------- - img : AnalyzeImage instance - """ - if mmap not in (True, False, 'c', 'r'): - raise ValueError("mmap should be one of {True, False, 'c', 'r'}") - hdr_fh, img_fh = klass._get_fileholders(file_map) - with hdr_fh.get_prepare_fileobj(mode='rb') as hdrf: - header = klass.header_class.from_fileobj(hdrf) - hdr_copy = header.copy() - imgf = img_fh.fileobj - if imgf is None: - imgf = img_fh.filename - data = klass.ImageArrayProxy(imgf, hdr_copy, mmap=mmap, keep_file_open=keep_file_open) - # Initialize without affine to allow header to pass through unmodified - img = klass(data, None, header, file_map=file_map) - # set affine from header though - img._affine = header.get_best_affine() - img._load_cache = { - 'header': hdr_copy, - 'affine': img._affine.copy(), - 'file_map': copy_file_map(file_map), - } - return img - - @staticmethod - def _get_fileholders(file_map): - """Return fileholder for header and image - - Allows single-file image types to return one fileholder for both types. - For Analyze there are two fileholders, one for the header, one for the - image. - """ - return file_map['header'], file_map['image'] - - def to_file_map(self, file_map=None, dtype=None): - """Write image to `file_map` or contained ``self.file_map`` - - Parameters - ---------- - file_map : None or mapping, optional - files mapping. If None (default) use object's ``file_map`` - attribute instead - dtype : dtype-like, optional - The on-disk data type to coerce the data array. - """ - if file_map is None: - file_map = self.file_map - data = np.asanyarray(self.dataobj) - self.update_header() - hdr = self._header - # Store consumable values for later restore - offset = hdr.get_data_offset() - data_dtype = hdr.get_data_dtype() - # Override dtype conditionally - if dtype is not None: - hdr.set_data_dtype(dtype) - out_dtype = hdr.get_data_dtype() - # Scalars of slope, offset to get immutable values - slope = hdr['scl_slope'].item() if hdr.has_data_slope else np.nan - inter = hdr['scl_inter'].item() if hdr.has_data_intercept else np.nan - # Check whether to calculate slope / inter - scale_me = np.all(np.isnan((slope, inter))) - try: - if scale_me: - arr_writer = make_array_writer( - data, out_dtype, hdr.has_data_slope, hdr.has_data_intercept - ) - else: - arr_writer = ArrayWriter(data, out_dtype, check_scaling=False) - except WriterError: - # Restore any changed consumable values, in case caller catches - # Should match cleanup at the end of the method - hdr.set_data_offset(offset) - hdr.set_data_dtype(data_dtype) - if hdr.has_data_slope: - hdr['scl_slope'] = slope - if hdr.has_data_intercept: - hdr['scl_inter'] = inter - raise - hdr_fh, img_fh = self._get_fileholders(file_map) - # Check if hdr and img refer to same file; this can happen with odd - # analyze images but most often this is because it's a single nifti - # file - hdr_img_same = hdr_fh.same_file_as(img_fh) - hdrf = hdr_fh.get_prepare_fileobj(mode='wb') - if hdr_img_same: - imgf = hdrf - else: - imgf = img_fh.get_prepare_fileobj(mode='wb') - # Rescale values if asked - if scale_me: - hdr.set_slope_inter(*get_slope_inter(arr_writer)) - # Write header - hdr.write_to(hdrf) - # Write image - # Seek to writing position, get there by writing zeros if seek fails - seek_tell(imgf, hdr.get_data_offset(), write0=True) - # Write array data - arr_writer.to_fileobj(imgf) - hdrf.close_if_mine() - if not hdr_img_same: - imgf.close_if_mine() - self._header = hdr - self.file_map = file_map - # Restore any changed consumable values - hdr.set_data_offset(offset) - hdr.set_data_dtype(data_dtype) - if hdr.has_data_slope: - hdr['scl_slope'] = slope - if hdr.has_data_intercept: - hdr['scl_inter'] = inter - - -load = AnalyzeImage.from_filename -save = AnalyzeImage.instance_to_filename diff --git a/nibabel/arrayproxy.py b/nibabel/arrayproxy.py deleted file mode 100644 index 82713f639f..0000000000 --- a/nibabel/arrayproxy.py +++ /dev/null @@ -1,512 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Array proxy base class - -The proxy API is - at minimum: - -* The object has a read-only attribute ``shape`` -* read only ``is_proxy`` attribute / property set to True -* the object returns the data array from ``np.asarray(prox)`` -* returns array slice from ``prox[]`` where ```` is any - ndarray slice specification that does not use numpy 'advanced indexing'. -* modifying no object outside ``obj`` will affect the result of - ``np.asarray(obj)``. Specifically: - - * Changes in position (``obj.tell()``) of passed file-like objects will - not affect the output of from ``np.asarray(proxy)``. - * if you pass a header into the __init__, then modifying the original - header will not affect the result of the array return. - -See :mod:`nibabel.tests.test_proxy_api` for proxy API conformance checks. -""" - -from __future__ import annotations - -import typing as ty -import warnings -from contextlib import contextmanager -from threading import RLock - -import numpy as np - -from . import openers -from .fileslice import canonical_slicers, fileslice -from .volumeutils import apply_read_scaling, array_from_file - -"""This flag controls whether a new file handle is created every time an image -is accessed through an ``ArrayProxy``, or a single file handle is created and -used for the lifetime of the ``ArrayProxy``. It should be set to one of -``True`` or ``False``. - -Management of file handles will be performed either by ``ArrayProxy`` objects, -or by the ``indexed_gzip`` package if it is used. - -If this flag is set to ``True``, a single file handle is created and used. If -``False``, a new file handle is created every time the image is accessed. - -If this is set to any other value, attempts to create an ``ArrayProxy`` without -specifying the ``keep_file_open`` flag will result in a ``ValueError`` being -raised. -""" -KEEP_FILE_OPEN_DEFAULT = False - - -if ty.TYPE_CHECKING: - import numpy.typing as npt - - from ._typing import Self, TypeVar - - # Taken from numpy/__init__.pyi - _DType = TypeVar('_DType', bound=np.dtype[ty.Any]) - - -class ArrayLike(ty.Protocol): - """Protocol for numpy ndarray-like objects - - This is more stringent than :class:`numpy.typing.ArrayLike`, but guarantees - access to shape, ndim and slicing. - """ - - shape: tuple[int, ...] - - @property - 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]]: ... - - # Any dtype might be passed, and *that* dtype must be returned - @ty.overload - def __array__(self, dtype: _DType, /) -> np.ndarray[ty.Any, _DType]: ... - - def __getitem__(self, key, /) -> npt.NDArray: ... - - -class ArrayProxy(ArrayLike): - """Class to act as proxy for the array that can be read from a file - - The array proxy allows us to freeze the passed fileobj and header such that - it returns the expected data array. - - This implementation assumes a contiguous array in the file object, with one - of the numpy dtypes, starting at a given file position ``offset`` with - single ``slope`` and ``intercept`` scaling to produce output values. - - The class ``__init__`` requires a spec which defines how the data will be - read and rescaled. The spec may be a tuple of length 2 - 5, containing the - shape, storage dtype, offset, slope and intercept, or a ``header`` object - with methods: - - * get_data_shape - * get_data_dtype - * get_data_offset - * get_slope_inter - - A header should also have a 'copy' method. This requirement will go away - when the deprecated 'header' property goes away. - - This implementation allows us to deal with Analyze and its variants, - including Nifti1, and with the MGH format. - - Other image types might need more specific classes to implement the API. - See :mod:`nibabel.minc1`, :mod:`nibabel.ecat` and :mod:`nibabel.parrec` for - examples. - """ - - _default_order = 'F' - - def __init__(self, file_like, spec, *, mmap=True, order=None, keep_file_open=None): - """Initialize array proxy instance - - Parameters - ---------- - file_like : object - File-like object or filename. If file-like object, should implement - at least ``read`` and ``seek``. - spec : object or tuple - Tuple must have length 2-5, with the following values: - - #. shape: tuple - tuple of ints describing shape of data; - #. storage_dtype: dtype specifier - dtype of array inside proxied - file, or input to ``numpy.dtype`` to specify array dtype; - #. offset: int - offset, in bytes, of data array from start of file - (default: 0); - #. slope: float - scaling factor for resulting data (default: 1.0); - #. inter: float - intercept for rescaled data (default: 0.0). - - OR - - Header object implementing ``get_data_shape``, ``get_data_dtype``, - ``get_data_offset``, ``get_slope_inter`` - mmap : {True, False, 'c', 'r'}, optional, keyword only - `mmap` controls the use of numpy memory mapping for reading data. - If False, do not try numpy ``memmap`` for data array. If one of - {'c', 'r'}, try numpy memmap with ``mode=mmap``. A `mmap` value of - True gives the same behavior as ``mmap='c'``. If `file_like` - cannot be memory-mapped, ignore `mmap` value and read array from - file. - order : {None, 'F', 'C'}, optional, keyword only - `order` controls the order of the data array layout. Fortran-style, - column-major order may be indicated with 'F', and C-style, row-major - order may be indicated with 'C'. None gives the default order, that - comes from the `_default_order` class variable. - keep_file_open : { None, True, False }, optional, keyword only - `keep_file_open` controls whether a new file handle is created - every time the image is accessed, or a single file handle is - created and used for the lifetime of this ``ArrayProxy``. If - ``True``, a single file handle is created and used. If ``False``, - a new file handle is created every time the image is accessed. - If ``file_like`` is an open file handle, this setting has no - effect. The default value (``None``) will result in the value of - ``KEEP_FILE_OPEN_DEFAULT`` being used. - """ - if mmap not in (True, False, 'c', 'r'): - raise ValueError("mmap should be one of {True, False, 'c', 'r'}") - if order not in (None, 'C', 'F'): - raise ValueError("order should be one of {None, 'C', 'F'}") - self.file_like = file_like - if hasattr(spec, 'get_data_shape'): - slope, inter = spec.get_slope_inter() - par = ( - spec.get_data_shape(), - spec.get_data_dtype(), - spec.get_data_offset(), - 1.0 if slope is None else slope, - 0.0 if inter is None else inter, - ) - elif 2 <= len(spec) <= 5: - optional = (0, 1.0, 0.0) - par = spec + optional[len(spec) - 2 :] - else: - raise TypeError('spec must be tuple of length 2-5 or header object') - - # Warn downstream users that the class variable order is going away - if hasattr(self.__class__, 'order'): - warnings.warn( - f'Class {self.__class__} has an `order` class variable. ' - 'ArrayProxy subclasses should rename this variable to `_default_order` ' - 'to avoid conflict with instance variables.\n' - '* deprecated in version: 5.0\n' - '* will raise error in version: 7.0\n', - DeprecationWarning, - stacklevel=2, - ) - # Override _default_order with order, to follow intent of subclasser - self._default_order = self.order - - # Copies of values needed to read array - self._shape, self._dtype, self._offset, self._slope, self._inter = par - # Permit any specifier that can be interpreted as a numpy dtype - self._dtype = np.dtype(self._dtype) - self._mmap = mmap - if order is None: - order = self._default_order - self.order = order - # Flags to keep track of whether a single ImageOpener is created, and - # whether a single underlying file handle is created. - self._keep_file_open, self._persist_opener = self._should_keep_file_open(keep_file_open) - self._lock = RLock() - - def _has_fh(self) -> bool: - """Determine if our file-like is a filehandle or path""" - return hasattr(self.file_like, 'read') and hasattr(self.file_like, 'seek') - - def copy(self) -> Self: - """Create a new ArrayProxy for the same file and parameters - - If the proxied file is an open file handle, the new ArrayProxy - will share a lock with the old one. - """ - spec = self._shape, self._dtype, self._offset, self._slope, self._inter - new = self.__class__( - self.file_like, - spec, - mmap=self._mmap, - keep_file_open=self._keep_file_open, - ) - if self._has_fh(): - new._lock = self._lock - return new - - def __del__(self): - """If this ``ArrayProxy`` was created with ``keep_file_open=True``, - the open file object is closed if necessary. - """ - if hasattr(self, '_opener') and not self._opener.closed: - self._opener.close_if_mine() - self._opener = None - - def __getstate__(self): - """Returns the state of this ``ArrayProxy`` during pickling.""" - state = self.__dict__.copy() - state.pop('_lock', None) - return state - - def __setstate__(self, state): - """Sets the state of this ``ArrayProxy`` during unpickling.""" - self.__dict__.update(state) - self._lock = RLock() - - def _should_keep_file_open(self, keep_file_open): - """Called by ``__init__``. - - This method determines how to manage ``ImageOpener`` instances, - and the underlying file handles - the behaviour depends on: - - - whether ``self.file_like`` is an an open file handle, or a path to a - ``'.gz'`` file, or a path to a non-gzip file. - - whether ``indexed_gzip`` is present (see - :attr:`.openers.HAVE_INDEXED_GZIP`). - - An ``ArrayProxy`` object uses two internal flags to manage - ``ImageOpener`` instances and underlying file handles. - - - The ``_persist_opener`` flag controls whether a single - ``ImageOpener`` should be created and used for the lifetime of - this ``ArrayProxy``, or whether separate ``ImageOpener`` instances - should be created on each file access. - - - The ``_keep_file_open`` flag controls qwhether the underlying file - handle should be kept open for the lifetime of this - ``ArrayProxy``, or whether the file handle should be (re-)opened - and closed on each file access. - - The internal ``_keep_file_open`` flag is only relevant if - ``self.file_like`` is a ``'.gz'`` file, and the ``indexed_gzip`` library is - present. - - This method returns the values to be used for the internal - ``_persist_opener`` and ``_keep_file_open`` flags; these values are - derived according to the following rules: - - 1. If ``self.file_like`` is a file(-like) object, both flags are set to - ``False``. - - 2. If ``keep_file_open`` (as passed to :meth:``__init__``) is - ``True``, both internal flags are set to ``True``. - - 3. If ``keep_file_open`` is ``False``, but ``self.file_like`` is not a path - to a ``.gz`` file or ``indexed_gzip`` is not present, both flags - are set to ``False``. - - 4. If ``keep_file_open`` is ``False``, ``self.file_like`` is a path to a - ``.gz`` file, and ``indexed_gzip`` is present, ``_persist_opener`` - is set to ``True``, and ``_keep_file_open`` is set to ``False``. - In this case, file handle management is delegated to the - ``indexed_gzip`` library. - - Parameters - ---------- - - keep_file_open : { True, False } - Flag as passed to ``__init__``. - - Returns - ------- - - A tuple containing: - - ``keep_file_open`` flag to control persistence of file handles - - ``persist_opener`` flag to control persistence of ``ImageOpener`` - objects. - """ - if keep_file_open is None: - keep_file_open = KEEP_FILE_OPEN_DEFAULT - if keep_file_open not in (True, False): - raise ValueError( - 'nibabel.arrayproxy.KEEP_FILE_OPEN_DEFAULT ' - f'must be boolean. Found: {keep_file_open}' - ) - elif keep_file_open not in (True, False): - raise ValueError('keep_file_open must be one of {None, True, False}') - - # file_like is a handle - keep_file_open is irrelevant - if self._has_fh(): - return False, False - # if the file is a gzip file, and we have_indexed_gzip, - have_igzip = openers.HAVE_INDEXED_GZIP and self.file_like.endswith('.gz') - - persist_opener = keep_file_open or have_igzip - return keep_file_open, persist_opener - - @property - def shape(self): - return self._shape - - @property - def ndim(self): - return len(self.shape) - - @property - def dtype(self): - return self._dtype - - @property - def offset(self): - return self._offset - - @property - def slope(self): - return self._slope - - @property - def inter(self): - return self._inter - - @property - def is_proxy(self): - return True - - @contextmanager - def _get_fileobj(self): - """Create and return a new ``ImageOpener``, or return an existing one. - - The specific behaviour depends on the value of the ``keep_file_open`` - flag that was passed to ``__init__``. - - Yields - ------ - ImageOpener - A newly created ``ImageOpener`` instance, or an existing one, - which provides access to the file. - """ - if self._persist_opener: - if not hasattr(self, '_opener'): - self._opener = openers.ImageOpener(self.file_like, keep_open=self._keep_file_open) - yield self._opener - else: - with openers.ImageOpener(self.file_like, keep_open=False) as opener: - yield opener - - def _get_unscaled(self, slicer): - if canonical_slicers(slicer, self._shape, False) == canonical_slicers( - (), self._shape, False - ): - with self._get_fileobj() as fileobj, self._lock: - return array_from_file( - self._shape, - self._dtype, - fileobj, - offset=self._offset, - order=self.order, - mmap=self._mmap, - ) - with self._get_fileobj() as fileobj: - return fileslice( - fileobj, - slicer, - self._shape, - self._dtype, - self._offset, - order=self.order, - lock=self._lock, - ) - - def _get_scaled(self, dtype, slicer): - # Ensure scale factors have dtypes - scl_slope = np.asanyarray(self._slope) - scl_inter = np.asanyarray(self._inter) - use_dtype = scl_slope.dtype if dtype is None else dtype - - if np.can_cast(scl_slope, use_dtype): - scl_slope = scl_slope.astype(use_dtype) - if np.can_cast(scl_inter, use_dtype): - scl_inter = scl_inter.astype(use_dtype) - # Read array and upcast as necessary for big slopes, intercepts - scaled = apply_read_scaling(self._get_unscaled(slicer=slicer), scl_slope, scl_inter) - if dtype is not None: - scaled = scaled.astype(np.promote_types(scaled.dtype, dtype), copy=False) - return scaled - - def get_unscaled(self): - """Read data from file - - This is an optional part of the proxy API - """ - return self._get_unscaled(slicer=()) - - def __array__(self, dtype=None): - """Read data from file and apply scaling, casting to ``dtype`` - - If ``dtype`` is unspecified, the dtype of the returned array is the - narrowest dtype that can represent the data without overflow. - Generally, it is the wider of the dtypes of the slopes or intercepts. - - The types of the scale factors will generally be determined by the - parameter size in the image header, and so should be consistent for a - given image format, but may vary across formats. - - Parameters - ---------- - dtype : numpy dtype specifier, optional - A numpy dtype specifier specifying the type of the returned array. - - Returns - ------- - array - Scaled image data with type `dtype`. - """ - arr = self._get_scaled(dtype=dtype, slicer=()) - if dtype is not None: - arr = arr.astype(dtype, copy=False) - return arr - - def __getitem__(self, slicer): - return self._get_scaled(dtype=None, slicer=slicer) - - def reshape(self, shape): - """Return an ArrayProxy with a new shape, without modifying data""" - size = np.prod(self._shape) - - # Calculate new shape if not fully specified - from functools import reduce - from operator import mul - - n_unknowns = len([e for e in shape if e == -1]) - if n_unknowns > 1: - raise ValueError('can only specify one unknown dimension') - elif n_unknowns == 1: - known_size = reduce(mul, shape, -1) - unknown_size = size // known_size - shape = tuple(unknown_size if e == -1 else e for e in shape) - - if np.prod(shape) != size: - raise ValueError(f'cannot reshape array of size {size:d} into shape {shape!s}') - return self.__class__( - file_like=self.file_like, - spec=(shape, self._dtype, self._offset, self._slope, self._inter), - mmap=self._mmap, - ) - - -def is_proxy(obj): - """Return True if `obj` is an array proxy""" - try: - return obj.is_proxy - except AttributeError: - return False - - -def reshape_dataobj(obj, shape): - """Use `obj` reshape method if possible, else numpy reshape function""" - return obj.reshape(shape) if hasattr(obj, 'reshape') else np.reshape(obj, shape) - - -def get_obj_dtype(obj): - """Get the effective dtype of an array-like object""" - if is_proxy(obj): - # Read and potentially apply scaling to one value - idx = (0,) * len(obj.shape) - return obj[idx].dtype - elif hasattr(obj, 'dtype'): - # Trust the dtype (probably an ndarray) - return obj.dtype - else: - # Coerce; this could be expensive but we don't know what we can do with it - return np.asanyarray(obj).dtype diff --git a/nibabel/arraywriters.py b/nibabel/arraywriters.py deleted file mode 100644 index 1f55263fc3..0000000000 --- a/nibabel/arraywriters.py +++ /dev/null @@ -1,763 +0,0 @@ -"""Array writer objects - -Array writers have init signature:: - - def __init__(self, array, out_dtype=None) - -and methods - -* scaling_needed() - returns True if array requires scaling for write -* finite_range() - returns min, max of self.array -* to_fileobj(fileobj, offset=None, order='F') - -They must have attributes / properties of: - -* array -* out_dtype -* has_nan - -They may have attributes: - -* slope -* inter - -They are designed to write arrays to a fileobj with reasonable memory -efficiency. - -Array writers may be able to scale the array or apply an intercept, or do -something else to make sense of conversions between float and int, or between -larger ints and smaller. -""" - -import numpy as np - -from .casting import best_float, floor_exact, int_abs, shared_range, type_info -from .volumeutils import array_to_file, finite_range - - -class WriterError(Exception): - pass - - -class ScalingError(WriterError): - pass - - -class ArrayWriter: - def __init__(self, array, out_dtype=None, **kwargs): - r"""Initialize array writer - - Parameters - ---------- - array : array-like - array-like object - out_dtype : None or dtype - dtype with which `array` will be written. For this class, - `out_dtype`` needs to be the same as the dtype of the input `array` - or a swapped version of the same. - \*\*kwargs : keyword arguments - This class processes only: - - * nan2zero : bool, optional - Whether to set NaN values to 0 when writing integer output. - Defaults to True. If False, NaNs get converted with numpy - ``astype``, and the behavior is undefined. Ignored for floating - point output. - * check_scaling : bool, optional - If True, check if scaling needed and raise error if so. Default - is True - - Examples - -------- - >>> arr = np.array([0, 255], np.uint8) - >>> aw = ArrayWriter(arr) - >>> aw = ArrayWriter(arr, np.int8) #doctest: +IGNORE_EXCEPTION_DETAIL - Traceback (most recent call last): - ... - WriterError: Scaling needed but cannot scale - >>> aw = ArrayWriter(arr, np.int8, check_scaling=False) - """ - nan2zero = kwargs.pop('nan2zero', True) - check_scaling = kwargs.pop('check_scaling', True) - self._array = np.asanyarray(array) - arr_dtype = self._array.dtype - if out_dtype is None: - out_dtype = arr_dtype - else: - out_dtype = np.dtype(out_dtype) - self._out_dtype = out_dtype - self._finite_range = None - self._has_nan = None - self._nan2zero = nan2zero - if check_scaling and self.scaling_needed(): - raise WriterError('Scaling needed but cannot scale') - - def scaling_needed(self): - """Checks if scaling is needed for input array - - Raises WriterError if no scaling possible. - - The rules are in the code, but: - - * If numpy will cast, return False (no scaling needed) - * If input or output is an object or structured type, raise - * If input is complex, raise - * If the output is float, return False - * If the input array is all zero, return False - * By now we are casting to (u)int. If the input type is a float, return - True (we do need scaling) - * Now input and output types are (u)ints. If the min and max in the - data are within range of the output type, return False - * Otherwise return True - """ - data = self._array - arr_dtype = data.dtype - out_dtype = self._out_dtype - # There's a bug in np.can_cast (at least up to and including 1.6.1) - # such that any structured output type passes. Check for this first. - if 'V' in (arr_dtype.kind, out_dtype.kind): - if arr_dtype == out_dtype: - return False - raise WriterError('Cannot cast to or from non-numeric types') - if np.can_cast(arr_dtype, out_dtype): - return False - # Direct casting for complex output from any numeric type - if out_dtype.kind == 'c': - return False - if arr_dtype.kind == 'c': - raise WriterError('Cannot cast complex types to non-complex') - # Direct casting for float output from any non-complex numeric type - if out_dtype.kind == 'f': - return False - # Now we need to look at the data for special cases - if data.size == 0: - return False - mn, mx = self.finite_range() # this is cached - if (mn, mx) == (0, 0): - # Data all zero - return False - # Floats -> (u)ints always need scaling - if arr_dtype.kind == 'f': - return True - # (u)int input, (u)int output - assert arr_dtype.kind in 'iu' and out_dtype.kind in 'iu' - info = np.iinfo(out_dtype) - # No scaling needed if data already fits in output type - # But note - we need to convert to ints, to avoid conversion to float - # during comparisons, and therefore int -> float conversions which are - # not exact. Only a problem for uint64 though. - if int(mn) >= int(info.min) and int(mx) <= int(info.max): - return False - return True - - @property - def array(self): - """Return array from arraywriter""" - return self._array - - @property - def out_dtype(self): - """Return `out_dtype` from arraywriter""" - return self._out_dtype - - @property - def has_nan(self): - """True if array has NaNs""" - # Structured types raise an error for finite range; don't run finite - # range unless we have to. - if self._has_nan is None: - if self._array.dtype.kind in 'fc': - self.finite_range() - else: - self._has_nan = False - return self._has_nan - - def finite_range(self): - """Return (maybe cached) finite range of data array""" - if self._finite_range is None: - mn, mx, has_nan = finite_range(self._array, True) - self._finite_range = (mn, mx) - self._has_nan = has_nan - return self._finite_range - - def _needs_nan2zero(self): - """True if nan2zero check needed for writing array""" - return ( - self._nan2zero - and self._array.dtype.kind in 'fc' - and self.out_dtype.kind in 'iu' - and self.has_nan - ) - - def to_fileobj(self, fileobj, order='F'): - """Write array into `fileobj` - - Parameters - ---------- - fileobj : file-like object - order : {'F', 'C'} - order (Fortran or C) to which to write array - """ - array_to_file( - self._array, - fileobj, - self._out_dtype, - offset=None, - mn=None, - mx=None, - order=order, - nan2zero=self._needs_nan2zero(), - ) - - -class SlopeArrayWriter(ArrayWriter): - """ArrayWriter that can use scalefactor for writing arrays - - The scalefactor allows the array writer to write floats to int output - types, and rescale larger ints to smaller. It can therefore lose - precision. - - It extends the ArrayWriter class with attribute: - - * slope - - and methods: - - * reset() - reset slope to default (not adapted to self.array) - * calc_scale() - calculate slope to best write self.array - """ - - def __init__(self, array, out_dtype=None, calc_scale=True, scaler_dtype=np.float32, **kwargs): - r"""Initialize array writer - - Parameters - ---------- - array : array-like - array-like object - out_dtype : None or dtype - dtype with which `array` will be written. For this class, - `out_dtype`` needs to be the same as the dtype of the input `array` - or a swapped version of the same. - calc_scale : {True, False}, optional - Whether to calculate scaling for writing `array` on initialization. - If False, then you can calculate this scaling with - ``obj.calc_scale()`` - see examples - scaler_dtype : dtype-like, optional - specifier for numpy dtype for scaling - \*\*kwargs : keyword arguments - This class processes only: - - * nan2zero : bool, optional - Whether to set NaN values to 0 when writing integer output. - Defaults to True. If False, NaNs get converted with numpy - ``astype``, and the behavior is undefined. Ignored for floating - point output. - - Examples - -------- - >>> arr = np.array([0, 254], np.uint8) - >>> aw = SlopeArrayWriter(arr) - >>> aw.slope - 1.0 - >>> aw = SlopeArrayWriter(arr, np.int8) - >>> aw.slope - 2.0 - >>> aw = SlopeArrayWriter(arr, np.int8, calc_scale=False) - >>> aw.slope - 1.0 - >>> aw.calc_scale() - >>> aw.slope - 2.0 - """ - nan2zero = kwargs.pop('nan2zero', True) - self._array = np.asanyarray(array) - arr_dtype = self._array.dtype - if out_dtype is None: - out_dtype = arr_dtype - else: - out_dtype = np.dtype(out_dtype) - self._out_dtype = out_dtype - self.scaler_dtype = np.dtype(scaler_dtype) - self.reset() - self._nan2zero = nan2zero - self._has_nan = None - if calc_scale: - self.calc_scale() - - def scaling_needed(self): - """Checks if scaling is needed for input array - - Raises WriterError if no scaling possible. - - The rules are in the code, but: - - * If numpy will cast, return False (no scaling needed) - * If input or output is an object or structured type, raise - * If input is complex, raise - * If the output is float, return False - * If the input array is all zero, return False - * If there is no finite value, return False (the writer will strip the - non-finite values) - * By now we are casting to (u)int. If the input type is a float, return - True (we do need scaling) - * Now input and output types are (u)ints. If the min and max in the - data are within range of the output type, return False - * Otherwise return True - """ - if not super().scaling_needed(): - return False - mn, mx = self.finite_range() # this is cached - # No finite data - no scaling needed - return (mn, mx) != (np.inf, -np.inf) - - def reset(self): - """Set object to values before any scaling calculation""" - self.slope = 1.0 - self._finite_range = None - self._scale_calced = False - - def _get_slope(self): - return self._slope - - def _set_slope(self, val): - self._slope = np.squeeze(self.scaler_dtype.type(val)) - - slope = property(_get_slope, _set_slope, None, 'get/set slope') - - def calc_scale(self, force=False): - """Calculate / set scaling for floats/(u)ints to (u)ints""" - # If we've run already, return unless told otherwise - if not force and self._scale_calced: - return - self.reset() - if not self.scaling_needed(): - return - self._do_scaling() - self._scale_calced = True - - def _writing_range(self): - """Finite range for thresholding on write""" - if self._out_dtype.kind in 'iu' and self._array.dtype.kind == 'f': - mn, mx = self.finite_range() - if (mn, mx) == (np.inf, -np.inf): # no finite data - mn, mx = 0, 0 - return mn, mx - return None, None - - def to_fileobj(self, fileobj, order='F'): - """Write array into `fileobj` - - Parameters - ---------- - fileobj : file-like object - order : {'F', 'C'} - order (Fortran or C) to which to write array - """ - mn, mx = self._writing_range() - array_to_file( - self._array, - fileobj, - self._out_dtype, - offset=None, - divslope=self.slope, - mn=mn, - mx=mx, - order=order, - nan2zero=self._needs_nan2zero(), - ) - - def _do_scaling(self): - arr = self._array - out_dtype = self._out_dtype - assert out_dtype.kind in 'iu' - mn, mx = self.finite_range() - if arr.dtype.kind == 'f': - # Float to (u)int scaling - # Need to take nan2zero value into account for scaling - if self._nan2zero and self.has_nan: - mn = min(mn, 0) - mx = max(mx, 0) - self._range_scale(mn, mx) - return - # (u)int to (u)int - info = np.iinfo(out_dtype) - out_max, out_min = info.max, info.min - # If left as int64, uint64, comparisons will default to floats, and - # these are inexact for > 2**53 - so convert to int - if int(mx) <= int(out_max) and int(mn) >= int(out_min): - # already in range - return - # (u)int to (u)int scaling - self._iu2iu() - - def _iu2iu(self): - # (u)int to (u)int scaling - mn, mx = self.finite_range() - out_dt = self._out_dtype - if out_dt.kind == 'u': - # We're checking for a sign flip. This can only work for uint - # output, because, for int output, the abs min of the type is - # greater than the abs max, so the data either fits into the range - # (tested for in _do_scaling), or this test can't pass. Need abs - # that deals with max neg ints. abs problem only arises when all - # the data is set to max neg integer value - o_min, o_max = shared_range(self.scaler_dtype, out_dt) - if mx <= 0 and int_abs(mn) <= int(o_max): # sign flip enough? - # -1.0 * arr will be in scaler_dtype precision - self.slope = -1.0 - return - self._range_scale(mn, mx) - - def _range_scale(self, in_min, in_max): - """Calculate scaling based on data range and output type""" - out_dtype = self._out_dtype - info = type_info(out_dtype) - out_min, out_max = info['min'], info['max'] - big_float = best_float() - if out_dtype.kind == 'f': - # But we want maximum precision for the calculations. Casting will - # not lose precision because min/max are of fp type. - out_min, out_max = np.array((out_min, out_max), dtype=big_float) - else: # (u)int - out_min, out_max = (big_float(v) for v in (out_min, out_max)) - if self._out_dtype.kind == 'u': - if in_min < 0 and in_max > 0: - raise WriterError( - 'Cannot scale negative and positive numbers to uint without intercept' - ) - if in_max <= 0: # All input numbers <= 0 - self.slope = in_min / out_max - else: # All input numbers > 0 - self.slope = in_max / out_max - return - # Scaling to int. We need the bigger slope of (in_min/out_min) and - # (in_max/out_max). If in_min or in_max is the wrong side of 0, that - # will make these negative and so they won't worry us - mx_slope = in_max / out_max - mn_slope = in_min / out_min - self.slope = np.max([mx_slope, mn_slope]) - - -class SlopeInterArrayWriter(SlopeArrayWriter): - """Array writer that can use slope and intercept to scale array - - The writer can subtract an intercept, and divided by a slope, in order to - be able to convert floating point values into a (u)int range, or to convert - larger (u)ints to smaller. - - It extends the ArrayWriter class with attributes: - - * inter - * slope - - and methods: - - * reset() - reset inter, slope to default (not adapted to self.array) - * calc_scale() - calculate inter, slope to best write self.array - """ - - def __init__(self, array, out_dtype=None, calc_scale=True, scaler_dtype=np.float32, **kwargs): - r"""Initialize array writer - - Parameters - ---------- - array : array-like - array-like object - out_dtype : None or dtype - dtype with which `array` will be written. For this class, - `out_dtype`` needs to be the same as the dtype of the input `array` - or a swapped version of the same. - calc_scale : {True, False}, optional - Whether to calculate scaling for writing `array` on initialization. - If False, then you can calculate this scaling with - ``obj.calc_scale()`` - see examples - scaler_dtype : dtype-like, optional - specifier for numpy dtype for slope, intercept - \*\*kwargs : keyword arguments - This class processes only: - - * nan2zero : bool, optional - Whether to set NaN values to 0 when writing integer output. - Defaults to True. If False, NaNs get converted with numpy - ``astype``, and the behavior is undefined. Ignored for floating - point output. - - Examples - -------- - >>> arr = np.array([0, 255], np.uint8) - >>> aw = SlopeInterArrayWriter(arr) - >>> aw.slope, aw.inter - (1.0, 0.0) - >>> aw = SlopeInterArrayWriter(arr, np.int8) - >>> (aw.slope, aw.inter) == (1.0, 128) - True - >>> aw = SlopeInterArrayWriter(arr, np.int8, calc_scale=False) - >>> aw.slope, aw.inter - (1.0, 0.0) - >>> aw.calc_scale() - >>> (aw.slope, aw.inter) == (1.0, 128) - True - """ - super().__init__(array, out_dtype, calc_scale, scaler_dtype, **kwargs) - - def reset(self): - """Set object to values before any scaling calculation""" - super().reset() - self.inter = 0.0 - - def _get_inter(self): - return self._inter - - def _set_inter(self, val): - self._inter = np.squeeze(self.scaler_dtype.type(val)) - - inter = property(_get_inter, _set_inter, None, 'get/set inter') - - def to_fileobj(self, fileobj, order='F'): - """Write array into `fileobj` - - Parameters - ---------- - fileobj : file-like object - order : {'F', 'C'} - order (Fortran or C) to which to write array - """ - mn, mx = self._writing_range() - array_to_file( - self._array, - fileobj, - self._out_dtype, - offset=None, - intercept=self.inter, - divslope=self.slope, - mn=mn, - mx=mx, - order=order, - nan2zero=self._needs_nan2zero(), - ) - - def _iu2iu(self): - # (u)int to (u)int - mn, mx = (int(v) for v in self.finite_range()) - # range may be greater than the largest integer for this type. - out_dtype = self._out_dtype - # Options in this method are scaling using intercept only. These will - # have to pass through ``self.scaler_dtype`` (because the intercept is - # in this type). - o_min, o_max = (int(v) for v in shared_range(self.scaler_dtype, out_dtype)) - type_range = o_max - o_min - mn2mx = mx - mn - if mn2mx <= type_range: # might offset be enough? - if o_min == 0: # uint output - take min to 0 - # decrease offset with floor_exact, meaning mn >= t_min after - # subtraction. But we may have pushed the data over t_max, - # which we check below - inter = floor_exact(mn - o_min, self.scaler_dtype) - else: # int output - take midpoint to 0 - # ceil below increases inter, pushing scale up to 0.5 towards - # -inf, because ints have abs min == abs max + 1 - midpoint = mn + int(np.ceil(mn2mx / 2.0)) - # Floor exact decreases inter, so pulling scaled values more - # positive. This may make mx - inter > t_max - inter = floor_exact(midpoint, self.scaler_dtype) - # Need to check still in range after floor_exact-ing - int_inter = int(inter) - assert mn - int_inter >= o_min - if mx - int_inter <= o_max: - self.inter = inter - return - # Try slope options (sign flip) and then range scaling - super()._iu2iu() - - def _range_scale(self, in_min, in_max): - """Calculate scaling, intercept based on data range and output type""" - if in_max == in_min: # Only one number in array - self.slope = 1.0 - self.inter = in_min - return - big_float = best_float() - in_dtype = self._array.dtype - out_dtype = self._out_dtype - working_dtype = self.scaler_dtype - if in_dtype.kind == 'f': # Already floats - # float64 and below cast correctly to longdouble. Longdouble needs - # no casting - in_min, in_max = np.array([in_min, in_max], dtype=big_float) - in_range = np.diff([in_min, in_max]) - else: # max possible (u)int range is 2**64-1 (int64, uint64) - # On windows longdouble is the same as double so in_range will be 2**64 - - # thus overestimating slope slightly. Casting to int needed to allow - # in_max-in_min to be larger than the largest (u)int value - in_min, in_max = int(in_min), int(in_max) - in_range = big_float(in_max - in_min) - # Cast to float for later processing. - in_min, in_max = (big_float(v) for v in (in_min, in_max)) - if out_dtype.kind == 'f': - # Type range, these are also floats - info = type_info(out_dtype) - out_min, out_max = info['min'], info['max'] - else: - # Use shared range to avoid rounding to values outside range. This - # doesn't matter much except for the case of nan2zero were we need - # to be able to represent the scaled zero correctly in order not to - # raise an error when writing - out_min, out_max = shared_range(working_dtype, out_dtype) - out_min, out_max = np.array((out_min, out_max), dtype=big_float) - # We want maximum precision for the calculations. Casting will not lose - # precision because min/max are of fp type. - assert [v.dtype.kind for v in (out_min, out_max)] == ['f', 'f'] - out_range = out_max - out_min - """ - Think of the input values as a line starting (left) at in_min and - ending (right) at in_max. - - The output values will be a line starting at out_min and ending at - out_max. - - We are going to match the input line to the output line by subtracting - `inter` then dividing by `slope`. - - Slope must scale the input line to have the same length as the output - line. We find this scale factor by dividing the input range (line - length) by the output range (line length) - """ - slope = in_range / out_range - """ - Now we know the slope, we need the intercept. The intercept will be - such that: - - (in_min - inter) / slope = out_min - - Solving for the intercept: - - inter = in_min - out_min * slope - - We can also flip the sign of the slope. In that case we match the - in_max to the out_min: - - (in_max - inter_flipped) / -slope = out_min - inter_flipped = in_max + out_min * slope - - When we reconstruct the data, we're going to do: - - data = saved_data * slope + inter - - We can't change the range of the saved data (the whole range of the - integer type) or the range of the output data (the values we input). We - can change the intermediate values ``saved_data * slope`` by choosing - the sign of the slope to match the in_min or in_max to the left or - right end of the saved data range. - - If the out_dtype is signed int, then abs(out_min) = abs(out_max) + 1 - and the absolute value and therefore precision for values at the left - and right of the saved data range are very similar (e.g. -128 * slope, - 127 * slope respectively). - - If the out_dtype is unsigned int, then the absolute value at the left - is 0 and the precision is much higher than for the right end of the - range (e.g. 0 * slope, 255 * slope). - - If the out_dtype is unsigned int then we choose the sign of the slope - to match the smaller of the in_min, in_max to the zero end of the saved - range. - """ - if out_min == 0 and np.abs(in_max) < np.abs(in_min): - inter = in_max + out_min * slope - slope *= -1 - else: - inter = in_min - out_min * slope - # slope, inter properties force scaling_dtype cast - self.inter = inter - self.slope = slope - if not np.all(np.isfinite([self.slope, self.inter])): - raise ScalingError('Slope / inter not both finite') - # Check nan fill value - if not (0 in (in_min, in_max) and self._nan2zero and self.has_nan): - return - nan_fill_f = -self.inter / self.slope - nan_fill_i = np.rint(nan_fill_f) - if nan_fill_i == np.array(nan_fill_i, dtype=out_dtype): - return - # recalculate intercept using dtype of inter, scale - self.inter = -np.clip(nan_fill_f, out_min, out_max) * self.slope - nan_fill_i = np.rint(-self.inter / self.slope) - assert nan_fill_i == np.array(nan_fill_i, dtype=out_dtype) - - -def get_slope_inter(writer): - """Return slope, intercept from array writer object - - Parameters - ---------- - writer : ArrayWriter instance - - Returns - ------- - slope : scalar - slope in `writer` or 1.0 if not present - inter : scalar - intercept in `writer` or 0.0 if not present - - Examples - -------- - >>> arr = np.arange(10) - >>> get_slope_inter(ArrayWriter(arr)) - (1.0, 0.0) - >>> get_slope_inter(SlopeArrayWriter(arr)) - (1.0, 0.0) - >>> get_slope_inter(SlopeInterArrayWriter(arr)) - (1.0, 0.0) - """ - try: - slope = writer.slope - except AttributeError: - slope = 1.0 - try: - inter = writer.inter - except AttributeError: - inter = 0.0 - return slope, inter - - -def make_array_writer(data, out_type, has_slope=True, has_intercept=True, **kwargs): - r"""Make array writer instance for array `data` and output type `out_type` - - Parameters - ---------- - data : array-like - array for which to create array writer - out_type : dtype-like - input to numpy dtype to specify array writer output type - has_slope : {True, False} - If True, array write can use scaling to adapt the array to `out_type` - has_intercept : {True, False} - If True, array write can use intercept to adapt the array to `out_type` - \*\*kwargs : other keyword arguments - to pass to the arraywriter class - - Returns - ------- - writer : arraywriter instance - Instance of array writer, with class adapted to `has_intercept` and - `has_slope`. - - Examples - -------- - >>> aw = make_array_writer(np.arange(10), np.uint8, True, True) - >>> type(aw) == SlopeInterArrayWriter - True - >>> aw = make_array_writer(np.arange(10), np.uint8, True, False) - >>> type(aw) == SlopeArrayWriter - True - >>> aw = make_array_writer(np.arange(10), np.uint8, False, False) - >>> type(aw) == ArrayWriter - True - """ - data = np.asarray(data) - if has_intercept and not has_slope: - raise ValueError('Cannot handle intercept without slope') - if has_intercept: - return SlopeInterArrayWriter(data, out_type, **kwargs) - if has_slope: - return SlopeArrayWriter(data, out_type, **kwargs) - return ArrayWriter(data, out_type, **kwargs) diff --git a/nibabel/batteryrunners.py b/nibabel/batteryrunners.py deleted file mode 100644 index 860b9b993c..0000000000 --- a/nibabel/batteryrunners.py +++ /dev/null @@ -1,291 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Battery runner classes and Report classes - -These classes / objects are for generic checking / fixing batteries - -The ``BatteryRunner`` class will run a series of checks on a single -object. - -A check is a callable, of signature ``func(obj, fix=False)`` which -returns a tuple ``(obj, Report)`` for ``func(obj, False)`` or -``func(obj, True)``, where the obj may be a modified object, or a -different object, if ``fix==True``. - -To run checks only, and return problem report objects: - ->>> from nibabel.batteryrunners import BatteryRunner, Report ->>> def chk(obj, fix=False): # minimal check -... return obj, Report() ->>> btrun = BatteryRunner((chk,)) ->>> reports = btrun.check_only('a string') - -To run checks and fixes, returning fixed object and problem report -sequence, with possible fix messages: - ->>> fixed_obj, report_seq = btrun.check_fix('a string') - -Reports are iterable things, where the elements in the iterations are -``Problems``, with attributes ``error``, ``problem_level``, -``problem_msg``, and possibly empty ``fix_msg``. The ``problem_level`` -is an integer, giving the level of problem, from 0 (no problem) to 50 -(very bad problem). The levels follow the log levels from the logging -module (e.g 40 equivalent to "error" level, 50 to "critical"). The -``error`` can be one of ``None`` if no error to suggest, or an Exception -class that the user might consider raising for this situation. The -``problem_msg`` and ``fix_msg`` are human readable strings that should -explain what happened. - -======================= - More about ``checks`` -======================= - -Checks are callables returning objects and reports, like ``chk`` below, -such that:: - - obj, report = chk(obj, fix=False) - obj, report = chk(obj, fix=True) - -For example, for the Analyze header, we need to check the datatype:: - - def chk_datatype(hdr, fix=True): - rep = Report(hdr, HeaderDataError) - code = int(hdr['datatype']) - try: - dtype = AnalyzeHeader._data_type_codes.dtype[code] - except KeyError: - rep.problem_level = 40 - rep.problem_msg = 'data code not recognized' - else: - if dtype.type is np.void: - rep.problem_level = 40 - rep.problem_msg = 'data code not supported' - else: - return hdr, rep - if fix: - rep.fix_problem_msg = 'not attempting fix' - return hdr, rep - -or the bitpix:: - - def chk_bitpix(hdr, fix=True): - rep = Report(HeaderDataError) - code = int(hdr['datatype']) - try: - dt = AnalyzeHeader._data_type_codes.dtype[code] - except KeyError: - rep.problem_level = 10 - rep.problem_msg = 'no valid datatype to fix bitpix' - return hdr, rep - bitpix = dt.itemsize * 8 - if bitpix == hdr['bitpix']: - return hdr, rep - rep.problem_level = 10 - rep.problem_msg = 'bitpix does not match datatype') - if fix: - hdr['bitpix'] = bitpix # inplace modification - rep.fix_msg = 'setting bitpix to match datatype' - return hdr, ret - -or the pixdims:: - - def chk_pixdims(hdr, fix=True): - rep = Report(hdr, HeaderDataError) - if not np.any(hdr['pixdim'][1:4] < 0): - return hdr, rep - rep.problem_level = 40 - rep.problem_msg = 'pixdim[1,2,3] should be positive' - if fix: - hdr['pixdim'][1:4] = np.abs(hdr['pixdim'][1:4]) - rep.fix_msg = 'setting to abs of pixdim values' - return hdr, rep -""" - - -class BatteryRunner: - """Class to run set of checks""" - - def __init__(self, checks): - """Initialize instance from sequence of `checks` - - Parameters - ---------- - checks : sequence - sequence of checks, where checks are callables matching - signature ``obj, rep = chk(obj, fix=False)``. Checks are run - in the order they are passed. - - Examples - -------- - >>> def chk(obj, fix=False): # minimal check - ... return obj, Report() - >>> btrun = BatteryRunner((chk,)) - """ - self._checks = checks - - def check_only(self, obj): - """Run checks on `obj` returning reports - - Parameters - ---------- - obj : anything - object on which to run checks - - Returns - ------- - reports : sequence - sequence of report objects reporting on result of running - checks (without fixes) on `obj` - """ - reports = [] - for check in self._checks: - obj, rep = check(obj, False) - reports.append(rep) - return reports - - def check_fix(self, obj): - """Run checks, with fixes, on `obj` returning `obj`, reports - - Parameters - ---------- - obj : anything - object on which to run checks, fixes - - Returns - ------- - obj : anything - possibly modified or replaced `obj`, after fixes - reports : sequence - sequence of reports on checks, fixes - """ - reports = [] - for check in self._checks: - obj, report = check(obj, True) - reports.append(report) - return obj, reports - - def __len__(self): - return len(self._checks) - - -class Report: - def __init__(self, error=Exception, problem_level=0, problem_msg='', fix_msg=''): - """Initialize report with values - - Parameters - ---------- - error : None or Exception - Error to raise if raising error for this check. If None, - no error can be raised for this check (it was probably - normal). - problem_level : int - level of problem. From 0 (no problem) to 50 (severe - problem). If the report originates from a fix, then this - is the level of the problem remaining after the fix. - Default is 0 - problem_msg : string - String describing problem detected. Default is '' - fix_msg : string - String describing any fix applied. Default is ''. - - Examples - -------- - >>> rep = Report() - >>> rep.problem_level - 0 - >>> rep = Report(TypeError, 10) - >>> rep.problem_level - 10 - """ - self.error = error - self.problem_level = problem_level - self.problem_msg = problem_msg - self.fix_msg = fix_msg - - def __getstate__(self): - """State that defines object - - Returns - ------- - tup : tuple - """ - return self.error, self.problem_level, self.problem_msg, self.fix_msg - - def __eq__(self, other): - """are two BatteryRunner-like objects equal? - - Parameters - ---------- - other : object - report-like object to test equality - - Examples - -------- - >>> rep = Report(problem_level=10) - >>> rep2 = Report(problem_level=10) - >>> rep == rep2 - True - >>> rep3 = Report(problem_level=20) - >>> rep == rep3 - False - """ - return self.__getstate__() == other.__getstate__() - - def __ne__(self, other): - """are two BatteryRunner-like objects not equal? - - See docstring for __eq__ - """ - return not self == other - - def __str__(self): - """Printable string for object""" - return self.__dict__.__str__() - - @property - def message(self): - """formatted message string, including fix message if present""" - if self.fix_msg: - return f'{self.problem_msg}; {self.fix_msg}' - return self.problem_msg - - def log_raise(self, logger, error_level=40): - """Log problem, raise error if problem >= `error_level` - - Parameters - ---------- - logger : log - log object, implementing ``log`` method - error_level : int, optional - If ``self.problem_level`` >= `error_level`, raise error - """ - logger.log(self.problem_level, self.message) - if self.problem_level and self.problem_level >= error_level: - if self.error: - raise self.error(self.problem_msg) - - def write_raise(self, stream, error_level=40, log_level=30): - """Write report to `stream` - - Parameters - ---------- - stream : file-like - implementing ``write`` method - error_level : int, optional - level at which to raise error for problem detected in - ``self`` - log_level : int, optional - Such that if `log_level` is >= ``self.problem_level`` we - write the report to `stream`, otherwise we write nothing. - """ - if self.problem_level >= log_level: - stream.write(f'Level {self.problem_level}: {self.message}\n') - if self.problem_level and self.problem_level >= error_level: - if self.error: - raise self.error(self.problem_msg) diff --git a/nibabel/benchmarks/__init__.py b/nibabel/benchmarks/__init__.py deleted file mode 100644 index 6ab2ff009a..0000000000 --- a/nibabel/benchmarks/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Benchmarks for nibabel diff --git a/nibabel/benchmarks/bench_array_to_file.py b/nibabel/benchmarks/bench_array_to_file.py deleted file mode 100644 index a77ae6cbc9..0000000000 --- a/nibabel/benchmarks/bench_array_to_file.py +++ /dev/null @@ -1,53 +0,0 @@ -"""Benchmarks for array_to_file routine - -Run benchmarks with:: - - import nibabel as nib - nib.bench() - -Run this benchmark with:: - - pytest -c /benchmarks/pytest.benchmark.ini /benchmarks/bench_array_to_file.py -""" - -import sys -from io import BytesIO # noqa: F401 - -import numpy as np -from numpy.testing import measure - -from nibabel.volumeutils import array_to_file # noqa: F401 - -from .butils import print_git_title - - -def bench_array_to_file(): - rng = np.random.RandomState(20111001) - repeat = 10 - img_shape = (128, 128, 64, 10) - arr = rng.normal(size=img_shape) - sys.stdout.flush() - print_git_title('\nArray to file') - mtime = measure('array_to_file(arr, BytesIO(), np.float32)', repeat) - fmt = '{:30s} {:6.2f}'.format - print(fmt('Save float64 to float32', mtime)) - mtime = measure('array_to_file(arr, BytesIO(), np.int16)', repeat) - print(fmt('Save float64 to int16', mtime)) - # Set a lot of NaNs to check timing - arr[:, :, :, 1] = np.nan - mtime = measure('array_to_file(arr, BytesIO(), np.float32)', repeat) - print(fmt('Save float64 to float32, NaNs', mtime)) - mtime = measure('array_to_file(arr, BytesIO(), np.int16)', repeat) - print(fmt('Save float64 to int16, NaNs', mtime)) - # Set a lot of infs to check timing - arr[:, :, :, 1] = np.inf - mtime = measure('array_to_file(arr, BytesIO(), np.float32)', repeat) - print(fmt('Save float64 to float32, infs', mtime)) - mtime = measure('array_to_file(arr, BytesIO(), np.int16)', repeat) - print(fmt('Save float64 to int16, infs', mtime)) - # Int16 input, float output - arr = np.random.random_integers(low=-1000, high=1000, size=img_shape) - arr = arr.astype(np.int16) - mtime = measure('array_to_file(arr, BytesIO(), np.float32)', repeat) - print(fmt('Save Int16 to float32', mtime)) - sys.stdout.flush() diff --git a/nibabel/benchmarks/bench_arrayproxy_slicing.py b/nibabel/benchmarks/bench_arrayproxy_slicing.py deleted file mode 100644 index 5da6c578f7..0000000000 --- a/nibabel/benchmarks/bench_arrayproxy_slicing.py +++ /dev/null @@ -1,181 +0,0 @@ -"""Benchmarks for ArrayProxy slicing of gzipped and non-gzipped files - -Run benchmarks with:: - - import nibabel as nib - nib.bench() - -Run this benchmark with:: - - pytest -c /benchmarks/pytest.benchmark.ini /benchmarks/bench_arrayproxy_slicing.py -""" - -import gc -import itertools as it -from timeit import timeit -from unittest import mock - -import numpy as np - -import nibabel as nib -from nibabel.openers import HAVE_INDEXED_GZIP -from nibabel.tmpdirs import InTemporaryDirectory - -from ..rstutils import rst_table -from .butils import print_git_title - -# if memory_profiler is installed, we get memory usage results -try: - from memory_profiler import memory_usage # type: ignore[import] -except ImportError: - memory_usage = None - - -# Each test involves loading an image of shape SHAPE, and then slicing it -# NITERS times -NITERS = 50 -SHAPE = (100, 100, 100, 100) - -# One test is run for each combination of SLICEOBJS, KEEP_OPENS, and HAVE_IGZIP - -# ':' gets replaced with slice(None) -# '?' gets replaced with a random index into the relevant axis -# numbers (assumed to be between 0 and 1) get scaled to the axis shape -SLICEOBJS = [ - ('?', ':', ':', ':'), - (':', ':', ':', '?'), - ('?', '?', '?', ':'), -] - -KEEP_OPENS = [False, True] - -if HAVE_INDEXED_GZIP: - HAVE_IGZIP = [False, True] -else: - HAVE_IGZIP = [False] - - -def bench_arrayproxy_slicing(): - print_git_title('\nArrayProxy gzip slicing') - - # each test is a tuple containing - # (HAVE_INDEXED_GZIP, keep_file_open, sliceobj) - tests = list(it.product(HAVE_IGZIP, KEEP_OPENS, SLICEOBJS)) - - # remove tests where HAVE_INDEXED_GZIP is True and keep_file_open is False, - # because if keep_file_open is False, HAVE_INDEXED_GZIP has no effect - tests = [t for t in tests if not (t[0] and not t[1])] - - testfile = 'testfile.nii' - testfilegz = 'test.nii.gz' - - def get_test_label(test): - have_igzip = test[0] - keep_open = test[1] - - if not (have_igzip and keep_open): - return 'gzip' - else: - return 'indexed_gzip' - - def fix_sliceobj(sliceobj): - new_sliceobj = [] - for i, s in enumerate(sliceobj): - if s == ':': - new_sliceobj.append(slice(None)) - elif s == '?': - new_sliceobj.append(np.random.randint(0, SHAPE[i])) - else: - new_sliceobj.append(int(s * SHAPE[i])) - return tuple(new_sliceobj) - - def fmt_sliceobj(sliceobj): - slcstr = [] - for i, s in enumerate(sliceobj): - if s in ':?': - slcstr.append(s) - else: - slcstr.append(str(int(s * SHAPE[i]))) - return f'[{", ".join(slcstr)}]' - - with InTemporaryDirectory(): - print(f'Generating test data... ({round(np.prod(SHAPE) * 4 / 1048576.0)} MB)') - - data = np.array(np.random.random(SHAPE), dtype=np.float32) - - # zero out 10% of voxels so gzip has something to compress - mask = np.random.random(SHAPE[:3]) > 0.1 - if len(SHAPE) > 3: - data[mask, :] = 0 - else: - data[mask] = 0 - - # save uncompressed and compressed versions of the image - img = nib.nifti1.Nifti1Image(data, np.eye(4)) - nib.save(img, testfilegz) - nib.save(img, testfile) - - # each result is a tuple containing - # (label, keep_open, sliceobj, testtime, basetime, testmem, basemem) - # - # where "basetime" is the time taken to load and slice a memmapped - # (uncompressed)image, and "basemem" is memory usage for the same - results = [] - - # We use the same random seed for each slice object, - seeds = [np.random.randint(0, 2**32) for s in SLICEOBJS] - - for ti, test in enumerate(tests): - label = get_test_label(test) - have_igzip, keep_open, sliceobj = test - seed = seeds[SLICEOBJS.index(sliceobj)] - - print(f'Running test {ti + 1} of {len(tests)} ({label})...') - - # load uncompressed and compressed versions of the image - img = nib.load(testfile, keep_file_open=keep_open) - - with mock.patch('nibabel.openers.HAVE_INDEXED_GZIP', have_igzip): - imggz = nib.load(testfilegz, keep_file_open=keep_open) - - def basefunc(): - img.dataobj[fix_sliceobj(sliceobj)] - - def testfunc(): - with mock.patch('nibabel.openers.HAVE_INDEXED_GZIP', have_igzip): - imggz.dataobj[fix_sliceobj(sliceobj)] - - # make sure nothing is floating around from the previous test - # iteration, so memory profiling is (hopefully) more accurate - gc.collect() - - if memory_usage is not None: - membaseline = max(memory_usage(lambda: None)) - testmem = max(memory_usage(testfunc)) - membaseline - basemem = max(memory_usage(basefunc)) - membaseline - else: - testmem = np.nan - basemem = np.nan - - # reset the random number generator, so test and baseline use the - # same slices - np.random.seed(seed) - testtime = float(timeit(testfunc, number=NITERS)) / float(NITERS) - np.random.seed(seed) - basetime = float(timeit(basefunc, number=NITERS)) / float(NITERS) - - results.append((label, keep_open, sliceobj, testtime, basetime, testmem, basemem)) - - data = np.zeros((len(results), 4)) - data[:, 0] = [r[3] for r in results] - data[:, 1] = [r[4] for r in results] - try: - data[:, 2] = [r[3] / r[4] for r in results] - except ZeroDivisionError: - data[:, 2] = np.nan - data[:, 3] = [r[5] - r[6] for r in results] - - rowlbls = [f'Type {r[0]}, keep_open {r[1]}, slice {fmt_sliceobj(r[2])}' for r in results] - collbls = ['Time', 'Baseline time', 'Time ratio', 'Memory deviation'] - - print(rst_table(data, rowlbls, collbls)) diff --git a/nibabel/benchmarks/bench_fileslice.py b/nibabel/benchmarks/bench_fileslice.py deleted file mode 100644 index cc3d837c2d..0000000000 --- a/nibabel/benchmarks/bench_fileslice.py +++ /dev/null @@ -1,93 +0,0 @@ -"""Benchmarks for fileslicing - - import nibabel as nib - nib.bench() - -Run this benchmark with:: - - pytest -c /benchmarks/pytest.benchmark.ini /benchmarks/bench_fileslice.py -""" - -import sys -from io import BytesIO -from timeit import timeit - -import numpy as np - -from ..fileslice import fileslice -from ..openers import ImageOpener -from ..optpkg import optional_package -from ..rstutils import rst_table -from ..tmpdirs import InTemporaryDirectory - -SHAPE = (64, 64, 32, 100) -ROW_NAMES = [f'axis {i}, len {dim}' for i, dim in enumerate(SHAPE)] -COL_NAMES = ['mid int', 'step 1', 'half step 1', 'step mid int'] -HAVE_ZSTD = optional_package('pyzstd')[1] - - -def _slices_for_len(L): - # Example slices for a dimension of length L - return (L // 2, slice(None, None, 1), slice(None, L // 2, 1), slice(None, None, L // 2)) - - -def run_slices(file_like, repeat=3, offset=0, order='F'): - arr = np.arange(np.prod(SHAPE)).reshape(SHAPE) - n_dim = len(SHAPE) - n_slicers = len(_slices_for_len(1)) - times_arr = np.zeros((n_dim, n_slicers)) - with ImageOpener(file_like, 'wb') as fobj: - fobj.write(b'\0' * offset) - fobj.write(arr.tobytes(order=order)) - with ImageOpener(file_like, 'rb') as fobj: - for i, L in enumerate(SHAPE): - for j, slicer in enumerate(_slices_for_len(L)): - sliceobj = [slice(None)] * n_dim - sliceobj[i] = slicer - - def f(): - fileslice(fobj, tuple(sliceobj), arr.shape, arr.dtype, offset, order) - - times_arr[i, j] = timeit(f, number=repeat) - - def g(): - fobj.seek(offset) - data = fobj.read() - np.ndarray(SHAPE, arr.dtype, buffer=data, order=order) - - base_time = timeit(g, number=repeat) - return times_arr, base_time - - -def bench_fileslice(bytes=True, file_=True, gz=True, bz2=False, zst=True): - sys.stdout.flush() - repeat = 2 - - def my_table(title, times, base): - print() - print(rst_table(times, ROW_NAMES, COL_NAMES, title, val_fmt='{0[0]:3.2f} ({0[1]:3.2f})')) - print(f'Base time: {base:3.2f}') - - if bytes: - fobj = BytesIO() - times, base = run_slices(fobj, repeat) - my_table('Bytes slice - raw (ratio)', np.dstack((times, times / base)), base) - if file_: - with InTemporaryDirectory(): - file_times, file_base = run_slices('data.bin', repeat) - my_table( - 'File slice - raw (ratio)', np.dstack((file_times, file_times / file_base)), file_base - ) - if gz: - with InTemporaryDirectory(): - gz_times, gz_base = run_slices('data.gz', repeat) - my_table('gz slice - raw (ratio)', np.dstack((gz_times, gz_times / gz_base)), gz_base) - if bz2: - with InTemporaryDirectory(): - bz2_times, bz2_base = run_slices('data.bz2', repeat) - my_table('bz2 slice - raw (ratio)', np.dstack((bz2_times, bz2_times / bz2_base)), bz2_base) - if zst and HAVE_ZSTD: - with InTemporaryDirectory(): - zst_times, zst_base = run_slices('data.zst', repeat) - my_table('zst slice - raw (ratio)', np.dstack((zst_times, zst_times / zst_base)), zst_base) - sys.stdout.flush() diff --git a/nibabel/benchmarks/bench_finite_range.py b/nibabel/benchmarks/bench_finite_range.py deleted file mode 100644 index a4f80f20cb..0000000000 --- a/nibabel/benchmarks/bench_finite_range.py +++ /dev/null @@ -1,44 +0,0 @@ -"""Benchmarks for finite_range routine - -Run benchmarks with:: - - import nibabel as nib - nib.bench() - -Run this benchmark with:: - - pytest -c /benchmarks/pytest.benchmark.ini /benchmarks/bench_finite_range.py -""" - -import sys - -import numpy as np -from numpy.testing import measure - -from nibabel.volumeutils import finite_range # noqa: F401 - -from .butils import print_git_title - - -def bench_finite_range(): - rng = np.random.RandomState(20111001) - repeat = 10 - img_shape = (128, 128, 64, 10) - arr = rng.normal(size=img_shape) - sys.stdout.flush() - print_git_title('\nFinite range') - mtime = measure('finite_range(arr)', repeat) - fmt = '{:30s} {:6.2f}'.format - print(fmt('float64 all finite', mtime)) - arr[:, :, :, 1] = np.nan - mtime = measure('finite_range(arr)', repeat) - print(fmt('float64 many NaNs', mtime)) - arr[:, :, :, 1] = np.inf - mtime = measure('finite_range(arr)', repeat) - print(fmt('float64 many infs', mtime)) - # Int16 input, float output - arr = np.random.random_integers(low=-1000, high=1000, size=img_shape) - arr = arr.astype(np.int16) - mtime = measure('finite_range(arr)', repeat) - print(fmt('int16', mtime)) - sys.stdout.flush() diff --git a/nibabel/benchmarks/bench_load_save.py b/nibabel/benchmarks/bench_load_save.py deleted file mode 100644 index b881c286fb..0000000000 --- a/nibabel/benchmarks/bench_load_save.py +++ /dev/null @@ -1,62 +0,0 @@ -"""Benchmarks for load and save of image arrays - -Run benchmarks with:: - - import nibabel as nib - nib.bench() - -Run this benchmark with:: - - pytest -c /benchmarks/pytest.benchmark.ini /benchmarks/bench_load_save.py -""" - -import sys -from io import BytesIO - -import numpy as np -from numpy.testing import measure - -from .. import Nifti1Image -from .butils import print_git_title - - -def bench_load_save(): - rng = np.random.RandomState(20111001) - repeat = 10 - img_shape = (128, 128, 64, 10) - arr = rng.normal(size=img_shape) - img = Nifti1Image(arr, np.eye(4)) - sio = BytesIO() - img.file_map['image'].fileobj = sio - hdr = img.header - sys.stdout.flush() - print() - print_git_title('Image load save') - hdr.set_data_dtype(np.float32) - mtime = measure('sio.truncate(0); img.to_file_map()', repeat) - fmt = '{:30s} {:6.2f}'.format - print(fmt('Save float64 to float32', mtime)) - mtime = measure('img.from_file_map(img.file_map)', repeat) - print(fmt('Load from float32', mtime)) - hdr.set_data_dtype(np.int16) - mtime = measure('sio.truncate(0); img.to_file_map()', repeat) - print(fmt('Save float64 to int16', mtime)) - mtime = measure('img.from_file_map(img.file_map)', repeat) - print(fmt('Load from int16', mtime)) - # Set a lot of NaNs to check timing - arr[:, :, :20] = np.nan - mtime = measure('sio.truncate(0); img.to_file_map()', repeat) - print(fmt('Save float64 to int16, NaNs', mtime)) - mtime = measure('img.from_file_map(img.file_map)', repeat) - print(fmt('Load from int16, NaNs', mtime)) - # Int16 input, float output - arr = np.random.random_integers(low=-1000, high=1000, size=img_shape) - arr = arr.astype(np.int16) - img = Nifti1Image(arr, np.eye(4)) - sio = BytesIO() - img.file_map['image'].fileobj = sio - hdr = img.header - hdr.set_data_dtype(np.float32) - mtime = measure('sio.truncate(0); img.to_file_map()', repeat) - print(fmt('Save Int16 to float32', mtime)) - sys.stdout.flush() diff --git a/nibabel/benchmarks/butils.py b/nibabel/benchmarks/butils.py deleted file mode 100644 index 6231629030..0000000000 --- a/nibabel/benchmarks/butils.py +++ /dev/null @@ -1,10 +0,0 @@ -"""Benchmarking utilities""" - -from .. import get_info - - -def print_git_title(title): - """Prints title string with git hash if possible, and underline""" - title = f'{title} for git revision {get_info()["commit_hash"]}' - print(title) - print('-' * len(title)) diff --git a/nibabel/benchmarks/pytest.benchmark.ini b/nibabel/benchmarks/pytest.benchmark.ini deleted file mode 100644 index 734e6c7d4c..0000000000 --- a/nibabel/benchmarks/pytest.benchmark.ini +++ /dev/null @@ -1,4 +0,0 @@ -[pytest] -python_files = bench_*.py -python_functions = bench_* -addopts = --capture=no diff --git a/nibabel/brikhead.py b/nibabel/brikhead.py deleted file mode 100644 index cd791adac1..0000000000 --- a/nibabel/brikhead.py +++ /dev/null @@ -1,568 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Class for reading AFNI BRIK/HEAD datasets - -See https://afni.nimh.nih.gov/pub/dist/doc/program_help/README.attributes.html -for information on what is required to have a valid BRIK/HEAD dataset. - -Unless otherwise noted, descriptions AFNI attributes in the code refer to this -document. - -Notes ------ - -In the AFNI HEAD file, the first two values of the attribute DATASET_RANK -determine the shape of the data array stored in the corresponding BRIK file. -The first value, DATASET_RANK[0], must be set to 3 denoting a 3D image. The -second value, DATASET_RANK[1], determines how many "sub-bricks" (in AFNI -parlance) / volumes there are along the fourth (traditionally, but not -exclusively) time axis. Thus, DATASET_RANK[1] will (at least as far as I (RM) -am aware) always be >= 1. This permits sub-brick indexing common in AFNI -programs (e.g., example4d+orig'[0]'). -""" - -import os -import re -from copy import deepcopy - -import numpy as np - -from .arrayproxy import ArrayProxy -from .fileslice import strided_scalar -from .spatialimages import HeaderDataError, ImageDataError, SpatialHeader, SpatialImage -from .volumeutils import Recoder - -# used for doc-tests -filepath = os.path.dirname(os.path.realpath(__file__)) -datadir = os.path.realpath(os.path.join(filepath, 'tests/data')) - -_attr_dic = {'string': str, 'integer': int, 'float': float} - -_endian_dict = { - 'LSB_FIRST': '<', - 'MSB_FIRST': '>', -} - -_dtype_dict = { - 0: 'B', - 1: 'h', - 3: 'f', - 5: 'D', -} - -space_codes = Recoder( - ( - (0, 'unknown', ''), - (1, 'scanner', 'ORIG'), - (3, 'talairach', 'TLRC'), - (4, 'mni', 'MNI'), - ), - fields=('code', 'label', 'space'), -) - - -class AFNIImageError(ImageDataError): - """Error when reading AFNI BRIK files""" - - -class AFNIHeaderError(HeaderDataError): - """Error when reading AFNI HEAD file""" - - -DATA_OFFSET = 0 -TYPE_RE = re.compile(r'type\s*=\s*(string|integer|float)-attribute\s*\n') -NAME_RE = re.compile(r'name\s*=\s*(\w+)\s*\n') - - -def _unpack_var(var): - """ - Parses key : value pair from `var` - - Parameters - ---------- - var : str - Entry from HEAD file - - Returns - ------- - name : str - Name of attribute - value : object - Value of attribute - - Examples - -------- - >>> var = "type = integer-attribute\\nname = BRICK_TYPES\\ncount = 1\\n1\\n" - >>> name, attr = _unpack_var(var) - >>> print(name, attr) - BRICK_TYPES 1 - >>> var = "type = string-attribute\\nname = TEMPLATE_SPACE\\ncount = 5\\n'ORIG~" - >>> name, attr = _unpack_var(var) - >>> print(name, attr) - TEMPLATE_SPACE ORIG - """ - - err_msg = f'Please check HEAD file to ensure it is AFNI compliant. Offending attribute:\n{var}' - atype, aname = TYPE_RE.findall(var), NAME_RE.findall(var) - if len(atype) != 1: - raise AFNIHeaderError(f'Invalid attribute type entry in HEAD file. {err_msg}') - if len(aname) != 1: - raise AFNIHeaderError(f'Invalid attribute name entry in HEAD file. {err_msg}') - atype = _attr_dic.get(atype[0], str) - attr = ' '.join(var.strip().splitlines()[3:]) - if atype is not str: - try: - attr = [atype(f) for f in attr.split()] - except ValueError: - raise AFNIHeaderError( - f'Failed to read variable from HEAD file due to improper type casting. {err_msg}' - ) - else: - # AFNI string attributes will always start with open single quote and - # end with a tilde (NUL). These attributes CANNOT contain tildes (so - # stripping is safe), but can contain single quotes (so we replace) - attr = attr.replace("'", '', 1).rstrip('~') - - return aname[0], attr[0] if len(attr) == 1 else attr - - -def _get_datatype(info): - """ - Gets datatype of BRIK file associated with HEAD file yielding `info` - - Parameters - ---------- - info : dict - As obtained by :func:`parse_AFNI_header` - - Returns - ------- - dt : np.dtype - Datatype of BRIK file associated with HEAD - - Notes - ----- - ``BYTEORDER_STRING`` may be absent, signifying platform native byte order, - or contain one of "LSB_FIRST" or "MSB_FIRST". - - ``BRICK_TYPES`` gives the storage data type for each sub-brick, with - 0=uint, 1=int16, 3=float32, 5=complex64 (see ``_dtype_dict``). This should - generally be the same value for each sub-brick in the dataset. - """ - bo = info['BYTEORDER_STRING'] - bt = info['BRICK_TYPES'] - if isinstance(bt, list): - if np.unique(bt).size > 1: - raise AFNIImageError("Can't load file with multiple data types.") - bt = bt[0] - bo = _endian_dict.get(bo, '=') - bt = _dtype_dict.get(bt, None) - if bt is None: - raise AFNIImageError("Can't deduce image data type.") - return np.dtype(bo + bt) - - -def parse_AFNI_header(fobj): - """ - Parses `fobj` to extract information from HEAD file - - Parameters - ---------- - fobj : file-like object - AFNI HEAD file object or filename. If file object, should - implement at least ``read`` - - Returns - ------- - info : dict - Dictionary containing AFNI-style key:value pairs from HEAD file - - Examples - -------- - >>> fname = os.path.join(datadir, 'example4d+orig.HEAD') - >>> info = parse_AFNI_header(fname) - >>> print(info['BYTEORDER_STRING']) - LSB_FIRST - >>> print(info['BRICK_TYPES']) - [1, 1, 1] - """ - # edge case for being fed a filename instead of a file object - if isinstance(fobj, str): - with open(fobj) as src: - return parse_AFNI_header(src) - # unpack variables in HEAD file - head = fobj.read().split('\n\n') - return dict(map(_unpack_var, head)) - - -class AFNIArrayProxy(ArrayProxy): - """Proxy object for AFNI image array. - - Attributes - ---------- - scaling : np.ndarray - Scaling factor (one factor per volume/sub-brick) for data. Default is - None - """ - - def __init__(self, file_like, header, *, mmap=True, keep_file_open=None): - """ - Initialize AFNI array proxy - - Parameters - ---------- - file_like : file-like object - File-like object or filename. If file-like object, should implement - at least ``read`` and ``seek``. - header : ``AFNIHeader`` object - mmap : {True, False, 'c', 'r'}, optional, keyword only - `mmap` controls the use of numpy memory mapping for reading data. - If False, do not try numpy ``memmap`` for data array. If one of - {'c', 'r'}, try numpy memmap with ``mode=mmap``. A `mmap` value of - True gives the same behavior as ``mmap='c'``. If `file_like` - cannot be memory-mapped, ignore `mmap` value and read array from - file. - keep_file_open : { None, True, False }, optional, keyword only - `keep_file_open` controls whether a new file handle is created - every time the image is accessed, or a single file handle is - created and used for the lifetime of this ``ArrayProxy``. If - ``True``, a single file handle is created and used. If ``False``, - a new file handle is created every time the image is accessed. - If ``file_like`` refers to an open file handle, this setting has no - effect. The default value (``None``) will result in the value of - ``nibabel.arrayproxy.KEEP_FILE_OPEN_DEFAULT`` being used. - """ - super().__init__(file_like, header, mmap=mmap, keep_file_open=keep_file_open) - self._scaling = header.get_data_scaling() - - @property - def scaling(self): - return self._scaling - - def _get_scaled(self, dtype, slicer): - raw_data = self._get_unscaled(slicer=slicer) - if self.scaling is None: - if dtype is None: - return raw_data - final_type = np.promote_types(raw_data.dtype, dtype) - return raw_data.astype(final_type, copy=False) - - # Broadcast scaling to shape of original data - fake_data = strided_scalar(self._shape) - _, scaling = np.broadcast_arrays(fake_data, self.scaling) - - final_type = np.result_type(raw_data, scaling) - if dtype is not None: - final_type = np.promote_types(final_type, dtype) - - # Slice scaling to give output shape - return raw_data * scaling[slicer].astype(final_type) - - -class AFNIHeader(SpatialHeader): - """Class for AFNI header""" - - def __init__(self, info): - """ - Initialize AFNI header object - - Parameters - ---------- - info : dict - Information from HEAD file as obtained by :func:`parse_AFNI_header` - - Examples - -------- - >>> fname = os.path.join(datadir, 'example4d+orig.HEAD') - >>> header = AFNIHeader(parse_AFNI_header(fname)) - >>> header.get_data_dtype().str - '>> header.get_zooms() - (3.0, 3.0, 3.0, 3.0) - >>> header.get_data_shape() - (33, 41, 25, 3) - """ - self.info = info - dt = _get_datatype(self.info) - super().__init__(data_dtype=dt, shape=self._calc_data_shape(), zooms=self._calc_zooms()) - - @classmethod - def from_header(klass, header=None): - if header is None: - raise AFNIHeaderError('Cannot create AFNIHeader from nothing.') - if type(header) == klass: - return header.copy() - raise AFNIHeaderError('Cannot create AFNIHeader from non-AFNIHeader.') - - @classmethod - def from_fileobj(klass, fileobj): - info = parse_AFNI_header(fileobj) - return klass(info) - - def copy(self): - return AFNIHeader(deepcopy(self.info)) - - def _calc_data_shape(self): - """ - Calculate the output shape of the image data - - Returns length 3 tuple for 3D image, length 4 tuple for 4D. - - Returns - ------- - (x, y, z, t) : tuple of int - - Notes - ----- - ``DATASET_RANK[0]`` gives number of spatial dimensions (and apparently - must be 3). ``DATASET_RANK[1]`` gives the number of sub-bricks. - ``DATASET_DIMENSIONS`` is length 3, giving the number of voxels in i, - j, k. - """ - dset_rank = self.info['DATASET_RANK'] - shape = tuple(self.info['DATASET_DIMENSIONS'][: dset_rank[0]]) - n_vols = dset_rank[1] - return shape + (n_vols,) - - def _calc_zooms(self): - """ - Get image zooms from header data - - Spatial axes are first three indices, time axis is last index. If - dataset is not a time series the last value will be zero. - - Returns - ------- - zooms : tuple - - Notes - ----- - Gets zooms from attributes ``DELTA`` and ``TAXIS_FLOATS``. - - ``DELTA`` gives (x,y,z) voxel sizes. - - ``TAXIS_FLOATS`` should be length 5, with first entry giving "Time - origin", and second giving "Time step (TR)". - """ - xyz_step = tuple(np.abs(self.info['DELTA'])) - t_step = self.info.get('TAXIS_FLOATS', (0, 0)) - if len(t_step) > 0: - t_step = (t_step[1],) - return xyz_step + t_step - - def get_space(self): - """ - Return label for anatomical space to which this dataset is aligned. - - Returns - ------- - space : str - AFNI "space" designation; one of [ORIG, ANAT, TLRC, MNI] - - Notes - ----- - There appears to be documentation for these spaces at - https://afni.nimh.nih.gov/pub/dist/atlases/elsedemo/AFNI_atlas_spaces.niml - """ - listed_space = self.info.get('TEMPLATE_SPACE', 0) - space = space_codes.space[listed_space] - return space - - def get_affine(self): - """ - Returns affine of dataset - - Examples - -------- - >>> fname = os.path.join(datadir, 'example4d+orig.HEAD') - >>> header = AFNIHeader(parse_AFNI_header(fname)) - >>> header.get_affine() - array([[ -3. , -0. , -0. , 49.5 ], - [ -0. , -3. , -0. , 82.312 ], - [ 0. , 0. , 3. , -52.3511], - [ 0. , 0. , 0. , 1. ]]) - """ - # 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.vstack((affine * [[-1], [-1], [1]], [0, 0, 0, 1])) - return affine - - def get_data_scaling(self): - """ - AFNI applies volume-specific data scaling - - Examples - -------- - >>> fname = os.path.join(datadir, 'scaled+tlrc.HEAD') - >>> header = AFNIHeader(parse_AFNI_header(fname)) - >>> header.get_data_scaling() - array([3.883363e-08]) - """ - # BRICK_FLOAT_FACS has one value per sub-brick, such that the scaled - # values for sub-brick array [n] are the values read from disk * - # BRICK_FLOAT_FACS[n] - floatfacs = self.info.get('BRICK_FLOAT_FACS', None) - if floatfacs is None or not np.any(floatfacs): - return None - scale = np.ones(self.info['DATASET_RANK'][1]) - floatfacs = np.atleast_1d(floatfacs) - scale[floatfacs.nonzero()] = floatfacs[floatfacs.nonzero()] - return scale - - def get_slope_inter(self): - """ - Use `self.get_data_scaling()` instead - - Holdover because ``AFNIArrayProxy`` (inheriting from ``ArrayProxy``) - requires this functionality so as to not error. - """ - return None, None - - def get_data_offset(self): - """Data offset in BRIK file - - Offset is always 0. - """ - return DATA_OFFSET - - def get_volume_labels(self): - """ - Returns volume labels - - Returns - ------- - labels : list of str - Labels for volumes along fourth dimension - - Examples - -------- - >>> header = AFNIHeader(parse_AFNI_header(os.path.join(datadir, 'example4d+orig.HEAD'))) - >>> header.get_volume_labels() - ['#0', '#1', '#2'] - """ - labels = self.info.get('BRICK_LABS', None) - if labels is not None: - labels = labels.split('~') - return labels - - -class AFNIImage(SpatialImage): - """ - AFNI Image file - - Can be loaded from either the BRIK or HEAD file (but MUST specify one!) - - Examples - -------- - >>> import nibabel as nib - >>> brik = nib.load(os.path.join(datadir, 'example4d+orig.BRIK.gz')) - >>> brik.shape - (33, 41, 25, 3) - >>> brik.affine - array([[ -3. , -0. , -0. , 49.5 ], - [ -0. , -3. , -0. , 82.312 ], - [ 0. , 0. , 3. , -52.3511], - [ 0. , 0. , 0. , 1. ]]) - >>> head = load(os.path.join(datadir, 'example4d+orig.HEAD')) - >>> np.array_equal(head.get_fdata(), brik.get_fdata()) - True - """ - - header_class = AFNIHeader - header: AFNIHeader - valid_exts = ('.brik', '.head') - files_types = (('image', '.brik'), ('header', '.head')) - _compressed_suffixes = ('.gz', '.bz2', '.Z', '.zst') - makeable = False - rw = False - ImageArrayProxy = AFNIArrayProxy - - @classmethod - def from_file_map(klass, file_map, *, mmap=True, keep_file_open=None): - """ - Creates an AFNIImage instance from `file_map` - - Parameters - ---------- - file_map : dict - dict with keys ``image, header`` and values being fileholder - objects for the respective BRIK and HEAD files - mmap : {True, False, 'c', 'r'}, optional, keyword only - `mmap` controls the use of numpy memory mapping for reading image - array data. If False, do not try numpy ``memmap`` for data array. - If one of {'c', 'r'}, try numpy memmap with ``mode=mmap``. A - `mmap` value of True gives the same behavior as ``mmap='c'``. If - image data file cannot be memory-mapped, ignore `mmap` value and - read array from file. - keep_file_open : {None, True, False}, optional, keyword only - `keep_file_open` controls whether a new file handle is created - every time the image is accessed, or a single file handle is - created and used for the lifetime of this ``ArrayProxy``. If - ``True``, a single file handle is created and used. If ``False``, - a new file handle is created every time the image is accessed. - If ``file_like`` refers to an open file handle, this setting has no - effect. The default value (``None``) will result in the value of - ``nibabel.arrayproxy.KEEP_FILE_OPEN_DEFAULT`` being used. - """ - with file_map['header'].get_prepare_fileobj('rt') as hdr_fobj: - hdr = klass.header_class.from_fileobj(hdr_fobj) - imgf = file_map['image'].fileobj - imgf = file_map['image'].filename if imgf is None else imgf - data = klass.ImageArrayProxy(imgf, hdr.copy(), mmap=mmap, keep_file_open=keep_file_open) - return klass(data, hdr.get_affine(), header=hdr, extra=None, file_map=file_map) - - @classmethod - def filespec_to_file_map(klass, filespec): - """ - Make `file_map` from filename `filespec` - - AFNI BRIK files can be compressed, but HEAD files cannot - see - afni.nimh.nih.gov/pub/dist/doc/program_help/README.compression.html. - Thus, if you have AFNI files my_image.HEAD and my_image.BRIK.gz and you - want to load the AFNI BRIK / HEAD pair, you can specify: - - * The HEAD filename - e.g., my_image.HEAD - * The BRIK filename w/o compressed extension - e.g., my_image.BRIK - * The full BRIK filename - e.g., my_image.BRIK.gz - - Parameters - ---------- - filespec : str - Filename that might be for this image file type. - - Returns - ------- - file_map : dict - dict with keys ``image`` and ``header`` where values are fileholder - objects for the respective BRIK and HEAD files - - Raises - ------ - ImageFileError - If `filespec` is not recognizable as being a filename for this - image type. - """ - file_map = super().filespec_to_file_map(filespec) - # check for AFNI-specific BRIK/HEAD compression idiosyncrasies - for key, fholder in file_map.items(): - fname = fholder.filename - if key == 'header' and not os.path.exists(fname): - for ext in klass._compressed_suffixes: - fname = fname.removesuffix(ext) - elif key == 'image' and not os.path.exists(fname): - for ext in klass._compressed_suffixes: - if os.path.exists(fname + ext): - fname += ext - break - file_map[key].filename = fname - return file_map - - -load = AFNIImage.from_filename diff --git a/nibabel/caret.py b/nibabel/caret.py deleted file mode 100644 index e142922f26..0000000000 --- a/nibabel/caret.py +++ /dev/null @@ -1,124 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -from collections.abc import MutableMapping - -from . import xmlutils as xml - - -class CaretMetaData(xml.XmlSerializable, MutableMapping): - """A list of name-value pairs used in various Caret-based XML formats - - * Description - Provides a simple method for user-supplied metadata that - associates names with values. - * Attributes: [NA] - * Child Elements - - * MD (0...N) - - * Text Content: [NA] - - MD elements are a single metadata entry consisting of a name and a value. - - Attributes - ---------- - data : mapping of {name: value} pairs - - >>> md = CaretMetaData() - >>> md['key'] = 'val' - >>> md - - >>> dict(md) - {'key': 'val'} - >>> md.to_xml() - b'keyval' - - Objects may be constructed like any ``dict``: - - >>> md = CaretMetaData(key='val') - >>> md.to_xml() - b'keyval' - """ - - def __init__(self, *args, **kwargs): - args, kwargs = self._sanitize(args, kwargs) - self._data = dict(*args, **kwargs) - - @staticmethod - def _sanitize(args, kwargs): - """Override in subclasses to accept and warn on previous invocations""" - return args, kwargs - - def __getitem__(self, key): - """Get metadata entry by name - - >>> md = CaretMetaData({'key': 'val'}) - >>> md['key'] - 'val' - """ - return self._data[key] - - def __setitem__(self, key, value): - """Set metadata entry by name - - >>> md = CaretMetaData({'key': 'val'}) - >>> dict(md) - {'key': 'val'} - >>> md['newkey'] = 'newval' - >>> dict(md) - {'key': 'val', 'newkey': 'newval'} - >>> md['key'] = 'otherval' - >>> dict(md) - {'key': 'otherval', 'newkey': 'newval'} - """ - self._data[key] = value - - def __delitem__(self, key): - """Delete metadata entry by name - - >>> md = CaretMetaData({'key': 'val'}) - >>> dict(md) - {'key': 'val'} - >>> del md['key'] - >>> dict(md) - {} - """ - del self._data[key] - - def __len__(self): - """Get length of metadata list - - >>> md = CaretMetaData({'key': 'val'}) - >>> len(md) - 1 - """ - return len(self._data) - - def __iter__(self): - """Iterate over metadata entries - - >>> md = CaretMetaData({'key': 'val'}) - >>> for key in md: - ... print(key) - key - """ - return iter(self._data) - - def __repr__(self): - return f'<{self.__class__.__name__} {self._data!r}>' - - def _to_xml_element(self): - metadata = xml.Element('MetaData') - - for name_text, value_text in self._data.items(): - md = xml.SubElement(metadata, 'MD') - name = xml.SubElement(md, 'Name') - name.text = str(name_text) - value = xml.SubElement(md, 'Value') - value.text = str(value_text) - return metadata diff --git a/nibabel/casting.py b/nibabel/casting.py deleted file mode 100644 index b279325477..0000000000 --- a/nibabel/casting.py +++ /dev/null @@ -1,817 +0,0 @@ -"""Utilities for casting numpy values in various ways - -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 platform import machine, processor - -import numpy as np - -from .deprecated import deprecate_with_version - - -class CastingError(Exception): - pass - - -# Test for VC truncation when casting floats to uint64 -# Christoph Gohlke says this is so for MSVC <= 2010 because VC is using x87 -# instructions; see: -# https://github.com/scipy/scipy/blob/99bb8411f6391d921cb3f4e56619291e91ddf43b/scipy/ndimage/tests/test_datatypes.py#L51 -_test_val = 2**63 + 2**11 # Should be exactly representable in float64 -TRUNC_UINT64 = np.float64(_test_val).astype(np.uint64) != _test_val - -# np.sctypes is deprecated in numpy 2.0 and np.core.sctypes should not be used instead. -sctypes = { - 'int': [ - getattr(np, dtype) for dtype in ('int8', 'int16', 'int32', 'int64') if hasattr(np, dtype) - ], - 'uint': [ - getattr(np, dtype) - for dtype in ('uint8', 'uint16', 'uint32', 'uint64') - if hasattr(np, dtype) - ], - 'float': [ - getattr(np, dtype) - for dtype in ('float16', 'float32', 'float64', 'float96', 'float128') - if hasattr(np, dtype) - ], - 'complex': [ - getattr(np, dtype) - for dtype in ('complex64', 'complex128', 'complex192', 'complex256') - if hasattr(np, dtype) - ], - 'others': [bool, object, bytes, str, np.void], -} -sctypes_aliases = { - getattr(np, dtype) - for dtype in ( - 'int8', 'byte', 'int16', 'short', 'int32', 'intc', 'int_', 'int64', 'longlong', - 'uint8', 'ubyte', 'uint16', 'ushort', 'uint32', 'uintc', 'uint', 'uint64', 'ulonglong', - 'float16', 'half', 'float32', 'single', 'float64', 'double', 'float96', 'float128', 'longdouble', - 'complex64', 'csingle', 'complex128', 'cdouble', 'complex192', 'complex256', 'clongdouble', - # other names of the built-in scalar types - 'int_', 'float_', 'complex_', 'bytes_', 'str_', 'bool_', 'datetime64', 'timedelta64', - # other - 'object_', 'void', - ) - if hasattr(np, dtype) -} # fmt:skip - - -def float_to_int(arr, int_type, nan2zero=True, infmax=False): - """Convert floating point array `arr` to type `int_type` - - * Rounds numbers to nearest integer - * Clips values to prevent overflows when casting - * Converts NaN to 0 (for `nan2zero` == True) - - Casting floats to integers is delicate because the result is undefined - and platform specific for float values outside the range of `int_type`. - Define ``shared_min`` to be the minimum value that can be exactly - represented in both the float type of `arr` and `int_type`. Define - `shared_max` to be the equivalent maximum value. To avoid undefined - results we threshold `arr` at ``shared_min`` and ``shared_max``. - - Parameters - ---------- - arr : array-like - Array of floating point type - int_type : object - Numpy integer type - nan2zero : {True, False, None} - Whether to convert NaN value to zero. Default is True. If False, and - NaNs are present, raise CastingError. If None, do not check for NaN - values and pass through directly to the ``astype`` casting mechanism. - In this last case, the resulting value is undefined. - infmax : {False, True} - If True, set np.inf values in `arr` to be `int_type` integer maximum - value, -np.inf as `int_type` integer minimum. If False, set +/- infs - to be ``shared_min``, ``shared_max`` as defined above. Therefore False - gives faster conversion at the expense of infs that are further from - infinity. - - Returns - ------- - iarr : ndarray - of type `int_type` - - Examples - -------- - >>> float_to_int([np.nan, np.inf, -np.inf, 1.1, 6.6], np.int16) - array([ 0, 32767, -32768, 1, 7], dtype=int16) - - Notes - ----- - Numpy relies on the C library to cast from float to int using the standard - ``astype`` method of the array. - - Quoting from section F4 of the C99 standard: - - If the floating value is infinite or NaN or if the integral part of the - floating value exceeds the range of the integer type, then the - "invalid" floating-point exception is raised and the resulting value - is unspecified. - - Hence we threshold at ``shared_min`` and ``shared_max`` to avoid casting to - values that are undefined. - - See: https://en.wikipedia.org/wiki/C99 . There are links to the C99 - standard from that page. - """ - arr = np.asarray(arr) - flt_type = arr.dtype.type - int_type = np.dtype(int_type).type - # Deal with scalar as input; fancy indexing needs 1D - shape = arr.shape - arr = np.atleast_1d(arr) - mn, mx = shared_range(flt_type, int_type) - if nan2zero is None: - seen_nans = False - else: - nans = np.isnan(arr) - seen_nans = np.any(nans) - if not nan2zero and seen_nans: - raise CastingError('NaNs in array, nan2zero is False') - iarr = np.clip(np.rint(arr), mn, mx).astype(int_type) - if seen_nans: - iarr[nans] = 0 - if not infmax: - return iarr.reshape(shape) - ii = np.iinfo(int_type) - iarr[arr == np.inf] = ii.max - if ii.min != int(mn): - iarr[arr == -np.inf] = ii.min - return iarr.reshape(shape) - - -# Cache range values -_SHARED_RANGES: dict[tuple[type, type], tuple[np.number, np.number]] = {} - - -def shared_range(flt_type, int_type): - """Min and max in float type that are >=min, <=max in integer type - - This is not as easy as it sounds, because the float type may not be able to - exactly represent the max or min integer values, so we have to find the - next exactly representable floating point value to do the thresholding. - - Parameters - ---------- - flt_type : dtype specifier - A dtype specifier referring to a numpy floating point type. For - example, ``f4``, ``np.dtype('f4')``, ``np.float32`` are equivalent. - int_type : dtype specifier - A dtype specifier referring to a numpy integer type. For example, - ``i4``, ``np.dtype('i4')``, ``np.int32`` are equivalent - - Returns - ------- - mn : object - Number of type `flt_type` that is the minimum value in the range of - `int_type`, such that ``mn.astype(int_type)`` >= min of `int_type` - mx : object - Number of type `flt_type` that is the maximum value in the range of - `int_type`, such that ``mx.astype(int_type)`` <= max of `int_type` - - Examples - -------- - >>> shared_range(np.float32, np.int32) == (-2147483648.0, 2147483520.0) - True - >>> shared_range('f4', 'i4') == (-2147483648.0, 2147483520.0) - True - """ - flt_type = np.dtype(flt_type).type - int_type = np.dtype(int_type).type - key = (flt_type, int_type) - # Used cached value if present - try: - return _SHARED_RANGES[key] - except KeyError: - pass - ii = np.iinfo(int_type) - fi = np.finfo(flt_type) - mn = ceil_exact(ii.min, flt_type) - if mn == -np.inf: - mn = fi.min - mx = floor_exact(ii.max, flt_type) - if mx == np.inf: - mx = fi.max - elif TRUNC_UINT64 and int_type == np.uint64: - mx = min(mx, flt_type(2**63)) - _SHARED_RANGES[key] = (mn, mx) - return mn, mx - - -# ---------------------------------------------------------------------------- -# Routines to work out the next lowest representable integer in floating point -# types. -# ---------------------------------------------------------------------------- - - -class FloatingError(Exception): - pass - - -def on_powerpc(): - """True if we are running on a Power PC platform - - Has to deal with older Macs and IBM POWER7 series among others - """ - return processor() == 'powerpc' or machine().startswith('ppc') - - -def type_info(np_type): - """Return dict with min, max, nexp, nmant, width for numpy type `np_type` - - Type can be integer in which case nexp and nmant are None. - - Parameters - ---------- - np_type : numpy type specifier - Any specifier for a numpy dtype - - Returns - ------- - info : dict - with fields ``min`` (minimum value), ``max`` (maximum value), ``nexp`` - (exponent width), ``nmant`` (significand precision not including - implicit first digit), ``minexp`` (minimum exponent), ``maxexp`` - (maximum exponent), ``width`` (width in bytes). (``nexp``, ``nmant``, - ``minexp``, ``maxexp``) are None for integer types. Both ``min`` and - ``max`` are of type `np_type`. - - Raises - ------ - FloatingError - for floating point types we don't recognize - - Notes - ----- - You might be thinking that ``np.finfo`` does this job, and it does, except - for PPC long doubles (https://github.com/numpy/numpy/issues/2669) and - float96 on Windows compiled with Mingw. This routine protects against such - errors in ``np.finfo`` by only accepting values that we know are likely to - be correct. - """ - dt = np.dtype(np_type) - np_type = dt.type - width = dt.itemsize - try: # integer type - info = np.iinfo(dt) - except ValueError: - pass - else: - return dict( - min=np_type(info.min), - max=np_type(info.max), - minexp=None, - maxexp=None, - nmant=None, - nexp=None, - width=width, - ) - # Mitigate warning from WSL1 when checking `np.longdouble` (#1309) - with warnings.catch_warnings(): - warnings.filterwarnings( - action='http://github.com/ignore', category=UserWarning, message='Signature.*numpy.longdouble' - ) - info = np.finfo(dt) - - # Trust the standard IEEE types - nmant, nexp = info.nmant, info.nexp - ret = dict( - min=np_type(info.min), - max=np_type(info.max), - nmant=nmant, - nexp=nexp, - minexp=info.minexp, - maxexp=info.maxexp, - width=width, - ) - if np_type in (np.float16, np.float32, np.float64, np.complex64, np.complex128): - return ret - info_64 = np.finfo(np.float64) - if dt.kind == 'c': - assert np_type is np.clongdouble - vals = (nmant, nexp, width / 2) - else: - assert np_type is np.longdouble - vals = (nmant, nexp, width) - if vals in ( - (112, 15, 16), # binary128 - (info_64.nmant, info_64.nexp, 8), # float64 - (63, 15, 12), # Intel extended 80 - (63, 15, 16), # Intel extended 80 - ): - return ret # these are OK without modification - # The remaining types are longdoubles with bad finfo values. Some we - # correct, others we wait to hear of errors. - # We start with float64 as basis - ret = type_info(np.float64) - if vals in ((52, 15, 12), (52, 15, 16)): # windows float96 / windows float128? - # On windows 32 bit at least, float96 is Intel 80 storage but operating - # at float64 precision. The finfo values give nexp == 15 (as for intel - # 80) but in calculations nexp in fact appears to be 11 as for float64 - ret.update(dict(width=width)) - return ret - if vals == (105, 11, 16): # correctly detected double double - ret.update(dict(nmant=nmant, nexp=nexp, width=width)) - return ret - # Oh dear, we don't recognize the type information. Try some known types - # and then give up. At this stage we're expecting exotic longdouble or - # their complex equivalent. - if np_type not in (np.longdouble, np.clongdouble) or width not in (16, 32): - raise FloatingError(f'We had not expected type {np_type}') - if vals == (1, 1, 16) and on_powerpc() and _check_maxexp(np.longdouble, 1024): - # double pair on PPC. The _check_nmant routine does not work for this - # type, hence the powerpc platform check instead - ret.update(dict(nmant=106, width=width)) - elif _check_nmant(np.longdouble, 52) and _check_maxexp(np.longdouble, 11): - # Got float64 despite everything - pass - elif _check_nmant(np.longdouble, 112) and _check_maxexp(np.longdouble, 16384): - # binary 128, but with some busted type information. np.clongdouble - # seems to break here too, so we need to use np.longdouble and - # complexify - two = np.longdouble(2) - # See: https://matthew-brett.github.io/pydagogue/floating_point.html - max_val = (two**113 - 1) / (two**112) * two**16383 - if np_type is np.clongdouble: - max_val += 0j - ret = dict( - min=-max_val, - max=max_val, - nmant=112, - nexp=15, - minexp=-16382, - maxexp=16384, - width=width, - ) - else: # don't recognize the type - raise FloatingError(f'We had not expected long double type {np_type} with info {info}') - return ret - - -def _check_nmant(np_type, nmant): - """True if fp type `np_type` seems to have `nmant` significand digits - - Note 'digits' does not include implicit digits. And in fact if there are - no implicit digits, the `nmant` number is one less than the actual digits. - Assumes base 2 representation. - - Parameters - ---------- - np_type : numpy type specifier - Any specifier for a numpy dtype - nmant : int - Number of digits to test against - - Returns - ------- - tf : bool - True if `nmant` is the correct number of significand digits, false - otherwise - """ - np_type = np.dtype(np_type).type - max_contig = np_type(2 ** (nmant + 1)) # maximum of contiguous integers - tests = max_contig + np.array([-2, -1, 0, 1, 2], dtype=np_type) - return np.all(tests - max_contig == [-2, -1, 0, 0, 2]) - - -def _check_maxexp(np_type, maxexp): - """True if fp type `np_type` seems to have `maxexp` maximum exponent - - We're testing "maxexp" as returned by numpy. This value is set to one - greater than the maximum power of 2 that `np_type` can represent. - - Assumes base 2 representation. Very crude check - - Parameters - ---------- - np_type : numpy type specifier - Any specifier for a numpy dtype - maxexp : int - Maximum exponent to test against - - Returns - ------- - tf : bool - True if `maxexp` is the correct maximum exponent, False otherwise. - """ - dt = np.dtype(np_type) - np_type = dt.type - two = np_type(2).reshape((1,)) # to avoid upcasting - with warnings.catch_warnings(): - warnings.simplefilter('ignore', RuntimeWarning) # Expected overflow warning - return np.isfinite(two ** (maxexp - 1)) and not np.isfinite(two**maxexp) - - -@deprecate_with_version('as_int() is deprecated. Use int() instead.', '5.2.0', '7.0.0') -def as_int(x, check=True): - """Return python integer representation of number - - This is useful because the numpy int(val) mechanism is broken for large - values in np.longdouble. - - It is also useful to work around a numpy 1.4.1 bug in conversion of uints - to python ints. - - Parameters - ---------- - x : object - integer, unsigned integer or floating point value - check : {True, False} - If True, raise error for values that are not integers - - Returns - ------- - i : int - Python integer - - Examples - -------- - >>> as_int(2.0) - 2 - >>> as_int(-2.0) - -2 - >>> as_int(2.1) #doctest: +IGNORE_EXCEPTION_DETAIL - Traceback (most recent call last): - ... - FloatingError: Not an integer: 2.1 - >>> as_int(2.1, check=False) - 2 - """ - ix = int(x) - if check and ix != x: - raise FloatingError(f'Not an integer: {x}') - return ix - - -@deprecate_with_version('int_to_float(..., dt) is deprecated. Use dt() instead.', '5.2.0', '7.0.0') -def int_to_float(val, flt_type): - """Convert integer `val` to floating point type `flt_type` - - Why is this so complicated? - - At least in numpy <= 1.6.1, numpy longdoubles do not correctly convert to - ints, and ints do not correctly convert to longdoubles. Specifically, in - both cases, the values seem to go through float64 conversion on the way, so - to convert better, we need to split into float64s and sum up the result. - - Parameters - ---------- - val : int - Integer value - flt_type : object - numpy floating point type - - Returns - ------- - f : numpy scalar - of type `flt_type` - - Examples - -------- - >>> int_to_float(1, np.float32) - 1.0 - """ - return flt_type(val) - - -def floor_exact(val, flt_type): - """Return nearest exact integer <= `val` in float type `flt_type` - - Parameters - ---------- - val : int - We have to pass val as an int rather than the floating point type - because large integers cast as floating point may be rounded by the - casting process. - flt_type : numpy type - numpy float type. - - Returns - ------- - floor_val : object - value of same floating point type as `val`, that is the nearest exact - integer in this type such that `floor_val` <= `val`. Thus if `val` is - exact in `flt_type`, `floor_val` == `val`. - - Examples - -------- - Obviously 2 is within the range of representable integers for float32 - - >>> floor_exact(2, np.float32) - 2.0 - - As is 2**24-1 (the number of significand digits is 23 + 1 implicit) - - >>> floor_exact(2**24-1, np.float32) == 2**24-1 - True - - But 2**24+1 gives a number that float32 can't represent exactly - - >>> floor_exact(2**24+1, np.float32) == 2**24 - True - - As for the numpy floor function, negatives floor towards -inf - - >>> floor_exact(-2**24-1, np.float32) == -2**24-2 - True - """ - val = int(val) - flt_type = np.dtype(flt_type).type - sign = 1 if val > 0 else -1 - try: - fval = flt_type(val) - except OverflowError: - return sign * np.inf - if not np.isfinite(fval): - return fval - info = type_info(flt_type) - diff = val - int(fval) - if diff >= 0: # floating point value <= val - return fval - # Float casting made the value go up - biggest_gap = 2 ** (floor_log2(val) - info['nmant']) - assert biggest_gap > 1 - fval -= flt_type(biggest_gap) - return fval - - -def ceil_exact(val, flt_type): - """Return nearest exact integer >= `val` in float type `flt_type` - - Parameters - ---------- - val : int - We have to pass val as an int rather than the floating point type - because large integers cast as floating point may be rounded by the - casting process. - flt_type : numpy type - numpy float type. - - Returns - ------- - ceil_val : object - value of same floating point type as `val`, that is the nearest exact - integer in this type such that `floor_val` >= `val`. Thus if `val` is - exact in `flt_type`, `ceil_val` == `val`. - - Examples - -------- - Obviously 2 is within the range of representable integers for float32 - - >>> ceil_exact(2, np.float32) - 2.0 - - As is 2**24-1 (the number of significand digits is 23 + 1 implicit) - - >>> ceil_exact(2**24-1, np.float32) == 2**24-1 - True - - But 2**24+1 gives a number that float32 can't represent exactly - - >>> ceil_exact(2**24+1, np.float32) == 2**24+2 - True - - As for the numpy ceil function, negatives ceil towards inf - - >>> ceil_exact(-2**24-1, np.float32) == -2**24 - True - """ - return -floor_exact(-val, flt_type) - - -def int_abs(arr): - """Absolute values of array taking care of max negative int values - - Parameters - ---------- - arr : array-like - - Returns - ------- - abs_arr : array - array the same shape as `arr` in which all negative numbers have been - changed to positive numbers with the magnitude. - - Examples - -------- - This kind of thing is confusing in base numpy: - - >>> import numpy as np - >>> np.abs(np.int8(-128)) - -128 - - ``int_abs`` fixes that: - - >>> int_abs(np.int8(-128)) - 128 - >>> int_abs(np.array([-128, 127], dtype=np.int8)) - array([128, 127], dtype=uint8) - >>> int_abs(np.array([-128, 127], dtype=np.float32)) - array([128., 127.], dtype=float32) - """ - arr = np.asarray(arr) - dt = arr.dtype - if dt.kind == 'u': - return arr - if dt.kind != 'i': - return np.absolute(arr) - out = arr.astype(np.dtype(dt.str.replace('i', 'u'))) - return np.choose(arr < 0, (arr, arr * -1), out=out) - - -def floor_log2(x): - """floor of log2 of abs(`x`) - - Embarrassingly, from https://en.wikipedia.org/wiki/Binary_logarithm - - Parameters - ---------- - x : int - - Returns - ------- - L : None or int - floor of base 2 log of `x`. None if `x` == 0. - - Examples - -------- - >>> floor_log2(2**9+1) - 9 - >>> floor_log2(-2**9+1) - 8 - >>> floor_log2(0.5) - -1 - >>> floor_log2(0) is None - True - """ - ip = 0 - rem = abs(x) - if rem > 1: - while rem >= 2: - ip += 1 - rem //= 2 - return ip - elif rem == 0: - return None - while rem < 1: - ip -= 1 - rem *= 2 - return ip - - -def best_float(): - """Floating point type with best precision - - This is nearly always np.longdouble, except on Windows, where np.longdouble - is Intel80 storage, but with float64 precision for calculations. In that - case we return float64 on the basis it's the fastest and smallest at the - highest precision. - - SPARC float128 also proved so slow that we prefer float64. - - Returns - ------- - best_type : numpy type - floating point type with highest precision - - Notes - ----- - Needs to run without error for module import, because it is called in - ``ok_floats`` below, and therefore in setting module global ``OK_FLOATS``. - """ - try: - long_info = type_info(np.longdouble) - except FloatingError: - return np.float64 - if ( - long_info['nmant'] > type_info(np.float64)['nmant'] and machine() != 'sparc64' - ): # sparc has crazy-slow float128 - return np.longdouble - return np.float64 - - -def longdouble_lte_float64(): - """Return True if longdouble appears to have the same precision as float64""" - return np.longdouble(2**53) == np.longdouble(2**53) + 1 - - -# Record longdouble precision at import because it can change on Windows -_LD_LTE_FLOAT64 = longdouble_lte_float64() - - -def longdouble_precision_improved(): - """True if longdouble precision increased since initial import - - This can happen on Windows compiled with MSVC. It may be because libraries - compiled with mingw (longdouble is Intel80) get linked to numpy compiled - with MSVC (longdouble is Float64) - """ - return not longdouble_lte_float64() and _LD_LTE_FLOAT64 - - -def have_binary128(): - """True if we have a binary128 IEEE longdouble""" - try: - ti = type_info(np.longdouble) - except FloatingError: - return False - return (ti['nmant'], ti['maxexp']) == (112, 16384) - - -def ok_floats(): - """Return floating point types sorted by precision - - Remove longdouble if it has no higher precision than float64 - """ - # copy float list so we don't change the numpy global - floats = sctypes['float'][:] - if best_float() != np.longdouble and np.longdouble in floats: - floats.remove(np.longdouble) - return sorted(floats, key=lambda f: type_info(f)['nmant']) - - -OK_FLOATS = ok_floats() - - -def able_int_type(values): - """Find the smallest integer numpy type to contain sequence `values` - - Prefers uint to int if minimum is >= 0 - - Parameters - ---------- - values : sequence - sequence of integer values - - Returns - ------- - itype : None or numpy type - numpy integer type or None if no integer type holds all `values` - - Examples - -------- - >>> able_int_type([0, 1]) == np.uint8 - True - >>> able_int_type([-1, 1]) == np.int8 - True - """ - if any(v % 1 for v in values): - return None - mn = min(values) - mx = max(values) - if mn >= 0: - for ityp in sctypes['uint']: - if mx <= np.iinfo(ityp).max: - return ityp - for ityp in sctypes['int']: - info = np.iinfo(ityp) - if mn >= info.min and mx <= info.max: - return ityp - return None - - -def ulp(val=np.float64(1.0)): - """Return gap between `val` and nearest representable number of same type - - This is the value of a unit in the last place (ULP), and is similar in - meaning to the MATLAB eps function. - - Parameters - ---------- - val : scalar, optional - scalar value of any numpy type. Default is 1.0 (float64) - - Returns - ------- - ulp_val : scalar - gap between `val` and nearest representable number of same type - - Notes - ----- - The wikipedia article on machine epsilon points out that the term *epsilon* - can be used in the sense of a unit in the last place (ULP), or as the - maximum relative rounding error. The MATLAB ``eps`` function uses the ULP - meaning, but this function is ``ulp`` rather than ``eps`` to avoid - confusion between different meanings of *eps*. - """ - val = np.array(val) - if not np.isfinite(val): - return np.nan - if val.dtype.kind in 'iu': - return 1 - aval = np.abs(val) - info = type_info(val.dtype) - fl2 = floor_log2(aval) - if fl2 is None or fl2 < info['minexp']: # subnormal - fl2 = info['minexp'] - # 'nmant' value does not include implicit first bit - return 2 ** (fl2 - info['nmant']) diff --git a/nibabel/cifti2/__init__.py b/nibabel/cifti2/__init__.py deleted file mode 100644 index 9c6805f818..0000000000 --- a/nibabel/cifti2/__init__.py +++ /dev/null @@ -1,44 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""CIFTI-2 format IO - -.. currentmodule:: nibabel.cifti2 - -.. autosummary:: - :toctree: ../generated - - cifti2 - cifti2_axes -""" - -from .cifti2 import ( - CIFTI_BRAIN_STRUCTURES, - CIFTI_MODEL_TYPES, - Cifti2BrainModel, - Cifti2Header, - Cifti2HeaderError, - Cifti2Image, - Cifti2Label, - Cifti2LabelTable, - Cifti2Matrix, - Cifti2MatrixIndicesMap, - Cifti2MetaData, - Cifti2NamedMap, - Cifti2Parcel, - Cifti2Surface, - Cifti2TransformationMatrixVoxelIndicesIJKtoXYZ, - Cifti2VertexIndices, - Cifti2Vertices, - Cifti2Volume, - Cifti2VoxelIndicesIJK, - load, - save, -) -from .cifti2_axes import Axis, BrainModelAxis, LabelAxis, ParcelsAxis, ScalarAxis, SeriesAxis -from .parse_cifti2 import Cifti2Extension diff --git a/nibabel/cifti2/cifti2.py b/nibabel/cifti2/cifti2.py deleted file mode 100644 index 7442a91860..0000000000 --- a/nibabel/cifti2/cifti2.py +++ /dev/null @@ -1,1626 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Read / write access to CIFTI-2 image format - -Format of the NIFTI2 container format described here: - - http://www.nitrc.org/forum/message.php?msg_id=3738 - -Definition of the CIFTI-2 header format and file extensions can be found at: - - http://www.nitrc.org/projects/cifti -""" - -import re -from collections import OrderedDict -from collections.abc import Iterable, MutableMapping, MutableSequence -from warnings import warn - -import numpy as np - -from .. import xmlutils as xml -from ..arrayproxy import reshape_dataobj -from ..caret import CaretMetaData -from ..dataobj_images import DataobjImage -from ..filebasedimages import FileBasedHeader, SerializableImage -from ..nifti1 import Nifti1Extensions -from ..nifti2 import Nifti2Header, Nifti2Image -from ..volumeutils import Recoder, make_dt_codes - - -def _float_01(val): - out = float(val) - if out < 0 or out > 1: - raise ValueError('Float must be between 0 and 1 inclusive') - return out - - -class Cifti2HeaderError(Exception): - """Error in CIFTI-2 header""" - - -_dtdefs = ( # code, label, dtype definition, niistring - (2, 'uint8', np.uint8, 'NIFTI_TYPE_UINT8'), - (4, 'int16', np.int16, 'NIFTI_TYPE_INT16'), - (8, 'int32', np.int32, 'NIFTI_TYPE_INT32'), - (16, 'float32', np.float32, 'NIFTI_TYPE_FLOAT32'), - (64, 'float64', np.float64, 'NIFTI_TYPE_FLOAT64'), - (256, 'int8', np.int8, 'NIFTI_TYPE_INT8'), - (512, 'uint16', np.uint16, 'NIFTI_TYPE_UINT16'), - (768, 'uint32', np.uint32, 'NIFTI_TYPE_UINT32'), - (1024, 'int64', np.int64, 'NIFTI_TYPE_INT64'), - (1280, 'uint64', np.uint64, 'NIFTI_TYPE_UINT64'), -) - -# Make full code alias bank, including dtype column -data_type_codes = make_dt_codes(_dtdefs) - -CIFTI_MAP_TYPES = ( - 'CIFTI_INDEX_TYPE_BRAIN_MODELS', - 'CIFTI_INDEX_TYPE_PARCELS', - 'CIFTI_INDEX_TYPE_SERIES', - 'CIFTI_INDEX_TYPE_SCALARS', - 'CIFTI_INDEX_TYPE_LABELS', -) - -CIFTI_MODEL_TYPES = ( - 'CIFTI_MODEL_TYPE_SURFACE', # Modeled using surface vertices - 'CIFTI_MODEL_TYPE_VOXELS', # Modeled using voxels. -) - -CIFTI_SERIESUNIT_TYPES = ( - 'SECOND', - 'HERTZ', - 'METER', - 'RADIAN', -) - - -def _full_structure(struct: str): - """Expands STRUCT_NAME into: - - STRUCT_NAME, CIFTI_STRUCTURE_STRUCT_NAME, StructName - """ - return ( - struct, - f'CIFTI_STRUCTURE_{struct}', - ''.join(word.capitalize() for word in struct.split('_')), - ) - - -CIFTI_BRAIN_STRUCTURES = Recoder( - ( - # For simplicity of comparison, use the ordering from: - # https://github.com/Washington-University/workbench/blob/b985f5d/src/Common/StructureEnum.cxx - # (name, ciftiname, guiname) - # ('CORTEX_LEFT', 'CIFTI_STRUCTURE_CORTEX_LEFT', 'CortexLeft') - _full_structure('CORTEX_LEFT'), - _full_structure('CORTEX_RIGHT'), - _full_structure('CEREBELLUM'), - _full_structure('ACCUMBENS_LEFT'), - _full_structure('ACCUMBENS_RIGHT'), - _full_structure('ALL'), - _full_structure('ALL_GREY_MATTER'), - _full_structure('ALL_WHITE_MATTER'), - _full_structure('AMYGDALA_LEFT'), - _full_structure('AMYGDALA_RIGHT'), - _full_structure('BRAIN_STEM'), - _full_structure('CAUDATE_LEFT'), - _full_structure('CAUDATE_RIGHT'), - _full_structure('CEREBELLAR_WHITE_MATTER_LEFT'), - _full_structure('CEREBELLAR_WHITE_MATTER_RIGHT'), - _full_structure('CEREBELLUM_LEFT'), - _full_structure('CEREBELLUM_RIGHT'), - _full_structure('CEREBRAL_WHITE_MATTER_LEFT'), - _full_structure('CEREBRAL_WHITE_MATTER_RIGHT'), - _full_structure('CORTEX'), - _full_structure('DIENCEPHALON_VENTRAL_LEFT'), - _full_structure('DIENCEPHALON_VENTRAL_RIGHT'), - _full_structure('HIPPOCAMPUS_LEFT'), - _full_structure('HIPPOCAMPUS_RIGHT'), - _full_structure('INVALID'), - _full_structure('OTHER'), - _full_structure('OTHER_GREY_MATTER'), - _full_structure('OTHER_WHITE_MATTER'), - _full_structure('PALLIDUM_LEFT'), - _full_structure('PALLIDUM_RIGHT'), - _full_structure('PUTAMEN_LEFT'), - _full_structure('PUTAMEN_RIGHT'), - ## Also commented out in connectome_wb; unclear if deprecated, planned, or what - # _full_structure("SUBCORTICAL_WHITE_MATTER_LEFT") - # _full_structure("SUBCORTICAL_WHITE_MATTER_RIGHT") - _full_structure('THALAMUS_LEFT'), - _full_structure('THALAMUS_RIGHT'), - ), - fields=('name', 'ciftiname', 'guiname'), -) - - -def _value_if_klass(val, klass): - if val is None or isinstance(val, klass): - return val - raise ValueError(f'Not a valid {klass.__name__} instance.') - - -def _underscore(string): - """Convert a string from CamelCase to underscored""" - string = re.sub(r'([A-Z]+)([A-Z][a-z])', r'\1_\2', string) - return re.sub(r'([a-z0-9])([A-Z])', r'\1_\2', string).lower() - - -class LimitedNifti2Header(Nifti2Header): - _data_type_codes = data_type_codes - - -class Cifti2MetaData(CaretMetaData): - """A list of name-value pairs - - * Description - Provides a simple method for user-supplied metadata that - associates names with values. - * Attributes: [NA] - * Child Elements - - * MD (0...N) - - * Text Content: [NA] - * Parent Elements - Matrix, NamedMap - - MD elements are a single metadata entry consisting of a name and a value. - - Attributes - ---------- - data : list of (name, value) tuples - """ - - @staticmethod - def _sanitize(args, kwargs): - """Sanitize and warn on deprecated arguments - - Accept metadata positional/keyword argument that can take - ``None`` to indicate no initialization. - - >>> import pytest - >>> Cifti2MetaData() - - >>> Cifti2MetaData([("key", "val")]) - - >>> Cifti2MetaData(key="val") - - >>> with pytest.warns(FutureWarning): - ... Cifti2MetaData(None) - - >>> with pytest.warns(FutureWarning): - ... Cifti2MetaData(metadata=None) - - >>> with pytest.warns(FutureWarning): - ... Cifti2MetaData(metadata={'key': 'val'}) - - - Note that "metadata" could be a valid key: - - >>> Cifti2MetaData(metadata='val') - - """ - if not args and list(kwargs) == ['metadata']: - if not isinstance(kwargs['metadata'], str): - warn( - 'Cifti2MetaData now has a dict-like interface and will ' - 'no longer accept the ``metadata`` keyword argument in ' - 'NiBabel 6.0. See ``pydoc dict`` for initialization options.', - FutureWarning, - stacklevel=3, - ) - md = kwargs.pop('metadata') - if md is not None: - args = (md,) - if args == (None,): - warn( - 'Cifti2MetaData now has a dict-like interface and will no longer ' - 'accept the positional argument ``None`` in NiBabel 6.0. ' - 'See ``pydoc dict`` for initialization options.', - FutureWarning, - stacklevel=3, - ) - args = () - return args, kwargs - - @property - def data(self): - return self._data - - def difference_update(self, metadata): - """Remove metadata key-value pairs - - Parameters - ---------- - metadata : dict-like datatype - - Returns - ------- - None - - """ - if metadata is None: - raise ValueError("The metadata parameter can't be None") - pairs = dict(metadata) - for k in pairs: - del self.data[k] - - -class Cifti2LabelTable(xml.XmlSerializable, MutableMapping): - r"""CIFTI-2 label table: a sequence of ``Cifti2Label``\s - - * Description - Used by NamedMap when IndicesMapToDataType is - "CIFTI_INDEX_TYPE_LABELS" in order to associate names and display colors - with label keys. Note that LABELS is the only mapping type that uses a - LabelTable. Display coloring of continuous-valued data is not specified - by CIFTI-2. - * Attributes: [NA] - * Child Elements - - * Label (0...N) - - * Text Content: [NA] - * Parent Element - NamedMap - """ - - def __init__(self): - self._labels = OrderedDict() - - def __len__(self): - return len(self._labels) - - def __getitem__(self, key): - return self._labels[key] - - def append(self, label): - self[label.key] = label - - def __setitem__(self, key, value): - if isinstance(value, Cifti2Label): - if key != value.key: - raise ValueError("The key and the label's key must agree") - self._labels[key] = value - return - if len(value) != 5: - raise ValueError('Value should be length 5') - try: - self._labels[key] = Cifti2Label(*([key] + list(value))) - except ValueError: - raise ValueError( - 'Key should be int, value should be sequence of str and 4 floats between 0 and 1' - ) - - def __delitem__(self, key): - del self._labels[key] - - def __iter__(self): - return iter(self._labels) - - def _to_xml_element(self): - if len(self) == 0: - raise Cifti2HeaderError('LabelTable element requires at least 1 label') - labeltable = xml.Element('LabelTable') - for ele in self._labels.values(): - labeltable.append(ele._to_xml_element()) - return labeltable - - -class Cifti2Label(xml.XmlSerializable): - """CIFTI-2 label: association of integer key with a name and RGBA values - - For all color components, value is floating point with range 0.0 to 1.0. - - * Description - Associates a label key value with a name and a display - color. - * Attributes - - * Key - Integer, data value which is assigned this name and color. - * Red - Red color component for label. Value is floating point with - range 0.0 to 1.0. - * Green - Green color component for label. Value is floating point with - range 0.0 to 1.0. - * Blue - Blue color component for label. Value is floating point with - range 0.0 to 1.0. - * Alpha - Alpha color component for label. Value is floating point with - range 0.0 to 1.0. - - * Child Elements: [NA] - * Text Content - Name of the label. - * Parent Element - LabelTable - - Attributes - ---------- - key : int, optional - Integer, data value which is assigned this name and color. - label : str, optional - Name of the label. - red : float, optional - Red color component for label (between 0 and 1). - green : float, optional - Green color component for label (between 0 and 1). - blue : float, optional - Blue color component for label (between 0 and 1). - alpha : float, optional - Alpha color component for label (between 0 and 1). - """ - - def __init__(self, key=0, label='', red=0.0, green=0.0, blue=0.0, alpha=0.0): - self.key = int(key) - self.label = str(label) - self.red = _float_01(red) - self.green = _float_01(green) - self.blue = _float_01(blue) - self.alpha = _float_01(alpha) - - @property - def rgba(self): - """Returns RGBA as tuple""" - return (self.red, self.green, self.blue, self.alpha) - - def _to_xml_element(self): - if self.label == '': - raise Cifti2HeaderError('Label needs a name') - try: - v = int(self.key) - except ValueError: - raise Cifti2HeaderError('The key must be an integer') - for c_ in ('red', 'blue', 'green', 'alpha'): - try: - v = _float_01(getattr(self, c_)) - except ValueError: - raise Cifti2HeaderError( - f'Label invalid {c_} needs to be a float between 0 and 1. and it is {v}' - ) - - lab = xml.Element('Label') - lab.attrib['Key'] = str(self.key) - lab.text = str(self.label) - - for name in ('red', 'green', 'blue', 'alpha'): - val = getattr(self, name) - attr = '0' if val == 0 else '1' if val == 1 else str(val) - lab.attrib[name.capitalize()] = attr - return lab - - -class Cifti2NamedMap(xml.XmlSerializable): - """CIFTI-2 named map: association of name and optional data with a map index - - Associates a name, optional metadata, and possibly a LabelTable with an - index in a map. - - * Description - Associates a name, optional metadata, and possibly a - LabelTable with an index in a map. - * Attributes: [NA] - * Child Elements - - * MapName (1) - * LabelTable (0...1) - * MetaData (0...1) - - * Text Content: [NA] - * Parent Element - MatrixIndicesMap - - Attributes - ---------- - map_name : str - Name of map - metadata : None or Cifti2MetaData - Metadata associated with named map - label_table : None or Cifti2LabelTable - Label table associated with named map - """ - - def __init__(self, map_name=None, metadata=None, label_table=None): - self.map_name = map_name - self.metadata = metadata - self.label_table = label_table - - @property - def metadata(self): - return self._metadata - - @metadata.setter - def metadata(self, metadata): - """Set the metadata for this NamedMap - - Parameters - ---------- - meta : Cifti2MetaData - - Returns - ------- - None - """ - self._metadata = _value_if_klass(metadata, Cifti2MetaData) - - @property - def label_table(self): - return self._label_table - - @label_table.setter - def label_table(self, label_table): - """Set the label_table for this NamedMap - - Parameters - ---------- - label_table : Cifti2LabelTable - - Returns - ------- - None - """ - self._label_table = _value_if_klass(label_table, Cifti2LabelTable) - - def _to_xml_element(self): - named_map = xml.Element('NamedMap') - if self.metadata: - named_map.append(self.metadata._to_xml_element()) - if self.label_table: - named_map.append(self.label_table._to_xml_element()) - map_name = xml.SubElement(named_map, 'MapName') - map_name.text = self.map_name - return named_map - - -class Cifti2Surface(xml.XmlSerializable): - """Cifti surface: association of brain structure and number of vertices - - * Description - Specifies the number of vertices for a surface, when - IndicesMapToDataType is "CIFTI_INDEX_TYPE_PARCELS." This is separate from - the Parcel element because there can be multiple parcels on one surface, - and one parcel may involve multiple surfaces. - * Attributes - - * BrainStructure - A string from the BrainStructure list to identify - what surface structure this element refers to (usually left cortex, - right cortex, or cerebellum). - * SurfaceNumberOfVertices - The number of vertices that this - structure's surface contains. - - * Child Elements: [NA] - * Text Content: [NA] - * Parent Element - MatrixIndicesMap - - Attributes - ---------- - brain_structure : str - Name of brain structure - surface_number_of_vertices : int - Number of vertices on surface - """ - - def __init__(self, brain_structure=None, surface_number_of_vertices=None): - self.brain_structure = brain_structure - self.surface_number_of_vertices = surface_number_of_vertices - - def _to_xml_element(self): - if self.brain_structure is None: - raise Cifti2HeaderError('Surface element requires at least 1 BrainStructure') - surf = xml.Element('Surface') - surf.attrib['BrainStructure'] = str(self.brain_structure) - surf.attrib['SurfaceNumberOfVertices'] = str(self.surface_number_of_vertices) - return surf - - -class Cifti2VoxelIndicesIJK(xml.XmlSerializable, MutableSequence): - """CIFTI-2 VoxelIndicesIJK: Set of voxel indices contained in a structure - - * Description - Identifies the voxels that model a brain structure, or - participate in a parcel. Note that when this is a child of BrainModel, - the IndexCount attribute of the BrainModel indicates the number of voxels - contained in this element. - * Attributes: [NA] - * Child Elements: [NA] - * Text Content - IJK indices (which are zero-based) of each voxel in this - brain model or parcel, with each index separated by a whitespace - character. There are three indices per voxel. If the parent element is - BrainModel, then the BrainModel element's IndexCount attribute indicates - the number of triplets (IJK indices) in this element's content. - * Parent Elements - BrainModel, Parcel - - Each element of this sequence is a triple of integers. - """ - - def __init__(self, indices=None): - self._indices = [] - if indices is not None: - self.extend(indices) - - def __len__(self): - return len(self._indices) - - def __delitem__(self, index): - if not isinstance(index, int) and len(index) > 1: - raise NotImplementedError - del self._indices[index] - - def __getitem__(self, index): - if isinstance(index, int): - return self._indices[index] - elif len(index) == 2: - if not isinstance(index[0], int): - raise NotImplementedError - return self._indices[index[0]][index[1]] - else: - raise ValueError('Only row and row,column access is allowed') - - def __setitem__(self, index, value): - if isinstance(index, int): - try: - value = [int(v) for v in value] - if len(value) != 3: - raise ValueError('rows are triples of ints') - self._indices[index] = value - except ValueError: - raise ValueError('value must be a triple of ints') - elif len(index) == 2: - try: - if not isinstance(index[0], int): - raise NotImplementedError - value = int(value) - self._indices[index[0]][index[1]] = value - except ValueError: - raise ValueError('value must be an int') - else: - raise ValueError - - def insert(self, index, value): - if not isinstance(index, int) and len(index) != 1: - raise ValueError('Only rows can be inserted') - try: - value = [int(v) for v in value] - if len(value) != 3: - raise ValueError - self._indices.insert(index, value) - except ValueError: - raise ValueError('value must be a triple of int') - - def _to_xml_element(self): - if len(self) == 0: - raise Cifti2HeaderError('VoxelIndicesIJK element require an index table') - - vox_ind = xml.Element('VoxelIndicesIJK') - vox_ind.text = '\n'.join(' '.join([str(v) for v in row]) for row in self._indices) - return vox_ind - - -class Cifti2Vertices(xml.XmlSerializable, MutableSequence): - """CIFTI-2 vertices - association of brain structure and a list of vertices - - * Description - Contains a BrainStructure type and a list of vertex indices - within a Parcel. - * Attributes - - * BrainStructure - A string from the BrainStructure list to identify - what surface this vertex list is from (usually left cortex, right - cortex, or cerebellum). - - * Child Elements: [NA] - * Text Content - Vertex indices (which are independent for each surface, - and zero-based) separated by whitespace characters. - * Parent Element - Parcel - - The class behaves like a list of Vertex indices (which are independent for - each surface, and zero-based) - - Attributes - ---------- - brain_structure : str - A string from the BrainStructure list to identify what surface this - vertex list is from (usually left cortex, right cortex, or cerebellum). - """ - - def __init__(self, brain_structure=None, vertices=None): - self._vertices = [] - if vertices is not None: - self.extend(vertices) - - self.brain_structure = brain_structure - - def __len__(self): - return len(self._vertices) - - def __delitem__(self, index): - del self._vertices[index] - - def __getitem__(self, index): - return self._vertices[index] - - def __setitem__(self, index, value): - try: - value = int(value) - self._vertices[index] = value - except ValueError: - raise ValueError('value must be an int') - - def insert(self, index, value): - try: - value = int(value) - self._vertices.insert(index, value) - except ValueError: - raise ValueError('value must be an int') - - def _to_xml_element(self): - if self.brain_structure is None: - raise Cifti2HeaderError('Vertices element require a BrainStructure') - - vertices = xml.Element('Vertices') - vertices.attrib['BrainStructure'] = str(self.brain_structure) - - vertices.text = ' '.join([str(i) for i in self]) - return vertices - - -class Cifti2Parcel(xml.XmlSerializable): - """CIFTI-2 parcel: association of a name with vertices and/or voxels - - * Description - Associates a name, plus vertices and/or voxels, with an - index. - * Attributes - - * Name - The name of the parcel - - * Child Elements - - * Vertices (0...N) - * VoxelIndicesIJK (0...1) - - * Text Content: [NA] - * Parent Element - MatrixIndicesMap - - Attributes - ---------- - name : str - Name of parcel - voxel_indices_ijk : None or Cifti2VoxelIndicesIJK - Voxel indices associated with parcel - vertices : list of Cifti2Vertices - Vertices associated with parcel - """ - - def __init__(self, name=None, voxel_indices_ijk=None, vertices=None): - self.name = name - self._voxel_indices_ijk = voxel_indices_ijk - self.vertices = vertices if vertices is not None else [] - for val in self.vertices: - if not isinstance(val, Cifti2Vertices): - raise ValueError('Cifti2Parcel vertices must be instances of Cifti2Vertices') - - @property - def voxel_indices_ijk(self): - return self._voxel_indices_ijk - - @voxel_indices_ijk.setter - def voxel_indices_ijk(self, value): - self._voxel_indices_ijk = _value_if_klass(value, Cifti2VoxelIndicesIJK) - - def append_cifti_vertices(self, vertices): - """Appends a Cifti2Vertices element to the Cifti2Parcel - - Parameters - ---------- - vertices : Cifti2Vertices - """ - if not isinstance(vertices, Cifti2Vertices): - raise TypeError('Not a valid Cifti2Vertices instance') - self.vertices.append(vertices) - - def pop_cifti2_vertices(self, ith): - """Pops the ith vertices element from the Cifti2Parcel""" - self.vertices.pop(ith) - - def _to_xml_element(self): - if self.name is None: - raise Cifti2HeaderError('Parcel element requires a name') - - parcel = xml.Element('Parcel') - parcel.attrib['Name'] = str(self.name) - if self.voxel_indices_ijk: - parcel.append(self.voxel_indices_ijk._to_xml_element()) - for vertex in self.vertices: - parcel.append(vertex._to_xml_element()) - return parcel - - -class Cifti2TransformationMatrixVoxelIndicesIJKtoXYZ(xml.XmlSerializable): - """Matrix that translates voxel indices to spatial coordinates - - * Description - Contains a matrix that translates Voxel IJK Indices to - spatial XYZ coordinates (+X=>right, +Y=>anterior, +Z=> superior). The - resulting coordinate is the center of the voxel. - * Attributes - - * MeterExponent - Integer, specifies that the coordinate result from - the transformation matrix should be multiplied by 10 to this power to - get the spatial coordinates in meters (e.g., if this is "-3", then - the transformation matrix is in millimeters). - - * Child Elements: [NA] - * Text Content - Sixteen floating-point values, in row-major order, that - form a 4x4 homogeneous transformation matrix. - * Parent Element - Volume - - Attributes - ---------- - meter_exponent : int - See attribute description above. - matrix : array-like shape (4, 4) - Affine transformation matrix from voxel indices to RAS space. - """ - - # meterExponent = int - # matrix = np.array - - def __init__(self, meter_exponent=None, matrix=None): - self.meter_exponent = meter_exponent - self.matrix = matrix - - def _to_xml_element(self): - if self.matrix is None: - raise Cifti2HeaderError( - 'TransformationMatrixVoxelIndicesIJKtoXYZ element requires a matrix' - ) - trans = xml.Element('TransformationMatrixVoxelIndicesIJKtoXYZ') - trans.attrib['MeterExponent'] = str(self.meter_exponent) - trans.text = '\n'.join(' '.join(map('{:.10f}'.format, row)) for row in self.matrix) - return trans - - -class Cifti2Volume(xml.XmlSerializable): - """CIFTI-2 volume: information about a volume for mappings that use voxels - - * Description - Provides information about the volume for any mappings that - use voxels. - * Attributes - - * VolumeDimensions - Three integer values separated by commas, the - lengths of the three volume file dimensions that are related to - spatial coordinates, in number of voxels. Voxel indices (which are - zero-based) that are used in the mapping that this element applies to - must be within these dimensions. - - * Child Elements - - * TransformationMatrixVoxelIndicesIJKtoXYZ (1) - - * Text Content: [NA] - * Parent Element - MatrixIndicesMap - - Attributes - ---------- - volume_dimensions : array-like shape (3,) - See attribute description above. - transformation_matrix_voxel_indices_ijk_to_xyz \ - : Cifti2TransformationMatrixVoxelIndicesIJKtoXYZ - Matrix that translates voxel indices to spatial coordinates - """ - - def __init__(self, volume_dimensions=None, transform_matrix=None): - self.volume_dimensions = volume_dimensions - self.transformation_matrix_voxel_indices_ijk_to_xyz = transform_matrix - - def _to_xml_element(self): - if self.volume_dimensions is None: - raise Cifti2HeaderError('Volume element requires dimensions') - - volume = xml.Element('Volume') - volume.attrib['VolumeDimensions'] = ','.join([str(val) for val in self.volume_dimensions]) - volume.append(self.transformation_matrix_voxel_indices_ijk_to_xyz._to_xml_element()) - return volume - - -class Cifti2VertexIndices(xml.XmlSerializable, MutableSequence): - """CIFTI-2 vertex indices: vertex indices for an associated brain model - - The vertex indices (which are independent for each surface, and - zero-based) that are used in this brain model[.] The parent - BrainModel's ``index_count`` indicates the number of indices. - - * Description - Contains a list of vertex indices for a BrainModel with - ModelType equal to CIFTI_MODEL_TYPE_SURFACE. - * Attributes: [NA] - * Child Elements: [NA] - * Text Content - The vertex indices (which are independent for each - surface, and zero-based) that are used in this brain model, with each - index separated by a whitespace character. The parent BrainModel's - IndexCount attribute indicates the number of indices in this element's - content. - * Parent Element - BrainModel - """ - - def __init__(self, indices=None): - self._indices = [] - if indices is not None: - self.extend(indices) - - def __len__(self): - return len(self._indices) - - def __delitem__(self, index): - del self._indices[index] - - def __getitem__(self, index): - return self._indices[index] - - def __setitem__(self, index, value): - try: - value = int(value) - self._indices[index] = value - except ValueError: - raise ValueError('value must be an int') - - def insert(self, index, value): - try: - value = int(value) - self._indices.insert(index, value) - except ValueError: - raise ValueError('value must be an int') - - def _to_xml_element(self): - if len(self) == 0: - raise Cifti2HeaderError('VertexIndices element requires indices') - - vert_indices = xml.Element('VertexIndices') - vert_indices.text = ' '.join([str(i) for i in self]) - return vert_indices - - -class Cifti2BrainModel(xml.XmlSerializable): - """Element representing a mapping of the dimension to vertex or voxels. - - Mapping to vertices of voxels must be specified. - - * Description - Maps a range of indices to surface vertices or voxels when - IndicesMapToDataType is "CIFTI_INDEX_TYPE_BRAIN_MODELS." - * Attributes - - * IndexOffset - The matrix index of the first brainordinate of this - BrainModel. Note that matrix indices are zero-based. - * IndexCount - Number of surface vertices or voxels in this brain - model, must be positive. - * ModelType - Type of model representing the brain structure (surface - or voxels). Valid values are listed in the table below. - * BrainStructure - Identifies the brain structure. Valid values for - BrainStructure are listed in the table below. However, if the needed - structure is not listed in the table, a message should be posted to - the CIFTI Forum so that a standardized name can be created for the - structure and added to the table. - * SurfaceNumberOfVertices - When ModelType is CIFTI_MODEL_TYPE_SURFACE - this attribute contains the actual (or true) number of vertices in - the surface that is associated with this BrainModel. When this - BrainModel represents all vertices in the surface, this value is the - same as IndexCount. When this BrainModel represents only a subset of - the surface's vertices, IndexCount will be less than this value. - - * Child Elements - - * VertexIndices (0...1) - * VoxelIndicesIJK (0...1) - - * Text Content: [NA] - * Parent Element - MatrixIndicesMap - - For ModelType values, see CIFTI_MODEL_TYPES module attribute. - - For BrainStructure values, see CIFTI_BRAIN_STRUCTURES model attribute. - - Attributes - ---------- - index_offset : int - Start of the mapping - index_count : int - Number of elements in the array to be mapped - model_type : str - One of CIFTI_MODEL_TYPES - brain_structure : str - One of CIFTI_BRAIN_STRUCTURES - surface_number_of_vertices : int - Number of vertices in the surface. Use only for surface-type structure - voxel_indices_ijk : Cifti2VoxelIndicesIJK, optional - Indices on the image towards where the array indices are mapped - vertex_indices : Cifti2VertexIndices, optional - Indices of the vertices towards where the array indices are mapped - """ - - def __init__( - self, - index_offset=None, - index_count=None, - model_type=None, - brain_structure=None, - n_surface_vertices=None, - voxel_indices_ijk=None, - vertex_indices=None, - ): - self.index_offset = index_offset - self.index_count = index_count - self.model_type = model_type - self.brain_structure = brain_structure - self.surface_number_of_vertices = n_surface_vertices - - self.voxel_indices_ijk = voxel_indices_ijk - self.vertex_indices = vertex_indices - - @property - def voxel_indices_ijk(self): - return self._voxel_indices_ijk - - @voxel_indices_ijk.setter - def voxel_indices_ijk(self, value): - self._voxel_indices_ijk = _value_if_klass(value, Cifti2VoxelIndicesIJK) - - @property - def vertex_indices(self): - return self._vertex_indices - - @vertex_indices.setter - def vertex_indices(self, value): - self._vertex_indices = _value_if_klass(value, Cifti2VertexIndices) - - def _to_xml_element(self): - brain_model = xml.Element('BrainModel') - - for key in ( - 'IndexOffset', - 'IndexCount', - 'ModelType', - 'BrainStructure', - 'SurfaceNumberOfVertices', - ): - attr = _underscore(key) - value = getattr(self, attr) - if value is not None: - brain_model.attrib[key] = str(value) - if self.voxel_indices_ijk: - brain_model.append(self.voxel_indices_ijk._to_xml_element()) - if self.vertex_indices: - brain_model.append(self.vertex_indices._to_xml_element()) - return brain_model - - -class Cifti2MatrixIndicesMap(xml.XmlSerializable, MutableSequence): - """Class for Matrix Indices Map - - * Description - Provides a mapping between matrix indices and their - interpretation. - * Attributes - - * AppliesToMatrixDimension - Lists the dimension(s) of the matrix to - which this MatrixIndicesMap applies. The dimensions of the matrix - start at zero (dimension 0 describes the indices along the first - dimension, dimension 1 describes the indices along the second - dimension, etc.). If this MatrixIndicesMap applies to more than one - matrix dimension, the values are separated by a comma. - * IndicesMapToDataType - Type of data to which the MatrixIndicesMap - applies. - * NumberOfSeriesPoints - Indicates how many samples there are in a - series mapping type. For example, this could be the number of - timepoints in a timeseries. - * SeriesExponent - Integer, SeriesStart and SeriesStep must be - multiplied by 10 raised to the power of the value of this attribute - to give the actual values assigned to indices (e.g., if SeriesStart - is "5" and SeriesExponent is "-3", the value of the first series - point is 0.005). - * SeriesStart - Indicates what quantity should be assigned to the first - series point. - * SeriesStep - Indicates amount of change between each series point. - * SeriesUnit - Indicates the unit of the result of multiplying - SeriesStart and SeriesStep by 10 to the power of SeriesExponent. - - * Child Elements - - * BrainModel (0...N) - * NamedMap (0...N) - * Parcel (0...N) - * Surface (0...N) - * Volume (0...1) - - * Text Content: [NA] - * Parent Element - Matrix - - Attributes - ---------- - applies_to_matrix_dimension : list of ints - Dimensions of this matrix that follow this mapping - indices_map_to_data_type : str one of CIFTI_MAP_TYPES - Type of mapping to the matrix indices - number_of_series_points : int, optional - If it is a series, number of points in the series - series_exponent : int, optional - If it is a series the exponent of the increment - series_start : float, optional - If it is a series, starting time - series_step : float, optional - If it is a series, step per element - series_unit : str, optional - 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, - indices_map_to_data_type, - number_of_series_points=None, - series_exponent=None, - series_start=None, - series_step=None, - series_unit=None, - maps=[], - ): - self.applies_to_matrix_dimension = applies_to_matrix_dimension - self.indices_map_to_data_type = indices_map_to_data_type - self.number_of_series_points = number_of_series_points - self.series_exponent = series_exponent - self.series_start = series_start - self.series_step = series_step - self.series_unit = series_unit - self._maps = [] - for m in maps: - self.append(m) - - def __len__(self): - return len(self._maps) - - def __delitem__(self, index): - del self._maps[index] - - def __getitem__(self, index): - return self._maps[index] - - def __setitem__(self, index, value): - if isinstance(value, Cifti2Volume) and ( - self.volume is not None and not isinstance(self._maps[index], Cifti2Volume) - ): - raise Cifti2HeaderError('Only one Volume can be in a MatrixIndicesMap') - self._maps[index] = value - - def insert(self, index, value): - if isinstance(value, Cifti2Volume) and self.volume is not None: - raise Cifti2HeaderError('Only one Volume can be in a MatrixIndicesMap') - - self._maps.insert(index, value) - - @property - def named_maps(self): - for p in self: - if isinstance(p, Cifti2NamedMap): - yield p - - @property - def surfaces(self): - for p in self: - if isinstance(p, Cifti2Surface): - yield p - - @property - def parcels(self): - for p in self: - if isinstance(p, Cifti2Parcel): - yield p - - @property - def volume(self): - for p in self: - if isinstance(p, Cifti2Volume): - return p - return None - - @volume.setter - def volume(self, volume): - if not isinstance(volume, Cifti2Volume): - raise ValueError('You can only set a volume with a volume') - for i, v in enumerate(self): - if isinstance(v, Cifti2Volume): - break - else: - self.append(volume) - return - self[i] = volume - - @volume.deleter - def volume(self): - for i, v in enumerate(self): - if isinstance(v, Cifti2Volume): - break - else: - raise ValueError('No Cifti2Volume element') - del self[i] - - @property - def brain_models(self): - for p in self: - if isinstance(p, Cifti2BrainModel): - yield p - - def _to_xml_element(self): - if self.applies_to_matrix_dimension is None: - raise Cifti2HeaderError( - 'MatrixIndicesMap element requires to be applied to at least 1 dimension' - ) - - mat_ind_map = xml.Element('MatrixIndicesMap') - dims_as_strings = [str(dim) for dim in self.applies_to_matrix_dimension] - mat_ind_map.attrib['AppliesToMatrixDimension'] = ','.join(dims_as_strings) - for key in ( - 'IndicesMapToDataType', - 'NumberOfSeriesPoints', - 'SeriesExponent', - 'SeriesStart', - 'SeriesStep', - 'SeriesUnit', - ): - attr = _underscore(key) - value = getattr(self, attr) - if value is not None: - mat_ind_map.attrib[key] = str(value) - for map_ in self: - mat_ind_map.append(map_._to_xml_element()) - - return mat_ind_map - - -class Cifti2Matrix(xml.XmlSerializable, MutableSequence): - """CIFTI-2 Matrix object - - This is a list-like container where the elements are instances of - :class:`Cifti2MatrixIndicesMap`. - - * Description: contains child elements that describe the meaning of the - values in the matrix. - * Attributes: [NA] - * Child Elements - - * MetaData (0 .. 1) - * MatrixIndicesMap (1 .. N) - - * Text Content: [NA] - * Parent Element: CIFTI - - For each matrix (data) dimension, exactly one MatrixIndicesMap element must - list it in the AppliesToMatrixDimension attribute. - """ - - def __init__(self): - self._mims = [] - self.metadata = None - - @property - def metadata(self): - return self._meta - - @metadata.setter - def metadata(self, meta): - """Set the metadata for this Cifti2Header - - Parameters - ---------- - meta : Cifti2MetaData - - Returns - ------- - None - """ - self._meta = _value_if_klass(meta, Cifti2MetaData) - - def _get_indices_from_mim(self, mim): - applies_to_matrix_dimension = mim.applies_to_matrix_dimension - if not isinstance(applies_to_matrix_dimension, Iterable): - applies_to_matrix_dimension = (int(applies_to_matrix_dimension),) - return applies_to_matrix_dimension - - @property - def mapped_indices(self): - """ - List of matrix indices that are mapped - """ - mapped_indices = [] - for v in self: - a2md = self._get_indices_from_mim(v) - mapped_indices += a2md - return mapped_indices - - def get_index_map(self, index): - """ - Cifti2 Mapping class for a given index - - Parameters - ---------- - index : int - Index for which we want to obtain the mapping. - Must be in the mapped_indices sequence. - - Returns - ------- - cifti2_map : Cifti2MatrixIndicesMap - Returns the Cifti2MatrixIndicesMap corresponding to - the given index. - """ - - for v in self: - a2md = self._get_indices_from_mim(v) - if index in a2md: - return v - raise Cifti2HeaderError('Index not mapped') - - def _validate_new_mim(self, value): - if value.applies_to_matrix_dimension is None: - raise Cifti2HeaderError( - 'Cifti2MatrixIndicesMap needs to have ' - 'the applies_to_matrix_dimension attribute set' - ) - a2md = self._get_indices_from_mim(value) - if not set(self.mapped_indices).isdisjoint(a2md): - raise Cifti2HeaderError( - 'Indices in this Cifti2MatrixIndicesMap already mapped in this matrix' - ) - - def __setitem__(self, key, value): - if not isinstance(value, Cifti2MatrixIndicesMap): - raise TypeError('Not a valid Cifti2MatrixIndicesMap instance') - self._validate_new_mim(value) - self._mims[key] = value - - def __getitem__(self, key): - return self._mims[key] - - def __delitem__(self, key): - del self._mims[key] - - def __len__(self): - return len(self._mims) - - def insert(self, index, value): - if not isinstance(value, Cifti2MatrixIndicesMap): - raise TypeError('Not a valid Cifti2MatrixIndicesMap instance') - self._validate_new_mim(value) - self._mims.insert(index, value) - - def _to_xml_element(self): - # From the spec: "For each matrix dimension, exactly one - # MatrixIndicesMap element must list it in the AppliesToMatrixDimension - # attribute." - mat = xml.Element('Matrix') - if self.metadata: - mat.append(self.metadata._to_xml_element()) - for mim in self._mims: - mat.append(mim._to_xml_element()) - return mat - - def get_axis(self, index): - """ - Generates the Cifti2 axis for a given dimension - - Parameters - ---------- - index : int - Dimension for which we want to obtain the mapping. - - Returns - ------- - axis : :class:`.cifti2_axes.Axis` - """ - from . import cifti2_axes - - return cifti2_axes.from_index_mapping(self.get_index_map(index)) - - def get_data_shape(self): - """ - Returns data shape expected based on the CIFTI-2 header - - Any dimensions omitted in the CIFTI-2 header will be given a default size of None. - """ - from . import cifti2_axes - - if len(self.mapped_indices) == 0: - return () - base_shape = [None] * (max(self.mapped_indices) + 1) - for mim in self: - size = len(cifti2_axes.from_index_mapping(mim)) - for idx in mim.applies_to_matrix_dimension: - base_shape[idx] = size - return tuple(base_shape) - - -class Cifti2Header(FileBasedHeader, xml.XmlSerializable): - """Class for CIFTI-2 header extension""" - - def __init__(self, matrix=None, version='2.0'): - FileBasedHeader.__init__(self) - xml.XmlSerializable.__init__(self) - if matrix is None: - matrix = Cifti2Matrix() - self.matrix = matrix - self.version = version - - def _to_xml_element(self): - cifti = xml.Element('CIFTI') - cifti.attrib['Version'] = str(self.version) - mat_xml = self.matrix._to_xml_element() - if mat_xml is not None: - cifti.append(mat_xml) - return cifti - - def __eq__(self, other): - return self.to_xml() == other.to_xml() - - @classmethod - def may_contain_header(klass, binaryblock): - from .parse_cifti2 import _Cifti2AsNiftiHeader - - return _Cifti2AsNiftiHeader.may_contain_header(binaryblock) - - @property - def number_of_mapped_indices(self): - """ - Number of mapped indices - """ - return len(self.matrix) - - @property - def mapped_indices(self): - """ - List of matrix indices that are mapped - """ - return self.matrix.mapped_indices - - def get_index_map(self, index): - """ - Cifti2 Mapping class for a given index - - Parameters - ---------- - index : int - Index for which we want to obtain the mapping. - Must be in the mapped_indices sequence. - - Returns - ------- - cifti2_map : Cifti2MatrixIndicesMap - Returns the Cifti2MatrixIndicesMap corresponding to - the given index. - """ - return self.matrix.get_index_map(index) - - def get_axis(self, index): - """ - Generates the Cifti2 axis for a given dimension - - Parameters - ---------- - index : int - Dimension for which we want to obtain the mapping. - - Returns - ------- - axis : :class:`.cifti2_axes.Axis` - """ - return self.matrix.get_axis(index) - - @classmethod - def from_axes(cls, axes): - """ - Creates a new Cifti2 header based on the Cifti2 axes - - Parameters - ---------- - axes : tuple of :class`.cifti2_axes.Axis` - sequence of Cifti2 axes describing each row/column of the matrix to be stored - - Returns - ------- - header : Cifti2Header - new header describing the rows/columns in a format consistent with Cifti2 - """ - from . import cifti2_axes - - return cifti2_axes.to_header(axes) - - -class Cifti2Image(DataobjImage, SerializableImage): - """Class for single file CIFTI-2 format image""" - - header_class = Cifti2Header - header: Cifti2Header - valid_exts = Nifti2Image.valid_exts - files_types = Nifti2Image.files_types - makeable = False - rw = True - - def __init__( - self, - dataobj=None, - header=None, - nifti_header=None, - extra=None, - file_map=None, - dtype=None, - ): - """Initialize image - - The image is a combination of (dataobj, header), with optional metadata - in `nifti_header` (a NIfTI2 header). There may be more metadata in the - mapping `extra`. Filename / file-like objects can also go in the - `file_map` mapping. - - Parameters - ---------- - dataobj : object - Object containing image data. It should be some object that - returns an array from ``np.asanyarray``. It should have a - ``shape`` attribute or property. - header : Cifti2Header instance or sequence of :class:`cifti2_axes.Axis` - Header with data for / from XML part of CIFTI-2 format. - Alternatively a sequence of cifti2_axes.Axis objects can be provided - describing each dimension of the array. - nifti_header : None or mapping or NIfTI2 header instance, optional - Metadata for NIfTI2 component of this format. - extra : None or mapping - Extra metadata not captured by `header` or `nifti_header`. - file_map : mapping, optional - Mapping giving file information for this image format. - """ - if not isinstance(header, Cifti2Header) and header: - header = Cifti2Header.from_axes(header) - super().__init__(dataobj, header=header, extra=extra, file_map=file_map) - self._nifti_header = LimitedNifti2Header.from_header(nifti_header) - - # if NIfTI header not specified, get data type from input array - if dtype is not None: - self.set_data_dtype(dtype) - elif nifti_header is None and hasattr(dataobj, 'dtype'): - self.set_data_dtype(dataobj.dtype) - self.update_headers() - - if self._dataobj.shape != self.header.matrix.get_data_shape(): - warn( - f'Dataobj shape {self._dataobj.shape} does not match shape ' - f'expected from CIFTI-2 header {self.header.matrix.get_data_shape()}' - ) - - @property - def nifti_header(self): - return self._nifti_header - - @classmethod - def from_file_map(klass, file_map, *, mmap=True, keep_file_open=None): - """Load a CIFTI-2 image from a file_map - - Parameters - ---------- - file_map : file_map - - Returns - ------- - img : Cifti2Image - Returns a Cifti2Image - """ - from .parse_cifti2 import Cifti2Extension, _Cifti2AsNiftiImage - - nifti_img = _Cifti2AsNiftiImage.from_file_map( - file_map, mmap=mmap, keep_file_open=keep_file_open - ) - - # Get cifti2 header - for item in nifti_img.header.extensions: - if isinstance(item, Cifti2Extension): - cifti_header = item.get_content() - break - else: - raise ValueError('NIfTI2 header does not contain a CIFTI-2 extension') - - # Construct cifti image. - # Use array proxy object where possible - dataobj = nifti_img.dataobj - return Cifti2Image( - reshape_dataobj(dataobj, dataobj.shape[4:]), - header=cifti_header, - nifti_header=nifti_img.header, - file_map=file_map, - ) - - @classmethod - def from_image(klass, img): - """Class method to create new instance of own class from `img` - - Parameters - ---------- - img : instance - In fact, an object with the API of :class:`DataobjImage`. - - Returns - ------- - cimg : instance - Image, of our own class - """ - if isinstance(img, klass): - return img - raise NotImplementedError - - def to_file_map(self, file_map=None, dtype=None): - """Write image to `file_map` or contained ``self.file_map`` - - Parameters - ---------- - file_map : None or mapping, optional - files mapping. If None (default) use object's ``file_map`` - attribute instead. - - Returns - ------- - None - """ - from .parse_cifti2 import Cifti2Extension - - self.update_headers() - header = self._nifti_header - extension = Cifti2Extension.from_bytes(self.header.to_xml()) - header.extensions = Nifti1Extensions( - ext for ext in header.extensions if not isinstance(ext, Cifti2Extension) - ) - header.extensions.append(extension) - if self._dataobj.shape != self.header.matrix.get_data_shape(): - raise ValueError( - f'Dataobj shape {self._dataobj.shape} does not match shape ' - f'expected from CIFTI-2 header {self.header.matrix.get_data_shape()}' - ) - # if intent code is not set, default to unknown CIFTI - if header.get_intent()[0] == 'none': - header.set_intent('NIFTI_INTENT_CONNECTIVITY_UNKNOWN') - data = reshape_dataobj(self.dataobj, (1, 1, 1, 1) + self.dataobj.shape) - # If qform not set, reset pixdim values so Nifti2 does not complain - if header['qform_code'] == 0: - header['pixdim'][:4] = 1 - img = Nifti2Image(data, None, header, dtype=dtype) - img.to_file_map(file_map or self.file_map) - - def update_headers(self): - """Harmonize NIfTI headers with image data - - Ensures that the NIfTI-2 header records the data shape in the last three - ``dim`` fields. Per the spec: - - Because the first four dimensions in NIfTI are reserved for space and time, the CIFTI - dimensions are stored in the NIfTI header in dim[5] and up, where dim[5] is the length - of the first CIFTI dimension (number of values in a row), dim[6] is the length of the - second CIFTI dimension, and dim[7] is the length of the third CIFTI dimension, if - applicable. The fields dim[1] through dim[4] will be 1; dim[0] will be 6 or 7, - depending on whether a third matrix dimension exists. - - >>> import numpy as np - >>> data = np.zeros((2,3,4)) - >>> img = Cifti2Image(data) # doctest: +IGNORE_WARNINGS - >>> img.shape == (2, 3, 4) - True - >>> img.update_headers() - >>> img.nifti_header.get_data_shape() == (1, 1, 1, 1, 2, 3, 4) - True - >>> img.shape == (2, 3, 4) - True - """ - self._nifti_header.set_data_shape((1, 1, 1, 1) + self._dataobj.shape) - - def get_data_dtype(self): - return self._nifti_header.get_data_dtype() - - def set_data_dtype(self, dtype): - self._nifti_header.set_data_dtype(dtype) - - -load = Cifti2Image.from_filename -save = Cifti2Image.instance_to_filename diff --git a/nibabel/cifti2/cifti2_axes.py b/nibabel/cifti2/cifti2_axes.py deleted file mode 100644 index 54dfc79179..0000000000 --- a/nibabel/cifti2/cifti2_axes.py +++ /dev/null @@ -1,1526 +0,0 @@ -""" -Defines :class:`Axis` objects to create, read, and manipulate CIFTI-2 files - -These axes provide an alternative interface to the information in the CIFTI-2 header. -Each type of CIFTI-2 axes describing the rows/columns in a CIFTI-2 matrix is given a unique class: - -* :class:`BrainModelAxis`: each row/column is a voxel or vertex -* :class:`ParcelsAxis`: each row/column is a group of voxels and/or vertices -* :class:`ScalarAxis`: each row/column has a unique name (with optional meta-data) -* :class:`LabelAxis`: each row/column has a unique name and label table (with optional meta-data) -* :class:`SeriesAxis`: each row/column is a timepoint, which increases monotonically - -All of these classes are derived from the Axis class. - -After loading a CIFTI-2 file a tuple of axes describing the rows and columns can be obtained -from the :meth:`.cifti2.Cifti2Header.get_axis` method on the header object -(e.g. ``nibabel.load().header.get_axis()``). Inversely, a new -:class:`.cifti2.Cifti2Header` object can be created from existing Axis objects -using the :meth:`.cifti2.Cifti2Header.from_axes` factory method. - -CIFTI-2 Axis objects of the same type can be concatenated using the '+'-operator. -Numpy indexing also works on axes -(except for SeriesAxis objects, which have to remain monotonically increasing or decreasing). - -Creating new CIFTI-2 axes -------------------------- -New Axis objects can be constructed by providing a description for what is contained -in each row/column of the described tensor. For each Axis sub-class this descriptor is: - -* :class:`BrainModelAxis`: a CIFTI-2 structure name and a voxel or vertex index -* :class:`ParcelsAxis`: a name and a sequence of voxel and vertex indices -* :class:`ScalarAxis`: a name and optionally a dict of meta-data -* :class:`LabelAxis`: a name, dict of label index to name and colour, - and optionally a dict of meta-data -* :class:`SeriesAxis`: the time-point of each row/column is set by setting the start, stop, size, - and unit of the time-series - -Several helper functions exist to create new :class:`BrainModelAxis` axes: - -* :meth:`BrainModelAxis.from_mask` creates a new BrainModelAxis volume covering the - non-zero values of a mask -* :meth:`BrainModelAxis.from_surface` creates a new BrainModelAxis surface covering the provided - indices of a surface - -A :class:`ParcelsAxis` axis can be created from a sequence of :class:`BrainModelAxis` axes using -:meth:`ParcelsAxis.from_brain_models`. - -Examples --------- -We can create brain models covering the left cortex and left thalamus using: - ->>> from nibabel import cifti2 ->>> import numpy as np ->>> bm_cortex = cifti2.BrainModelAxis.from_mask([True, False, True, True], -... name='cortex_left') ->>> bm_thal = cifti2.BrainModelAxis.from_mask(np.ones((2, 2, 2)), affine=np.eye(4), -... name='thalamus_left') - -In this very simple case ``bm_cortex`` describes a left cortical surface skipping the second -out of four vertices. ``bm_thal`` contains all voxels in a 2x2x2 volume. - -Brain structure names automatically get converted to valid CIFTI-2 identifiers using -:meth:`BrainModelAxis.to_cifti_brain_structure_name`. -A 1-dimensional mask will be automatically interpreted as a surface element and a 3-dimensional -mask as a volume element. - -These can be concatenated in a single brain model covering the left cortex and thalamus by -simply adding them together - ->>> bm_full = bm_cortex + bm_thal - -Brain models covering the full HCP grayordinate space can be constructed by adding all the -volumetric and surface brain models together like this (or by reading one from an already -existing HCP file). - -Getting a specific brain region from the full brain model is as simple as: - ->>> assert bm_full[bm_full.name == 'CIFTI_STRUCTURE_CORTEX_LEFT'] == bm_cortex ->>> assert bm_full[bm_full.name == 'CIFTI_STRUCTURE_THALAMUS_LEFT'] == bm_thal - -You can also iterate over all brain structures in a brain model: - ->>> for idx, (name, slc, bm) in enumerate(bm_full.iter_structures()): -... print((str(name), slc)) -... assert bm == bm_full[slc] -... assert bm == bm_cortex if idx == 0 else bm_thal -('CIFTI_STRUCTURE_CORTEX_LEFT', slice(0, 3, None)) -('CIFTI_STRUCTURE_THALAMUS_LEFT', slice(3, None, None)) - -In this case there will be two iterations, namely: -('CIFTI_STRUCTURE_CORTEX_LEFT', slice(0, ), bm_cortex) -and -('CIFTI_STRUCTURE_THALAMUS_LEFT', slice(, None), bm_thal) - -ParcelsAxis can be constructed from selections of these brain models: - ->>> parcel = cifti2.ParcelsAxis.from_brain_models([ -... ('surface_parcel', bm_cortex[:2]), # contains first 2 cortical vertices -... ('volume_parcel', bm_thal), # contains thalamus -... ('combined_parcel', bm_full[[1, 8, 10]]), # contains selected voxels/vertices -... ]) - -Time series are represented by their starting time (typically 0), step size -(i.e. sampling time or TR), and number of elements: - ->>> series = cifti2.SeriesAxis(start=0, step=100, size=5000) - -So a header for fMRI data with a TR of 100 ms covering the left cortex and thalamus with -5000 timepoints could be created with - ->>> type(cifti2.Cifti2Header.from_axes((series, bm_cortex + bm_thal))) - - -Similarly the curvature and cortical thickness on the left cortex could be stored using a header -like: - ->>> type(cifti2.Cifti2Header.from_axes((cifti2.ScalarAxis(['curvature', 'thickness']), -... bm_cortex))) - -""" - -import abc -from operator import xor - -import numpy as np - -from . import cifti2 - - -def from_index_mapping(mim): - """ - Parses the MatrixIndicesMap to find the appropriate CIFTI-2 axis describing the rows or columns - - Parameters - ---------- - mim : :class:`.cifti2.Cifti2MatrixIndicesMap` - - Returns - ------- - axis : subclass of :class:`Axis` - """ - return_type = { - 'CIFTI_INDEX_TYPE_SCALARS': ScalarAxis, - 'CIFTI_INDEX_TYPE_LABELS': LabelAxis, - 'CIFTI_INDEX_TYPE_SERIES': SeriesAxis, - 'CIFTI_INDEX_TYPE_BRAIN_MODELS': BrainModelAxis, - 'CIFTI_INDEX_TYPE_PARCELS': ParcelsAxis, - } - return return_type[mim.indices_map_to_data_type].from_index_mapping(mim) - - -def to_header(axes): - """ - Converts the axes describing the rows/columns of a CIFTI-2 vector/matrix to a Cifti2Header - - Parameters - ---------- - axes : iterable of :py:class:`Axis` objects - one or more axes describing each dimension in turn - - Returns - ------- - header : :class:`.cifti2.Cifti2Header` - """ - axes = tuple(axes) - mims_all = [] - matrix = cifti2.Cifti2Matrix() - for dim, ax in enumerate(axes): - if ax in axes[:dim]: - dim_prev = axes.index(ax) - mims_all[dim_prev].applies_to_matrix_dimension.append(dim) - mims_all.append(mims_all[dim_prev]) - else: - mim = ax.to_mapping(dim) - mims_all.append(mim) - matrix.append(mim) - return cifti2.Cifti2Header(matrix) - - -class Axis(abc.ABC): - """ - Abstract class for any object describing the rows or columns of a CIFTI-2 vector/matrix - - Mainly used for type checking. - - Base class for the following concrete CIFTI-2 axes: - - * :class:`BrainModelAxis`: each row/column is a voxel or vertex - * :class:`ParcelsAxis`: each row/column is a group of voxels and/or vertices - * :class:`ScalarAxis`: each row/column has a unique name with optional meta-data - * :class:`LabelAxis`: each row/column has a unique name and label table with optional meta-data - * :class:`SeriesAxis`: each row/column is a timepoint, which increases monotonically - """ - - @property - def size(self): - return len(self) - - @abc.abstractmethod - def __len__(self): - pass - - @abc.abstractmethod - def __eq__(self, other): - """ - Compares whether two Axes are equal - - Parameters - ---------- - other : Axis - other axis to compare to - - Returns - ------- - False if the axes don't have the same type or if their content differs - """ - pass - - @abc.abstractmethod - def __add__(self, other): - """ - Concatenates two Axes of the same type - - Parameters - ---------- - other : Axis - axis to be appended to the current one - - Returns - ------- - Axis of the same subtype as self and other - """ - pass - - @abc.abstractmethod - def __getitem__(self, item): - """ - Extracts definition of single row/column or new Axis describing a subset of the rows/columns - """ - pass - - -class BrainModelAxis(Axis): - """ - Each row/column in the CIFTI-2 vector/matrix represents a single vertex or voxel - - This Axis describes which vertex/voxel is represented by each row/column. - """ - - def __init__( - self, name, voxel=None, vertex=None, affine=None, volume_shape=None, nvertices=None - ): - """ - New BrainModelAxis axes can be constructed by passing on the greyordinate brain-structure - names and voxel/vertex indices to the constructor or by one of the - factory methods: - - - :py:meth:`~BrainModelAxis.from_mask`: creates surface or volumetric BrainModelAxis axis - from respectively 1D or 3D masks - - :py:meth:`~BrainModelAxis.from_surface`: creates a surface BrainModelAxis axis - - The resulting BrainModelAxis axes can be concatenated by adding them together. - - Parameters - ---------- - name : array_like - brain structure name or (N, ) string array with the brain structure names - voxel : array_like, optional - (N, 3) array with the voxel indices (can be omitted for CIFTI-2 files only - covering the surface) - vertex : array_like, optional - (N, ) array with the vertex indices (can be omitted for volumetric CIFTI-2 files) - affine : array_like, optional - (4, 4) array mapping voxel indices to mm space (not needed for CIFTI-2 files only - covering the surface) - volume_shape : tuple of three integers, optional - shape of the volume in which the voxels were defined (not needed for CIFTI-2 files only - covering the surface) - nvertices : dict from string to integer, optional - maps names of surface elements to integers (not needed for volumetric CIFTI-2 files) - """ - if voxel is None: - if vertex is None: - raise ValueError('At least one of voxel or vertex indices should be defined') - nelements = len(vertex) - self.voxel = np.full((nelements, 3), fill_value=-1, dtype=int) - else: - nelements = len(voxel) - self.voxel = np.asanyarray(voxel, dtype=int) - - if vertex is None: - self.vertex = np.full(nelements, fill_value=-1, dtype=int) - else: - self.vertex = np.asanyarray(vertex, dtype=int) - - if isinstance(name, str): - name = [self.to_cifti_brain_structure_name(name)] * self.vertex.size - self.name = np.asanyarray(name, dtype='U') - - if nvertices is None: - self.nvertices = {} - else: - self.nvertices = { - self.to_cifti_brain_structure_name(name): number - for name, number in nvertices.items() - } - - for name in list(self.nvertices.keys()): - if name not in self.name: - del self.nvertices[name] - - surface_mask = self.surface_mask - if surface_mask.all(): - self.affine = None - self.volume_shape = None - else: - if affine is None or volume_shape is None: - raise ValueError( - 'Affine and volume shape should be defined ' - 'for BrainModelAxis containing voxels' - ) - self.affine = np.asanyarray(affine) - self.volume_shape = volume_shape - - if np.any(self.vertex[surface_mask] < 0): - raise ValueError('Undefined vertex indices found for surface elements') - if np.any(self.voxel[~surface_mask] < 0): - raise ValueError('Undefined voxel indices found for volumetric elements') - - for check_name in ('name', 'voxel', 'vertex'): - shape = (self.size, 3) if check_name == 'voxel' else (self.size,) - if getattr(self, check_name).shape != shape: - raise ValueError( - f'Input {check_name} has incorrect shape ' - f'({getattr(self, check_name).shape}) for BrainModelAxis axis' - ) - - @classmethod - def from_mask(cls, mask, name='other', affine=None): - """ - Creates a new BrainModelAxis axis describing the provided mask - - Parameters - ---------- - mask : array_like - all non-zero voxels will be included in the BrainModelAxis axis - should be (Nx, Ny, Nz) array for volume mask or (Nvertex, ) array for surface mask - name : str, optional - Name of the brain structure (e.g. 'CortexRight', 'thalamus_left' or 'brain_stem') - affine : array_like, optional - (4, 4) array with the voxel to mm transformation (defaults to identity matrix) - Argument will be ignored for surface masks - - Returns - ------- - BrainModelAxis which covers the provided mask - """ - if affine is None: - affine = np.eye(4) - else: - affine = np.asanyarray(affine) - if affine.shape != (4, 4): - raise ValueError( - f'Affine transformation should be a 4x4 array or None, not {affine!r}' - ) - - mask = np.asanyarray(mask) - if mask.ndim == 1: - return cls.from_surface(np.where(mask != 0)[0], mask.size, name=name) - elif mask.ndim == 3: - voxels = np.array(np.where(mask != 0)).T - return cls(name, voxel=voxels, affine=affine, volume_shape=mask.shape) - else: - raise ValueError( - 'Mask should be either 1-dimensional (for surfaces) or ' - f'3-dimensional (for volumes), not {mask.ndim}-dimensional' - ) - - @classmethod - def from_surface(cls, vertices, nvertex, name='Other'): - """ - Creates a new BrainModelAxis axis describing the vertices on a surface - - Parameters - ---------- - vertices : array_like - indices of the vertices on the surface - nvertex : int - total number of vertices on the surface - name : str - Name of the brain structure (e.g. 'CortexLeft' or 'CortexRight') - - Returns - ------- - BrainModelAxis which covers (part of) the surface - """ - cifti_name = cls.to_cifti_brain_structure_name(name) - return cls(cifti_name, vertex=vertices, nvertices={cifti_name: nvertex}) - - @classmethod - def from_index_mapping(cls, mim): - """ - Creates a new BrainModel axis based on a CIFTI-2 dataset - - Parameters - ---------- - mim : :class:`.cifti2.Cifti2MatrixIndicesMap` - - Returns - ------- - BrainModelAxis - """ - nbm = sum(bm.index_count for bm in mim.brain_models) - voxel = np.full((nbm, 3), fill_value=-1, dtype=int) - vertex = np.full(nbm, fill_value=-1, dtype=int) - name = [] - - nvertices = {} - affine, shape = None, None - for bm in mim.brain_models: - index_end = bm.index_offset + bm.index_count - is_surface = bm.model_type == 'CIFTI_MODEL_TYPE_SURFACE' - name.extend([bm.brain_structure] * bm.index_count) - if is_surface: - vertex[bm.index_offset : index_end] = bm.vertex_indices - nvertices[bm.brain_structure] = bm.surface_number_of_vertices - else: - voxel[bm.index_offset : index_end, :] = bm.voxel_indices_ijk - if affine is None: - shape = mim.volume.volume_dimensions - affine = mim.volume.transformation_matrix_voxel_indices_ijk_to_xyz.matrix - return cls(name, voxel, vertex, affine, shape, nvertices) - - def to_mapping(self, dim): - """ - Converts the brain model axis to a MatrixIndicesMap for storage in CIFTI-2 format - - Parameters - ---------- - dim : int - which dimension of the CIFTI-2 vector/matrix is described by this dataset (zero-based) - - Returns - ------- - :class:`.cifti2.Cifti2MatrixIndicesMap` - """ - mim = cifti2.Cifti2MatrixIndicesMap([dim], 'CIFTI_INDEX_TYPE_BRAIN_MODELS') - for name, to_slice, bm in self.iter_structures(): - is_surface = name in self.nvertices.keys() - if is_surface: - voxels = None - vertices = cifti2.Cifti2VertexIndices(bm.vertex) - nvertex = self.nvertices[name] - else: - voxels = cifti2.Cifti2VoxelIndicesIJK(bm.voxel) - vertices = None - nvertex = None - if mim.volume is None: - affine = cifti2.Cifti2TransformationMatrixVoxelIndicesIJKtoXYZ(-3, self.affine) - mim.volume = cifti2.Cifti2Volume(self.volume_shape, affine) - cifti_bm = cifti2.Cifti2BrainModel( - to_slice.start, - len(bm), - 'CIFTI_MODEL_TYPE_SURFACE' if is_surface else 'CIFTI_MODEL_TYPE_VOXELS', - name, - nvertex, - voxels, - vertices, - ) - mim.append(cifti_bm) - return mim - - def iter_structures(self): - """ - Iterates over all brain structures in the order that they appear along the axis - - Yields - ------ - tuple with 3 elements: - - CIFTI-2 brain structure name - - slice to select the data associated with the brain structure from the tensor - - brain model covering that specific brain structure - """ - idx_start = 0 - start_name = self.name[idx_start] - for idx_current, name in enumerate(self.name): - if start_name != name: - yield start_name, slice(idx_start, idx_current), self[idx_start:idx_current] - idx_start = idx_current - start_name = self.name[idx_start] - yield start_name, slice(idx_start, None), self[idx_start:] - - @staticmethod - def to_cifti_brain_structure_name(name): - """ - Attempts to convert the name of an anatomical region in a format recognized by CIFTI-2 - - This function returns: - - - the name if it is in the CIFTI-2 format already - - if the name is a tuple the first element is assumed to be the structure name while - the second is assumed to be the hemisphere (left, right or both). The latter will default - to both. - - names like left_cortex, cortex_left, LeftCortex, or CortexLeft will be converted to - CIFTI_STRUCTURE_CORTEX_LEFT - - see :py:func:`nibabel.cifti2.tests.test_name` for examples of - which conversions are possible - - Parameters - ---------- - name: iterable of 2-element tuples of integer and string - input name of an anatomical region - - Returns - ------- - CIFTI-2 compatible name - - Raises - ------ - ValueError: raised if the input name does not match a known anatomical structure in CIFTI-2 - """ - if name in cifti2.CIFTI_BRAIN_STRUCTURES: - return cifti2.CIFTI_BRAIN_STRUCTURES.ciftiname[name] - if not isinstance(name, str): - if len(name) == 1: - structure = name[0] - orientation = 'both' - else: - structure, orientation = name - if structure.lower() in ('left', 'right', 'both'): - orientation, structure = name - else: - orient_names = ('left', 'right', 'both') - for poss_orient in orient_names: - idx = len(poss_orient) - if poss_orient == name.lower()[:idx]: - orientation = poss_orient - if name[idx] in '_ ': - structure = name[idx + 1 :] - else: - structure = name[idx:] - break - if poss_orient == name.lower()[-idx:]: - orientation = poss_orient - if name[-idx - 1] in '_ ': - structure = name[: -idx - 1] - else: - structure = name[:-idx] - break - else: - orientation = 'both' - structure = name - if orientation.lower() == 'both': - proposed_name = f'CIFTI_STRUCTURE_{structure.upper()}' - else: - proposed_name = f'CIFTI_STRUCTURE_{structure.upper()}_{orientation.upper()}' - if proposed_name not in cifti2.CIFTI_BRAIN_STRUCTURES.ciftiname: - raise ValueError( - f'{name} was interpreted as {proposed_name}, ' - 'which is not a valid CIFTI brain structure' - ) - return proposed_name - - @property - def surface_mask(self): - """ - (N, ) boolean array which is true for any element on the surface - """ - return np.vectorize(lambda name: name in self.nvertices.keys())(self.name) - - @property - def volume_mask(self): - """ - (N, ) boolean array which is true for any element on the surface - """ - return np.vectorize(lambda name: name not in self.nvertices.keys())(self.name) - - _affine = None - - @property - def affine(self): - """ - Affine of the volumetric image in which the greyordinate voxels were defined - """ - return self._affine - - @affine.setter - def affine(self, value): - if value is not None: - value = np.asanyarray(value) - if value.shape != (4, 4): - raise ValueError('Affine transformation should be a 4x4 array') - self._affine = value - - _volume_shape = None - - @property - def volume_shape(self): - """ - Shape of the volumetric image in which the greyordinate voxels were defined - """ - return self._volume_shape - - @volume_shape.setter - def volume_shape(self, value): - if value is not None: - value = tuple(value) - if len(value) != 3: - raise ValueError('Volume shape should be a tuple of length 3') - if not all(isinstance(v, int) for v in value): - raise ValueError('All elements of the volume shape should be integers') - self._volume_shape = value - - _name = None - - @property - def name(self): - """The brain structure to which the voxel/vertices of belong""" - return self._name - - @name.setter - def name(self, values): - self._name = np.array([self.to_cifti_brain_structure_name(name) for name in values]) - - def __len__(self): - return self.name.size - - def __eq__(self, other): - if not isinstance(other, BrainModelAxis) or len(self) != len(other): - return False - if xor(self.affine is None, other.affine is None): - return False - return ( - ( - self.affine is None - 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) - and np.array_equal(self.voxel[self.volume_mask], other.voxel[other.volume_mask]) - and np.array_equal(self.vertex[self.surface_mask], other.vertex[other.surface_mask]) - ) - - def __add__(self, other): - """ - Concatenates two BrainModels - - Parameters - ---------- - other : BrainModelAxis - brain model to be appended to the current one - - Returns - ------- - BrainModelAxis - """ - if not isinstance(other, BrainModelAxis): - return NotImplemented - if self.affine is None: - affine, shape = other.affine, other.volume_shape - else: - affine, shape = self.affine, self.volume_shape - if other.affine is not None and ( - not np.allclose(other.affine, affine) or other.volume_shape != shape - ): - raise ValueError( - 'Trying to concatenate two BrainModels defined in a different brain volume' - ) - - nvertices = dict(self.nvertices) - for name, value in other.nvertices.items(): - if name in nvertices.keys() and nvertices[name] != value: - raise ValueError( - 'Trying to concatenate two BrainModels with ' - f'inconsistent number of vertices for {name}' - ) - nvertices[name] = value - return self.__class__( - np.append(self.name, other.name), - np.concatenate((self.voxel, other.voxel), 0), - np.append(self.vertex, other.vertex), - affine, - shape, - nvertices, - ) - - def __getitem__(self, item): - """ - Extracts part of the brain structure - - Parameters - ---------- - item : anything that can index a 1D array - - Returns - ------- - If `item` is an integer returns a tuple with 3 elements: - - boolean, which is True if it is a surface element - - vertex index if it is a surface element, otherwise array with 3 voxel indices - - structure.BrainStructure object describing the brain structure the element was taken from - - Otherwise returns a new BrainModelAxis - """ - if isinstance(item, int): - return self.get_element(item) - if isinstance(item, str): - raise IndexError('Can not index an Axis with a string (except for ParcelsAxis)') - return self.__class__( - self.name[item], - self.voxel[item], - self.vertex[item], - self.affine, - self.volume_shape, - self.nvertices, - ) - - def get_element(self, index): - """ - Describes a single element from the axis - - Parameters - ---------- - index : int - Indexes the row/column of interest - - Returns - ------- - tuple with 3 elements - - str, 'CIFTI_MODEL_TYPE_SURFACE' for vertex or 'CIFTI_MODEL_TYPE_VOXELS' for voxel - - vertex index if it is a surface element, otherwise array with 3 voxel indices - - structure.BrainStructure object describing the brain structure the element was taken from - """ - element_type = 'CIFTI_MODEL_TYPE_' + ( - 'SURFACE' if self.name[index] in self.nvertices.keys() else 'VOXELS' - ) - struct = self.vertex if 'SURFACE' in element_type else self.voxel - return element_type, struct[index], self.name[index] - - -class ParcelsAxis(Axis): - """ - Each row/column in the CIFTI-2 vector/matrix represents a parcel of voxels/vertices - - This Axis describes which parcel is represented by each row/column. - - Individual parcels can be accessed based on their name, using - ``parcel = parcel_axis[name]`` - """ - - def __init__(self, name, voxels, vertices, affine=None, volume_shape=None, nvertices=None): - """ - Use of this constructor is not recommended. New ParcelsAxis axes can be constructed more - easily from a sequence of BrainModelAxis axes using - :py:meth:`~ParcelsAxis.from_brain_models` - - Parameters - ---------- - name : array_like - (N, ) string array with the parcel names - voxels : array_like - (N, ) object array each containing a sequence of voxels. - For each parcel the voxels are represented by a (M, 3) index array - vertices : array_like - (N, ) object array each containing a sequence of vertices. - For each parcel the vertices are represented by a mapping from brain structure name to - (M, ) index array - affine : array_like, optional - (4, 4) array mapping voxel indices to mm space (not needed for CIFTI-2 files only - covering the surface) - volume_shape : tuple of three integers, optional - shape of the volume in which the voxels were defined (not needed for CIFTI-2 files only - covering the surface) - nvertices : dict from string to integer, optional - maps names of surface elements to integers (not needed for volumetric CIFTI-2 files) - """ - self.name = np.asanyarray(name, dtype='U') - self.voxels = np.empty(len(voxels), dtype='object') - for idx, vox in enumerate(voxels): - self.voxels[idx] = vox - self.vertices = np.asanyarray(vertices, dtype='object') - self.affine = np.asanyarray(affine) if affine is not None else None - self.volume_shape = volume_shape - if nvertices is None: - self.nvertices = {} - else: - self.nvertices = { - BrainModelAxis.to_cifti_brain_structure_name(name): number - for name, number in nvertices.items() - } - - for check_name in ('name', 'voxels', 'vertices'): - if getattr(self, check_name).shape != (self.size,): - raise ValueError( - f'Input {check_name} has incorrect shape ' - f'({getattr(self, check_name).shape}) for Parcel axis' - ) - - @classmethod - def from_brain_models(cls, named_brain_models): - """ - Creates a Parcel axis from a list of BrainModelAxis axes with names - - Parameters - ---------- - named_brain_models : iterable of 2-element tuples of string and BrainModelAxis - list of (parcel name, brain model representation) pairs defining each parcel - - Returns - ------- - ParcelsAxis - """ - nparcels = len(named_brain_models) - affine = None - volume_shape = None - all_names = [] - all_voxels = np.zeros(nparcels, dtype='object') - all_vertices = np.zeros(nparcels, dtype='object') - nvertices = {} - for idx_parcel, (parcel_name, bm) in enumerate(named_brain_models): - all_names.append(parcel_name) - - voxels = bm.voxel[bm.volume_mask] - if voxels.shape[0] != 0: - if affine is None: - affine = bm.affine - volume_shape = bm.volume_shape - elif not np.allclose(affine, bm.affine) or (volume_shape != bm.volume_shape): - raise ValueError( - 'Can not combine brain models defined in different ' - 'volumes into a single Parcel axis' - ) - all_voxels[idx_parcel] = voxels - - vertices = {} - for name, _, bm_part in bm.iter_structures(): - if name in bm.nvertices.keys(): - if name in nvertices.keys() and nvertices[name] != bm.nvertices[name]: - raise ValueError( - 'Got multiple conflicting number of ' - f'vertices for surface structure {name}' - ) - nvertices[name] = bm.nvertices[name] - vertices[name] = bm_part.vertex - all_vertices[idx_parcel] = vertices - return ParcelsAxis(all_names, all_voxels, all_vertices, affine, volume_shape, nvertices) - - @classmethod - def from_index_mapping(cls, mim): - """ - Creates a new Parcels axis based on a CIFTI-2 dataset - - Parameters - ---------- - mim : :class:`cifti2.Cifti2MatrixIndicesMap` - - Returns - ------- - ParcelsAxis - """ - nparcels = len(list(mim.parcels)) - all_names = [] - all_voxels = np.zeros(nparcels, dtype='object') - all_vertices = np.zeros(nparcels, dtype='object') - - volume_shape = None if mim.volume is None else mim.volume.volume_dimensions - affine = None - if mim.volume is not None: - affine = mim.volume.transformation_matrix_voxel_indices_ijk_to_xyz.matrix - nvertices = {} - for surface in mim.surfaces: - nvertices[surface.brain_structure] = surface.surface_number_of_vertices - for idx_parcel, parcel in enumerate(mim.parcels): - nvoxels = 0 if parcel.voxel_indices_ijk is None else len(parcel.voxel_indices_ijk) - voxels = np.zeros((nvoxels, 3), dtype='i4') - if nvoxels != 0: - voxels[:] = parcel.voxel_indices_ijk - vertices = {} - for vertex in parcel.vertices: - name = vertex.brain_structure - vertices[vertex.brain_structure] = np.array(vertex) - if name not in nvertices.keys(): - raise ValueError( - f'Number of vertices for surface structure {name} not defined' - ) - all_voxels[idx_parcel] = voxels - all_vertices[idx_parcel] = vertices - all_names.append(parcel.name) - return cls(all_names, all_voxels, all_vertices, affine, volume_shape, nvertices) - - def to_mapping(self, dim): - """ - Converts the Parcel to a MatrixIndicesMap for storage in CIFTI-2 format - - Parameters - ---------- - dim : int - which dimension of the CIFTI-2 vector/matrix is described by this dataset (zero-based) - - Returns - ------- - :class:`cifti2.Cifti2MatrixIndicesMap` - """ - mim = cifti2.Cifti2MatrixIndicesMap([dim], 'CIFTI_INDEX_TYPE_PARCELS') - if self.affine is not None: - affine = cifti2.Cifti2TransformationMatrixVoxelIndicesIJKtoXYZ(-3, matrix=self.affine) - mim.volume = cifti2.Cifti2Volume(self.volume_shape, affine) - for name, nvertex in self.nvertices.items(): - mim.append(cifti2.Cifti2Surface(name, nvertex)) - for name, voxels, vertices in zip(self.name, self.voxels, self.vertices): - cifti_voxels = cifti2.Cifti2VoxelIndicesIJK(voxels) - element = cifti2.Cifti2Parcel(name, cifti_voxels) - for name_vertex, idx_vertices in vertices.items(): - element.vertices.append(cifti2.Cifti2Vertices(name_vertex, idx_vertices)) - mim.append(element) - return mim - - _affine = None - - @property - def affine(self): - """ - Affine of the volumetric image in which the greyordinate voxels were defined - """ - return self._affine - - @affine.setter - def affine(self, value): - if value is not None: - value = np.asanyarray(value) - if value.shape != (4, 4): - raise ValueError('Affine transformation should be a 4x4 array') - self._affine = value - - _volume_shape = None - - @property - def volume_shape(self): - """ - Shape of the volumetric image in which the greyordinate voxels were defined - """ - return self._volume_shape - - @volume_shape.setter - def volume_shape(self, value): - if value is not None: - value = tuple(value) - if len(value) != 3: - raise ValueError('Volume shape should be a tuple of length 3') - if not all(isinstance(v, int) for v in value): - raise ValueError('All elements of the volume shape should be integers') - self._volume_shape = value - - def __len__(self): - return self.name.size - - def __eq__(self, other): - if ( - self.__class__ != other.__class__ - or len(self) != len(other) - or not np.array_equal(self.name, other.name) - or self.nvertices != other.nvertices - or any(not np.array_equal(vox1, vox2) for vox1, vox2 in zip(self.voxels, other.voxels)) - ): - return False - if self.affine is not None: - if ( - other.affine is None - or not np.allclose(self.affine, other.affine) - or self.volume_shape != other.volume_shape - ): - return False - elif other.affine is not None: - return False - for vert1, vert2 in zip(self.vertices, other.vertices): - if len(vert1) != len(vert2): - return False - for name in vert1.keys(): - if name not in vert2 or not np.array_equal(vert1[name], vert2[name]): - return False - return True - - def __add__(self, other): - """ - Concatenates two Parcels - - Parameters - ---------- - other : ParcelsAxis - parcel to be appended to the current one - - Returns - ------- - Parcel - """ - if not isinstance(other, ParcelsAxis): - return NotImplemented - if self.affine is None: - affine, shape = other.affine, other.volume_shape - else: - affine, shape = self.affine, self.volume_shape - if other.affine is not None and ( - not np.allclose(other.affine, affine) or other.volume_shape != shape - ): - raise ValueError( - 'Trying to concatenate two ParcelsAxis defined in a different brain volume' - ) - nvertices = dict(self.nvertices) - for name, value in other.nvertices.items(): - if name in nvertices.keys() and nvertices[name] != value: - raise ValueError( - 'Trying to concatenate two ParcelsAxis with ' - f'inconsistent number of vertices for {name}' - ) - nvertices[name] = value - return self.__class__( - np.append(self.name, other.name), - np.append(self.voxels, other.voxels), - np.append(self.vertices, other.vertices), - affine, - shape, - nvertices, - ) - - def __getitem__(self, item): - """ - Extracts subset of the axes based on the type of ``item``: - - - `int`: 3-element tuple of (parcel name, parcel voxels, parcel vertices) - - `string`: 2-element tuple of (parcel voxels, parcel vertices - - other object that can index 1D arrays: new Parcel axis - """ - if isinstance(item, str): - idx = np.where(self.name == item)[0] - if len(idx) == 0: - raise IndexError(f'Parcel {item} not found') - if len(idx) > 1: - raise IndexError(f'Multiple parcels with name {item} found') - return self.voxels[idx[0]], self.vertices[idx[0]] - if isinstance(item, int): - return self.get_element(item) - return self.__class__( - self.name[item], - self.voxels[item], - self.vertices[item], - self.affine, - self.volume_shape, - self.nvertices, - ) - - def get_element(self, index): - """ - Describes a single element from the axis - - Parameters - ---------- - index : int - Indexes the row/column of interest - - Returns - ------- - tuple with 3 elements - - unicode name of the parcel - - (M, 3) int array with voxel indices - - dict from string to (K, ) int array with vertex indices - for a specific surface brain structure - """ - return self.name[index], self.voxels[index], self.vertices[index] - - -class ScalarAxis(Axis): - """ - Along this axis of the CIFTI-2 vector/matrix each row/column has been given - a unique name and optionally metadata - """ - - def __init__(self, name, meta=None): - """ - Parameters - ---------- - name : array_like - (N, ) string array with the parcel names - meta : array_like - (N, ) object array with a dictionary of metadata for each row/column. - Defaults to empty dictionary - """ - self.name = np.asanyarray(name, dtype='U') - if meta is None: - meta = [{} for _ in range(self.name.size)] - self.meta = np.asanyarray(meta, dtype='object') - - for check_name in ('name', 'meta'): - if getattr(self, check_name).shape != (self.size,): - raise ValueError( - f'Input {check_name} has incorrect shape ' - f'({getattr(self, check_name).shape}) for ScalarAxis axis' - ) - - @classmethod - def from_index_mapping(cls, mim): - """ - Creates a new Scalar axis based on a CIFTI-2 dataset - - Parameters - ---------- - mim : :class:`.cifti2.Cifti2MatrixIndicesMap` - - Returns - ------- - ScalarAxis - """ - names = [nm.map_name for nm in mim.named_maps] - meta = [{} if nm.metadata is None else dict(nm.metadata) for nm in mim.named_maps] - return cls(names, meta) - - def to_mapping(self, dim): - """ - Converts the hcp_labels to a MatrixIndicesMap for storage in CIFTI-2 format - - Parameters - ---------- - dim : int - which dimension of the CIFTI-2 vector/matrix is described by this dataset (zero-based) - - Returns - ------- - :class:`.cifti2.Cifti2MatrixIndicesMap` - """ - mim = cifti2.Cifti2MatrixIndicesMap([dim], 'CIFTI_INDEX_TYPE_SCALARS') - for name, meta in zip(self.name, self.meta): - named_map = cifti2.Cifti2NamedMap(name, cifti2.Cifti2MetaData(meta)) - mim.append(named_map) - return mim - - def __len__(self): - return self.name.size - - def __eq__(self, other): - """ - Compares two Scalars - - Parameters - ---------- - other : ScalarAxis - scalar axis to be compared - - Returns - ------- - bool : False if type, length or content do not match - """ - if not isinstance(other, ScalarAxis) or self.size != other.size: - return False - return np.array_equal(self.name, other.name) and np.array_equal(self.meta, other.meta) - - def __add__(self, other): - """ - Concatenates two Scalars - - Parameters - ---------- - other : ScalarAxis - scalar axis to be appended to the current one - - Returns - ------- - ScalarAxis - """ - if not isinstance(other, ScalarAxis): - return NotImplemented - return ScalarAxis( - np.append(self.name, other.name), - np.append(self.meta, other.meta), - ) - - def __getitem__(self, item): - if isinstance(item, int): - return self.get_element(item) - return self.__class__(self.name[item], self.meta[item]) - - def get_element(self, index): - """ - Describes a single element from the axis - - Parameters - ---------- - index : int - Indexes the row/column of interest - - Returns - ------- - tuple with 2 elements - - unicode name of the row/column - - dictionary with the element metadata - """ - return self.name[index], self.meta[index] - - -class LabelAxis(Axis): - """ - Defines CIFTI-2 axis for label array. - - Along this axis of the CIFTI-2 vector/matrix each row/column has been given a unique name, - label table, and optionally metadata - """ - - def __init__(self, name, label, meta=None): - """ - Parameters - ---------- - name : array_like - (N, ) string array with the parcel names - label : array_like - single dictionary or (N, ) object array with dictionaries mapping - from integers to (name, (R, G, B, A)), where name is a string and R, G, B, and A are - floats between 0 and 1 giving the colour and alpha (i.e., transparency) - meta : array_like, optional - (N, ) object array with a dictionary of metadata for each row/column - """ - self.name = np.asanyarray(name, dtype='U') - if isinstance(label, dict): - label = [label.copy() for _ in range(self.name.size)] - self.label = np.asanyarray(label, dtype='object') - if meta is None: - meta = [{} for _ in range(self.name.size)] - self.meta = np.asanyarray(meta, dtype='object') - - for check_name in ('name', 'meta', 'label'): - if getattr(self, check_name).shape != (self.size,): - raise ValueError( - f'Input {check_name} has incorrect shape ' - f'({getattr(self, check_name).shape}) for LabelAxis axis' - ) - - @classmethod - def from_index_mapping(cls, mim): - """ - Creates a new Label axis based on a CIFTI-2 dataset - - Parameters - ---------- - mim : :class:`.cifti2.Cifti2MatrixIndicesMap` - - Returns - ------- - LabelAxis - """ - tables = [ - {key: (value.label, value.rgba) for key, value in nm.label_table.items()} - for nm in mim.named_maps - ] - rest = ScalarAxis.from_index_mapping(mim) - return LabelAxis(rest.name, tables, rest.meta) - - def to_mapping(self, dim): - """ - Converts the hcp_labels to a MatrixIndicesMap for storage in CIFTI-2 format - - Parameters - ---------- - dim : int - which dimension of the CIFTI-2 vector/matrix is described by this dataset (zero-based) - - Returns - ------- - :class:`.cifti2.Cifti2MatrixIndicesMap` - """ - mim = cifti2.Cifti2MatrixIndicesMap([dim], 'CIFTI_INDEX_TYPE_LABELS') - for name, label, meta in zip(self.name, self.label, self.meta): - label_table = cifti2.Cifti2LabelTable() - for key, value in label.items(): - label_table[key] = (value[0],) + tuple(value[1]) - named_map = cifti2.Cifti2NamedMap(name, cifti2.Cifti2MetaData(meta), label_table) - mim.append(named_map) - return mim - - def __len__(self): - return self.name.size - - def __eq__(self, other): - """ - Compares two Labels - - Parameters - ---------- - other : LabelAxis - label axis to be compared - - Returns - ------- - bool : False if type, length or content do not match - """ - if not isinstance(other, LabelAxis) or self.size != other.size: - return False - return ( - np.array_equal(self.name, other.name) - and np.array_equal(self.meta, other.meta) - and np.array_equal(self.label, other.label) - ) - - def __add__(self, other): - """ - Concatenates two Labels - - Parameters - ---------- - other : LabelAxis - label axis to be appended to the current one - - Returns - ------- - LabelAxis - """ - if not isinstance(other, LabelAxis): - return NotImplemented - return LabelAxis( - np.append(self.name, other.name), - np.append(self.label, other.label), - np.append(self.meta, other.meta), - ) - - def __getitem__(self, item): - if isinstance(item, int): - return self.get_element(item) - return self.__class__(self.name[item], self.label[item], self.meta[item]) - - def get_element(self, index): - """ - Describes a single element from the axis - - Parameters - ---------- - index : int - Indexes the row/column of interest - - Returns - ------- - tuple with 2 elements - - unicode name of the row/column - - dictionary with the label table - - dictionary with the element metadata - """ - return self.name[index], self.label[index], self.meta[index] - - -class SeriesAxis(Axis): - """ - Along this axis of the CIFTI-2 vector/matrix the rows/columns increase monotonously in time - - This Axis describes the time point of each row/column. - """ - - size = None - - def __init__(self, start, step, size, unit='SECOND'): - """ - Creates a new SeriesAxis axis - - Parameters - ---------- - start : float - starting time point - step : float - sampling time (TR) - size : int - number of time points - unit : str - Unit of the step size (one of 'second', 'hertz', 'meter', or 'radian') - """ - self.unit = unit - self.start = start - self.step = step - self.size = size - - @property - def time(self): - return np.arange(self.size) * self.step + self.start - - @classmethod - def from_index_mapping(cls, mim): - """ - Creates a new SeriesAxis axis based on a CIFTI-2 dataset - - Parameters - ---------- - mim : :class:`.cifti2.Cifti2MatrixIndicesMap` - - Returns - ------- - SeriesAxis - """ - start = mim.series_start * 10**mim.series_exponent - step = mim.series_step * 10**mim.series_exponent - return cls(start, step, mim.number_of_series_points, mim.series_unit) - - def to_mapping(self, dim): - """ - Converts the SeriesAxis to a MatrixIndicesMap for storage in CIFTI-2 format - - Parameters - ---------- - dim : int - which dimension of the CIFTI-2 vector/matrix is described by this dataset (zero-based) - - Returns - ------- - :class:`cifti2.Cifti2MatrixIndicesMap` - """ - mim = cifti2.Cifti2MatrixIndicesMap([dim], 'CIFTI_INDEX_TYPE_SERIES') - mim.series_exponent = 0 - mim.series_start = self.start - mim.series_step = self.step - mim.number_of_series_points = self.size - mim.series_unit = self.unit - return mim - - _unit = None - - @property - def unit(self): - return self._unit - - @unit.setter - def unit(self, value): - if value.upper() not in ('SECOND', 'HERTZ', 'METER', 'RADIAN'): - raise ValueError( - 'SeriesAxis unit should be one of ' + "('second', 'hertz', 'meter', or 'radian'" - ) - self._unit = value.upper() - - def __len__(self): - return self.size - - def __eq__(self, other): - """ - True if start, step, size, and unit are the same. - """ - return ( - isinstance(other, SeriesAxis) - and self.start == other.start - and self.step == other.step - and self.size == other.size - and self.unit == other.unit - ) - - def __add__(self, other): - """ - Concatenates two SeriesAxis - - Parameters - ---------- - other : SeriesAxis - Time SeriesAxis to append at the end of the current time SeriesAxis. - Note that the starting time of the other time SeriesAxis is ignored. - - Returns - ------- - SeriesAxis - New time SeriesAxis with the concatenation of the two - - Raises - ------ - ValueError - raised if the repetition time of the two time SeriesAxis is different - """ - if isinstance(other, SeriesAxis): - if other.step != self.step: - raise ValueError('Can only concatenate SeriesAxis with the same step size') - if other.unit != self.unit: - raise ValueError('Can only concatenate SeriesAxis with the same unit') - return SeriesAxis(self.start, self.step, self.size + other.size, self.unit) - return NotImplemented - - def __getitem__(self, item): - if isinstance(item, slice): - step = 1 if item.step is None else item.step - idx_start = ( - (self.size - 1 if step < 0 else 0) - if item.start is None - else (item.start if item.start >= 0 else self.size + item.start) - ) - idx_end = ( - (-1 if step < 0 else self.size) - if item.stop is None - else (item.stop if item.stop >= 0 else self.size + item.stop) - ) - if idx_start > self.size and step < 0: - idx_start = self.size - 1 - if idx_end > self.size: - idx_end = self.size - nelements = (idx_end - idx_start) // step - if nelements < 0: - nelements = 0 - return SeriesAxis( - idx_start * self.step + self.start, self.step * step, nelements, self.unit - ) - elif isinstance(item, int): - return self.get_element(item) - raise IndexError( - 'SeriesAxis can only be indexed with integers or slices ' - 'without breaking the regular structure' - ) - - def get_element(self, index): - """ - Gives the time point of a specific row/column - - Parameters - ---------- - index : int - Indexes the row/column of interest - - Returns - ------- - float - """ - original_index = index - if index < 0: - index = self.size + index - if index >= self.size or index < 0: - raise IndexError( - f'index {original_index} is out of range for SeriesAxis with size {self.size}' - ) - return self.start + self.step * index diff --git a/nibabel/cifti2/parse_cifti2.py b/nibabel/cifti2/parse_cifti2.py deleted file mode 100644 index 6ed2a29b52..0000000000 --- a/nibabel/cifti2/parse_cifti2.py +++ /dev/null @@ -1,562 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## - -from io import BytesIO - -import numpy as np -from packaging.version import Version, parse - -from .. import xmlutils as xml -from ..batteryrunners import Report -from ..nifti1 import Nifti1Extension, extension_codes, intent_codes -from ..nifti2 import Nifti2Header, Nifti2Image -from ..spatialimages import HeaderDataError -from .cifti2 import ( - CIFTI_BRAIN_STRUCTURES, - CIFTI_MODEL_TYPES, - Cifti2BrainModel, - Cifti2Header, - Cifti2HeaderError, - Cifti2Label, - Cifti2LabelTable, - Cifti2Matrix, - Cifti2MatrixIndicesMap, - Cifti2MetaData, - Cifti2NamedMap, - Cifti2Parcel, - Cifti2Surface, - Cifti2TransformationMatrixVoxelIndicesIJKtoXYZ, - Cifti2VertexIndices, - Cifti2Vertices, - Cifti2Volume, - Cifti2VoxelIndicesIJK, - _underscore, -) - - -class Cifti2Extension(Nifti1Extension[Cifti2Header]): - code = 32 - - def _unmangle(self, value: bytes) -> Cifti2Header: - parser = Cifti2Parser() - parser.parse(string=value) - return parser.header - - def _mangle(self, value: Cifti2Header) -> bytes: - if not isinstance(value, Cifti2Header): - raise ValueError('Can only mangle a Cifti2Header.') - return value.to_xml() - - -extension_codes.add_codes(((Cifti2Extension.code, 'cifti', Cifti2Extension),)) - -intent_codes.add_codes( - ( - # The codes below appear on the CIFTI-2 standard - # http://www.nitrc.org/plugins/mwiki/index.php/cifti:ConnectivityMatrixFileFormats - # https://www.nitrc.org/forum/attachment.php?attachid=341&group_id=454&forum_id=1955 - (3000, 'ConnUnknown', (), 'NIFTI_INTENT_CONNECTIVITY_UNKNOWN'), - (3001, 'ConnDense', (), 'NIFTI_INTENT_CONNECTIVITY_DENSE'), - (3002, 'ConnDenseSeries', (), 'NIFTI_INTENT_CONNECTIVITY_DENSE_SERIES'), - (3003, 'ConnParcels', (), 'NIFTI_INTENT_CONNECTIVITY_PARCELLATED'), - (3004, 'ConnParcelSries', (), 'NIFTI_INTENT_CONNECTIVITY_PARCELLATED_SERIES'), - (3006, 'ConnDenseScalar', (), 'NIFTI_INTENT_CONNECTIVITY_DENSE_SCALARS'), - (3007, 'ConnDenseLabel', (), 'NIFTI_INTENT_CONNECTIVITY_DENSE_LABELS'), - (3008, 'ConnParcelScalr', (), 'NIFTI_INTENT_CONNECTIVITY_PARCELLATED_SCALAR'), - (3009, 'ConnParcelDense', (), 'NIFTI_INTENT_CONNECTIVITY_PARCELLATED_DENSE'), - (3010, 'ConnDenseParcel', (), 'NIFTI_INTENT_CONNECTIVITY_DENSE_PARCELLATED'), - (3011, 'ConnPPSr', (), 'NIFTI_INTENT_CONNECTIVITY_PARCELLATED_PARCELLATED_SERIES'), - (3012, 'ConnPPSc', (), 'NIFTI_INTENT_CONNECTIVITY_PARCELLATED_PARCELLATED_SCALAR'), - ) -) - - -class _Cifti2AsNiftiHeader(Nifti2Header): - """Class for Cifti2 header extension""" - - @classmethod - def _valid_intent_code(klass, intent_code): - """Return True if `intent_code` matches our class `klass`""" - return intent_code >= 3000 and intent_code < 3100 - - @classmethod - def may_contain_header(klass, binaryblock): - if not super().may_contain_header(binaryblock): - return False - hdr = klass(binaryblock=binaryblock[: klass.sizeof_hdr]) - return klass._valid_intent_code(hdr.get_intent('code')[0]) - - @staticmethod - def _chk_qfac(hdr, fix=False): - # Allow qfac of 0 without complaint for CIFTI-2 - rep = Report(HeaderDataError) - if hdr['pixdim'][0] in (-1, 0, 1): - return hdr, rep - rep.problem_level = 20 - rep.problem_msg = 'pixdim[0] (qfac) should be 1 (default) or 0 or -1' - if fix: - hdr['pixdim'][0] = 1 - rep.fix_msg = 'setting qfac to 1' - return hdr, rep - - @staticmethod - def _chk_pixdims(hdr, fix=False): - rep = Report(HeaderDataError) - pixdims = hdr['pixdim'] - spat_dims = pixdims[1:4] - if not np.any(spat_dims < 0): - return hdr, rep - rep.problem_level = 35 - rep.problem_msg = 'pixdim[1,2,3] should be zero or positive' - if fix: - hdr['pixdim'][1:4] = np.abs(spat_dims) - rep.fix_msg = 'setting to abs of pixdim values' - return hdr, rep - - -class _Cifti2AsNiftiImage(Nifti2Image): - """Load a NIfTI2 image with a Cifti2 header""" - - header_class = _Cifti2AsNiftiHeader - makeable = False - - -class Cifti2Parser(xml.XmlParser): - """Class to parse an XML string into a CIFTI-2 header object""" - - def __init__(self, encoding=None, buffer_size=3500000, verbose=0): - super().__init__(encoding=encoding, buffer_size=buffer_size, verbose=verbose) - self.fsm_state = [] - self.struct_state = [] - - # where to write CDATA: - self.write_to = None - self.header = None - - # Collecting char buffer fragments - self._char_blocks = None - - __init__.__doc__ = xml.XmlParser.__init__.__doc__ - - def StartElementHandler(self, name, attrs): - self.flush_chardata() - if self.verbose > 0: - print('Start element:\n\t', repr(name), attrs) - - if name == 'CIFTI': - # create cifti2 image - self.header = Cifti2Header() - self.header.version = ver = attrs['Version'] - if parse(ver) < Version('2'): - raise ValueError(f'Only CIFTI-2 files are supported; found version {ver}') - self.fsm_state.append('CIFTI') - self.struct_state.append(self.header) - - elif name == 'Matrix': - self.fsm_state.append('Matrix') - matrix = Cifti2Matrix() - parent = self.struct_state[-1] - if not isinstance(parent, Cifti2Header): - raise Cifti2HeaderError( - 'Matrix element can only be a child of the CIFTI-2 Header element' - ) - parent.matrix = matrix - self.struct_state.append(matrix) - - elif name == 'MetaData': - self.fsm_state.append('MetaData') - meta = Cifti2MetaData() - parent = self.struct_state[-1] - if not isinstance(parent, (Cifti2Matrix, Cifti2NamedMap)): - raise Cifti2HeaderError( - 'MetaData element can only be a child of the CIFTI-2 Matrix ' - 'or NamedMap elements' - ) - - self.struct_state.append(meta) - - elif name == 'MD': - pair = ['', ''] - self.fsm_state.append('MD') - self.struct_state.append(pair) - - elif name == 'Name': - self.write_to = 'Name' - - elif name == 'Value': - self.write_to = 'Value' - - elif name == 'MatrixIndicesMap': - self.fsm_state.append('MatrixIndicesMap') - dimensions = [int(value) for value in attrs['AppliesToMatrixDimension'].split(',')] - mim = Cifti2MatrixIndicesMap( - applies_to_matrix_dimension=dimensions, - indices_map_to_data_type=attrs['IndicesMapToDataType'], - ) - for key, dtype in ( - ('NumberOfSeriesPoints', int), - ('SeriesExponent', int), - ('SeriesStart', float), - ('SeriesStep', float), - ('SeriesUnit', str), - ): - if key in attrs: - setattr(mim, _underscore(key), dtype(attrs[key])) - matrix = self.struct_state[-1] - if not isinstance(matrix, Cifti2Matrix): - raise Cifti2HeaderError( - 'MatrixIndicesMap element can only be a child of the CIFTI-2 Matrix element' - ) - matrix.append(mim) - self.struct_state.append(mim) - - elif name == 'NamedMap': - self.fsm_state.append('NamedMap') - named_map = Cifti2NamedMap() - mim = self.struct_state[-1] - if not isinstance(mim, Cifti2MatrixIndicesMap): - raise Cifti2HeaderError( - 'NamedMap element can only be a child of the CIFTI-2 MatrixIndicesMap element' - ) - self.struct_state.append(named_map) - mim.append(named_map) - - elif name == 'LabelTable': - named_map = self.struct_state[-1] - mim = self.struct_state[-2] - if mim.indices_map_to_data_type != 'CIFTI_INDEX_TYPE_LABELS': - raise Cifti2HeaderError( - 'LabelTable element can only be a child of a MatrixIndicesMap ' - 'with CIFTI_INDEX_TYPE_LABELS type' - ) - lata = Cifti2LabelTable() - if not isinstance(named_map, Cifti2NamedMap): - raise Cifti2HeaderError( - 'LabelTable element can only be a child of the CIFTI-2 NamedMap element' - ) - self.fsm_state.append('LabelTable') - self.struct_state.append(lata) - named_map.label_table = lata - - elif name == 'Label': - lata = self.struct_state[-1] - if not isinstance(lata, Cifti2LabelTable): - raise Cifti2HeaderError( - 'Label element can only be a child of the CIFTI-2 LabelTable element' - ) - label = Cifti2Label() - label.key = int(attrs['Key']) - label.red = float(attrs['Red']) - label.green = float(attrs['Green']) - label.blue = float(attrs['Blue']) - label.alpha = float(attrs['Alpha']) - self.write_to = 'Label' - self.fsm_state.append('Label') - self.struct_state.append(label) - - elif name == 'MapName': - named_map = self.struct_state[-1] - if not isinstance(named_map, Cifti2NamedMap): - raise Cifti2HeaderError( - 'MapName element can only be a child of the CIFTI-2 NamedMap element' - ) - - self.fsm_state.append('MapName') - self.write_to = 'MapName' - - elif name == 'Surface': - surface = Cifti2Surface() - mim = self.struct_state[-1] - if not isinstance(mim, Cifti2MatrixIndicesMap): - raise Cifti2HeaderError( - 'Surface element can only be a child of the CIFTI-2 MatrixIndicesMap element' - ) - if mim.indices_map_to_data_type != 'CIFTI_INDEX_TYPE_PARCELS': - raise Cifti2HeaderError( - 'Surface element can only be a child of a MatrixIndicesMap ' - 'with CIFTI_INDEX_TYPE_PARCELS type' - ) - surface.brain_structure = attrs['BrainStructure'] - surface.surface_number_of_vertices = int(attrs['SurfaceNumberOfVertices']) - mim.append(surface) - - elif name == 'Parcel': - parcel = Cifti2Parcel() - mim = self.struct_state[-1] - if not isinstance(mim, Cifti2MatrixIndicesMap): - raise Cifti2HeaderError( - 'Parcel element can only be a child of the CIFTI-2 MatrixIndicesMap element' - ) - parcel.name = attrs['Name'] - mim.append(parcel) - self.fsm_state.append('Parcel') - self.struct_state.append(parcel) - - elif name == 'Vertices': - vertices = Cifti2Vertices() - parcel = self.struct_state[-1] - if not isinstance(parcel, Cifti2Parcel): - raise Cifti2HeaderError( - 'Vertices element can only be a child of the CIFTI-2 Parcel element' - ) - vertices.brain_structure = attrs['BrainStructure'] - if vertices.brain_structure not in CIFTI_BRAIN_STRUCTURES: - raise Cifti2HeaderError('BrainStructure for this Vertices element is not valid') - parcel.append_cifti_vertices(vertices) - self.fsm_state.append('Vertices') - self.struct_state.append(vertices) - self.write_to = 'Vertices' - - elif name == 'VoxelIndicesIJK': - parent = self.struct_state[-1] - if not isinstance(parent, (Cifti2Parcel, Cifti2BrainModel)): - raise Cifti2HeaderError( - 'VoxelIndicesIJK element can only be a child of the CIFTI-2 ' - 'Parcel or BrainModel elements' - ) - parent.voxel_indices_ijk = Cifti2VoxelIndicesIJK() - self.write_to = 'VoxelIndices' - - elif name == 'Volume': - mim = self.struct_state[-1] - if not isinstance(mim, Cifti2MatrixIndicesMap): - raise Cifti2HeaderError( - 'Volume element can only be a child of the CIFTI-2 MatrixIndicesMap element' - ) - dimensions = tuple(int(val) for val in attrs['VolumeDimensions'].split(',')) - volume = Cifti2Volume(volume_dimensions=dimensions) - mim.append(volume) - self.fsm_state.append('Volume') - self.struct_state.append(volume) - - elif name == 'TransformationMatrixVoxelIndicesIJKtoXYZ': - volume = self.struct_state[-1] - if not isinstance(volume, Cifti2Volume): - raise Cifti2HeaderError( - 'TransformationMatrixVoxelIndicesIJKtoXYZ element can only be a child ' - 'of the CIFTI-2 Volume element' - ) - transform = Cifti2TransformationMatrixVoxelIndicesIJKtoXYZ() - transform.meter_exponent = int(attrs['MeterExponent']) - volume.transformation_matrix_voxel_indices_ijk_to_xyz = transform - self.fsm_state.append('TransformMatrix') - self.struct_state.append(transform) - self.write_to = 'TransformMatrix' - - elif name == 'BrainModel': - model = Cifti2BrainModel() - mim = self.struct_state[-1] - if not isinstance(mim, Cifti2MatrixIndicesMap): - raise Cifti2HeaderError( - 'BrainModel element can only be a child ' - 'of the CIFTI-2 MatrixIndicesMap element' - ) - if mim.indices_map_to_data_type != 'CIFTI_INDEX_TYPE_BRAIN_MODELS': - raise Cifti2HeaderError( - 'BrainModel element can only be a child of a MatrixIndicesMap ' - 'with CIFTI_INDEX_TYPE_BRAIN_MODELS type' - ) - for key, dtype in ( - ('IndexOffset', int), - ('IndexCount', int), - ('ModelType', str), - ('BrainStructure', str), - ('SurfaceNumberOfVertices', int), - ): - if key in attrs: - setattr(model, _underscore(key), dtype(attrs[key])) - if model.brain_structure not in CIFTI_BRAIN_STRUCTURES: - raise Cifti2HeaderError('BrainStructure for this BrainModel element is not valid') - if model.model_type not in CIFTI_MODEL_TYPES: - raise Cifti2HeaderError('ModelType for this BrainModel element is not valid') - mim.append(model) - self.fsm_state.append('BrainModel') - self.struct_state.append(model) - - elif name == 'VertexIndices': - index = Cifti2VertexIndices() - model = self.struct_state[-1] - if not isinstance(model, Cifti2BrainModel): - raise Cifti2HeaderError( - 'VertexIndices element can only be a child of the CIFTI-2 BrainModel element' - ) - self.fsm_state.append('VertexIndices') - model.vertex_indices = index - self.struct_state.append(index) - self.write_to = 'VertexIndices' - - def EndElementHandler(self, name): - self.flush_chardata() - if self.verbose > 0: - print('End element:\n\t', repr(name)) - - if name == 'CIFTI': - # remove last element of the list - self.fsm_state.pop() - self.struct_state.pop() - - elif name == 'Matrix': - self.fsm_state.pop() - self.struct_state.pop() - - elif name == 'MetaData': - self.fsm_state.pop() - meta = self.struct_state.pop() - parent = self.struct_state[-1] - parent.metadata = meta - - elif name == 'MD': - self.fsm_state.pop() - pair = self.struct_state.pop() - meta = self.struct_state[-1] - meta[pair[0]] = pair[1] - - elif name == 'Name': - self.write_to = None - - elif name == 'Value': - self.write_to = None - - elif name == 'MatrixIndicesMap': - self.fsm_state.pop() - self.struct_state.pop() - - elif name == 'NamedMap': - self.fsm_state.pop() - self.struct_state.pop() - - elif name == 'LabelTable': - self.fsm_state.pop() - self.struct_state.pop() - - elif name == 'Label': - self.fsm_state.pop() - label = self.struct_state.pop() - lata = self.struct_state[-1] - lata.append(label) - self.write_to = None - - elif name == 'MapName': - self.fsm_state.pop() - self.write_to = None - - elif name == 'Parcel': - self.fsm_state.pop() - self.struct_state.pop() - - elif name == 'Vertices': - self.fsm_state.pop() - self.struct_state.pop() - self.write_to = None - - elif name == 'VoxelIndicesIJK': - self.write_to = None - - elif name == 'Volume': - self.fsm_state.pop() - self.struct_state.pop() - - elif name == 'TransformationMatrixVoxelIndicesIJKtoXYZ': - self.fsm_state.pop() - self.struct_state.pop() - self.write_to = None - - elif name == 'BrainModel': - self.fsm_state.pop() - self.struct_state.pop() - - elif name == 'VertexIndices': - self.fsm_state.pop() - self.struct_state.pop() - self.write_to = None - - def CharacterDataHandler(self, data): - """Collect character data chunks pending collation - - The parser breaks the data up into chunks of size depending on the - buffer_size of the parser. A large bit of character data, with standard - parser buffer_size (such as 8K) can easily span many calls to this - function. We thus collect the chunks and process them when we hit start - or end tags. - """ - if self._char_blocks is None: - self._char_blocks = [] - self._char_blocks.append(data) - - def flush_chardata(self): - """Collate and process collected character data""" - if self._char_blocks is None: - return - # Just join the strings to get the data. Maybe there are some memory - # optimizations we could do by passing the list of strings to the - # read_data_block function. - data = ''.join(self._char_blocks) - # Reset the char collector - self._char_blocks = None - # Process data - if self.write_to == 'Name': - data = data.strip() # .decode('utf-8') - pair = self.struct_state[-1] - pair[0] = data - - elif self.write_to == 'Value': - data = data.strip() # .decode('utf-8') - pair = self.struct_state[-1] - pair[1] = data - - elif self.write_to == 'Vertices': - # conversion to numpy array - c = BytesIO(data.strip().encode('utf-8')) - vertices = self.struct_state[-1] - vertices.extend(np.loadtxt(c, dtype=int, ndmin=1)) - c.close() - - elif self.write_to == 'VoxelIndices': - # conversion to numpy array - c = BytesIO(data.strip().encode('utf-8')) - parent = self.struct_state[-1] - parent.voxel_indices_ijk.extend(np.loadtxt(c, dtype=int).reshape(-1, 3)) - c.close() - - elif self.write_to == 'VertexIndices': - # conversion to numpy array - c = BytesIO(data.strip().encode('utf-8')) - index = self.struct_state[-1] - index.extend(np.loadtxt(c, dtype=int, ndmin=1)) - c.close() - - elif self.write_to == 'TransformMatrix': - # conversion to numpy array - c = BytesIO(data.strip().encode('utf-8')) - transform = self.struct_state[-1] - matrix = np.loadtxt(c, dtype=np.float64) - transform.matrix = matrix.reshape(4, 4) - c.close() - - elif self.write_to == 'Label': - label = self.struct_state[-1] - label.label = data.strip() - - elif self.write_to == 'MapName': - named_map = self.struct_state[-1] - named_map.map_name = data.strip() # .decode('utf-8') - - @property - def pending_data(self): - """True if there is character data pending for processing""" - return self._char_blocks is not None - - -# class _Cifti2DenseDataSeriesNiftiHeader(_Cifti2AsNiftiHeader): -# -# @classmethod -# def _valid_intent_code(klass, intent_code): -# """ Return True if `intent_code` matches our class `klass` -# """ -# return intent_code == 3002 diff --git a/nibabel/cifti2/tests/test_axes.py b/nibabel/cifti2/tests/test_axes.py deleted file mode 100644 index 245964502f..0000000000 --- a/nibabel/cifti2/tests/test_axes.py +++ /dev/null @@ -1,738 +0,0 @@ -from copy import deepcopy - -import numpy as np -import pytest - -import nibabel.cifti2.cifti2_axes as axes - -from .test_cifti2io_axes import check_rewrite - -rand_affine = np.random.randn(4, 4) -vol_shape = (5, 10, 3) -use_label = {0: ('something', (0.2, 0.4, 0.1, 0.5)), 1: ('even better', (0.3, 0.8, 0.43, 0.9))} - - -def get_brain_models(): - """ - Generates a set of practice BrainModelAxis axes - - Yields - ------ - BrainModelAxis axis - """ - mask = np.zeros(vol_shape) - mask[0, 1, 2] = 1 - mask[0, 4, 2] = True - mask[0, 4, 0] = True - yield axes.BrainModelAxis.from_mask(mask, 'ThalamusRight', rand_affine) - mask[0, 0, 0] = True - yield axes.BrainModelAxis.from_mask(mask, affine=rand_affine) - - yield axes.BrainModelAxis.from_surface([0, 5, 10], 15, 'CortexLeft') - yield axes.BrainModelAxis.from_surface([0, 5, 10, 13], 15) - - surface_mask = np.zeros(15, dtype='bool') - surface_mask[[2, 9, 14]] = True - yield axes.BrainModelAxis.from_mask(surface_mask, name='CortexRight') - - -def get_parcels(): - """ - Generates a practice Parcel axis out of all practice brain models - - Returns - ------- - Parcel axis - """ - bml = list(get_brain_models()) - return axes.ParcelsAxis.from_brain_models( - [('mixed', bml[0] + bml[2]), ('volume', bml[1]), ('surface', bml[3])] - ) - - -def get_scalar(): - """ - Generates a practice ScalarAxis axis with names ('one', 'two', 'three') - - Returns - ------- - ScalarAxis axis - """ - return axes.ScalarAxis(['one', 'two', 'three']) - - -def get_label(): - """ - Generates a practice LabelAxis axis with names ('one', 'two', 'three') and two labels - - Returns - ------- - LabelAxis axis - """ - return axes.LabelAxis(['one', 'two', 'three'], use_label) - - -def get_series(): - """ - Generates a set of 4 practice SeriesAxis axes with different starting times/lengths/time steps and units - - Yields - ------ - SeriesAxis axis - """ - yield axes.SeriesAxis(3, 10, 4) - yield axes.SeriesAxis(8, 10, 3) - yield axes.SeriesAxis(3, 2, 4) - yield axes.SeriesAxis(5, 10, 5, 'HERTZ') - - -def get_axes(): - """ - Iterates through all of the practice axes defined in the functions above - - Yields - ------ - Cifti2 axis - """ - yield get_parcels() - yield get_scalar() - yield get_label() - yield from get_brain_models() - yield from get_series() - - -def test_brain_models(): - """ - Tests the introspection and creation of CIFTI-2 BrainModelAxis axes - """ - bml = list(get_brain_models()) - assert len(bml[0]) == 3 - assert (bml[0].vertex == -1).all() - assert (bml[0].voxel == [[0, 1, 2], [0, 4, 0], [0, 4, 2]]).all() - assert bml[0][1][0] == 'CIFTI_MODEL_TYPE_VOXELS' - assert (bml[0][1][1] == [0, 4, 0]).all() - assert bml[0][1][2] == axes.BrainModelAxis.to_cifti_brain_structure_name('thalamus_right') - assert len(bml[1]) == 4 - assert (bml[1].vertex == -1).all() - assert (bml[1].voxel == [[0, 0, 0], [0, 1, 2], [0, 4, 0], [0, 4, 2]]).all() - assert len(bml[2]) == 3 - assert (bml[2].voxel == -1).all() - assert (bml[2].vertex == [0, 5, 10]).all() - assert bml[2][1] == ('CIFTI_MODEL_TYPE_SURFACE', 5, 'CIFTI_STRUCTURE_CORTEX_LEFT') - assert len(bml[3]) == 4 - assert (bml[3].voxel == -1).all() - assert (bml[3].vertex == [0, 5, 10, 13]).all() - assert bml[4][1] == ('CIFTI_MODEL_TYPE_SURFACE', 9, 'CIFTI_STRUCTURE_CORTEX_RIGHT') - assert len(bml[4]) == 3 - assert (bml[4].voxel == -1).all() - assert (bml[4].vertex == [2, 9, 14]).all() - - for bm, label, is_surface in zip( - bml, - ['ThalamusRight', 'Other', 'cortex_left', 'Other'], - (False, False, True, True), - ): - assert np.all(bm.surface_mask == ~bm.volume_mask) - structures = list(bm.iter_structures()) - assert len(structures) == 1 - name = structures[0][0] - assert name == axes.BrainModelAxis.to_cifti_brain_structure_name(label) - if is_surface: - assert bm.nvertices[name] == 15 - else: - assert name not in bm.nvertices - assert (bm.affine == rand_affine).all() - assert bm.volume_shape == vol_shape - - bmt = bml[0] + bml[1] + bml[2] - assert len(bmt) == 10 - structures = list(bmt.iter_structures()) - assert len(structures) == 3 - for bm, (name, _, bm_split) in zip(bml[:3], structures): - assert bm == bm_split - assert (bm_split.name == name).all() - assert bm == bmt[bmt.name == bm.name[0]] - assert bm == bmt[np.where(bmt.name == bm.name[0])] - - bmt = bmt + bml[2] - assert len(bmt) == 13 - structures = list(bmt.iter_structures()) - assert len(structures) == 3 - assert len(structures[-1][2]) == 6 - - # break brain model - bmt.affine = np.eye(4) - with pytest.raises(ValueError): - bmt.affine = np.eye(3) - with pytest.raises(ValueError): - bmt.affine = np.eye(4).flatten() - - bmt.volume_shape = (5, 3, 1) - with pytest.raises(ValueError): - bmt.volume_shape = (5.0, 3, 1) - with pytest.raises(ValueError): - bmt.volume_shape = (5, 3, 1, 4) - - with pytest.raises(IndexError): - bmt['thalamus_left'] - - # Test the constructor - bm_vox = axes.BrainModelAxis( - 'thalamus_left', - voxel=np.ones((5, 3), dtype=int), - affine=np.eye(4), - volume_shape=(2, 3, 4), - ) - assert np.all(bm_vox.name == ['CIFTI_STRUCTURE_THALAMUS_LEFT'] * 5) - assert np.array_equal(bm_vox.vertex, np.full(5, -1)) - assert np.array_equal(bm_vox.voxel, np.full((5, 3), 1)) - with pytest.raises(ValueError): - # no volume shape - axes.BrainModelAxis( - 'thalamus_left', - voxel=np.ones((5, 3), dtype=int), - affine=np.eye(4), - ) - with pytest.raises(ValueError): - # no affine - axes.BrainModelAxis( - 'thalamus_left', - voxel=np.ones((5, 3), dtype=int), - volume_shape=(2, 3, 4), - ) - with pytest.raises(ValueError): - # incorrect name - axes.BrainModelAxis( - 'random_name', - voxel=np.ones((5, 3), dtype=int), - affine=np.eye(4), - volume_shape=(2, 3, 4), - ) - with pytest.raises(ValueError): - # negative voxel indices - axes.BrainModelAxis( - 'thalamus_left', - voxel=-np.ones((5, 3), dtype=int), - affine=np.eye(4), - volume_shape=(2, 3, 4), - ) - with pytest.raises(ValueError): - # no voxels or vertices - axes.BrainModelAxis( - 'thalamus_left', - affine=np.eye(4), - volume_shape=(2, 3, 4), - ) - with pytest.raises(ValueError): - # incorrect voxel shape - axes.BrainModelAxis( - 'thalamus_left', - voxel=np.ones((5, 2), dtype=int), - affine=np.eye(4), - volume_shape=(2, 3, 4), - ) - - bm_vertex = axes.BrainModelAxis( - 'cortex_left', - vertex=np.ones(5, dtype=int), - nvertices={'cortex_left': 20}, - ) - assert np.array_equal(bm_vertex.name, ['CIFTI_STRUCTURE_CORTEX_LEFT'] * 5) - assert np.array_equal(bm_vertex.vertex, np.full(5, 1)) - assert np.array_equal(bm_vertex.voxel, np.full((5, 3), -1)) - with pytest.raises(ValueError): - axes.BrainModelAxis('cortex_left', vertex=np.ones(5, dtype=int)) - with pytest.raises(ValueError): - axes.BrainModelAxis( - 'cortex_left', - vertex=np.ones(5, dtype=int), - nvertices={'cortex_right': 20}, - ) - with pytest.raises(ValueError): - axes.BrainModelAxis( - 'cortex_left', - vertex=-np.ones(5, dtype=int), - nvertices={'cortex_left': 20}, - ) - - # test from_mask errors - with pytest.raises(ValueError): - # affine should be 4x4 matrix - axes.BrainModelAxis.from_mask(np.arange(5) > 2, affine=np.ones(5)) - with pytest.raises(ValueError): - # only 1D or 3D masks accepted - axes.BrainModelAxis.from_mask(np.ones((5, 3))) - - # tests error in adding together or combining as ParcelsAxis - bm_vox = axes.BrainModelAxis( - 'thalamus_left', - voxel=np.ones((5, 3), dtype=int), - affine=np.eye(4), - volume_shape=(2, 3, 4), - ) - bm_vox + bm_vox - assert (bm_vertex + bm_vox)[: bm_vertex.size] == bm_vertex - assert (bm_vox + bm_vertex)[: bm_vox.size] == bm_vox - for bm_added in (bm_vox + bm_vertex, bm_vertex + bm_vox): - assert bm_added.nvertices == bm_vertex.nvertices - assert np.all(bm_added.affine == bm_vox.affine) - assert bm_added.volume_shape == bm_vox.volume_shape - - axes.ParcelsAxis.from_brain_models([('a', bm_vox), ('b', bm_vox)]) - with pytest.raises(Exception): - bm_vox + get_label() - - bm_other_shape = axes.BrainModelAxis( - 'thalamus_left', voxel=np.ones((5, 3), dtype=int), affine=np.eye(4), volume_shape=(4, 3, 4) - ) - with pytest.raises(ValueError): - bm_vox + bm_other_shape - with pytest.raises(ValueError): - axes.ParcelsAxis.from_brain_models([('a', bm_vox), ('b', bm_other_shape)]) - bm_other_affine = axes.BrainModelAxis( - 'thalamus_left', - voxel=np.ones((5, 3), dtype=int), - affine=np.eye(4) * 2, - volume_shape=(2, 3, 4), - ) - with pytest.raises(ValueError): - bm_vox + bm_other_affine - with pytest.raises(ValueError): - axes.ParcelsAxis.from_brain_models([('a', bm_vox), ('b', bm_other_affine)]) - - bm_vertex = axes.BrainModelAxis( - 'cortex_left', vertex=np.ones(5, dtype=int), nvertices={'cortex_left': 20} - ) - bm_other_number = axes.BrainModelAxis( - 'cortex_left', vertex=np.ones(5, dtype=int), nvertices={'cortex_left': 30} - ) - with pytest.raises(ValueError): - bm_vertex + bm_other_number - with pytest.raises(ValueError): - axes.ParcelsAxis.from_brain_models([('a', bm_vertex), ('b', bm_other_number)]) - - # test equalities - bm_vox = axes.BrainModelAxis( - 'thalamus_left', - voxel=np.ones((5, 3), dtype=int), - affine=np.eye(4), - volume_shape=(2, 3, 4), - ) - bm_other = deepcopy(bm_vox) - assert bm_vox == bm_other - bm_other.voxel[1, 0] = 0 - assert bm_vox != bm_other - - bm_other = deepcopy(bm_vox) - bm_other.vertex[1] = 10 - assert bm_vox == bm_other, 'vertices are ignored in volumetric BrainModelAxis' - - bm_other = deepcopy(bm_vox) - bm_other.name[1] = 'BRAIN_STRUCTURE_OTHER' - assert bm_vox != bm_other - - bm_other = deepcopy(bm_vox) - bm_other.affine[0, 0] = 10 - assert bm_vox != bm_other - - bm_other = deepcopy(bm_vox) - bm_other.affine = None - assert bm_vox != bm_other - assert bm_other != bm_vox - - bm_other = deepcopy(bm_vox) - bm_other.volume_shape = (10, 3, 4) - assert bm_vox != bm_other - - bm_vertex = axes.BrainModelAxis( - 'cortex_left', vertex=np.ones(5, dtype=int), nvertices={'cortex_left': 20} - ) - bm_other = deepcopy(bm_vertex) - assert bm_vertex == bm_other - bm_other.voxel[1, 0] = 0 - assert bm_vertex == bm_other, 'voxels are ignored in surface BrainModelAxis' - - bm_other = deepcopy(bm_vertex) - bm_other.vertex[1] = 10 - assert bm_vertex != bm_other - - bm_other = deepcopy(bm_vertex) - bm_other.name[1] = 'BRAIN_STRUCTURE_CORTEX_RIGHT' - assert bm_vertex != bm_other - - bm_other = deepcopy(bm_vertex) - bm_other.nvertices['BRAIN_STRUCTURE_CORTEX_LEFT'] = 50 - assert bm_vertex != bm_other - - bm_other = deepcopy(bm_vertex) - bm_other.nvertices['BRAIN_STRUCTURE_CORTEX_RIGHT'] = 20 - assert bm_vertex != bm_other - - assert bm_vox != get_parcels() - assert bm_vertex != get_parcels() - - -def test_parcels(): - """ - Test the introspection and creation of CIFTI-2 Parcel axes - """ - prc = get_parcels() - assert isinstance(prc, axes.ParcelsAxis) - assert prc[0] == ('mixed',) + prc['mixed'] - assert prc['mixed'][0].shape == (3, 3) - assert len(prc['mixed'][1]) == 1 - assert prc['mixed'][1]['CIFTI_STRUCTURE_CORTEX_LEFT'].shape == (3,) - - assert prc[1] == ('volume',) + prc['volume'] - assert prc['volume'][0].shape == (4, 3) - assert len(prc['volume'][1]) == 0 - - assert prc[2] == ('surface',) + prc['surface'] - assert prc['surface'][0].shape == (0, 3) - assert len(prc['surface'][1]) == 1 - assert prc['surface'][1]['CIFTI_STRUCTURE_OTHER'].shape == (4,) - - prc2 = prc + prc - assert len(prc2) == 6 - assert (prc2.affine == prc.affine).all() - assert prc2.nvertices == prc.nvertices - assert prc2.volume_shape == prc.volume_shape - assert prc2[:3] == prc - assert prc2[3:] == prc - - assert prc2[3:]['mixed'][0].shape == (3, 3) - assert len(prc2[3:]['mixed'][1]) == 1 - assert prc2[3:]['mixed'][1]['CIFTI_STRUCTURE_CORTEX_LEFT'].shape == (3,) - - with pytest.raises(IndexError): - prc['non_existent'] - - prc['surface'] - with pytest.raises(IndexError): - # parcel exists twice - prc2['surface'] - - # break parcels - prc.affine = np.eye(4) - with pytest.raises(ValueError): - prc.affine = np.eye(3) - with pytest.raises(ValueError): - prc.affine = np.eye(4).flatten() - - prc.volume_shape = (5, 3, 1) - with pytest.raises(ValueError): - prc.volume_shape = (5.0, 3, 1) - with pytest.raises(ValueError): - prc.volume_shape = (5, 3, 1, 4) - - # break adding of parcels - with pytest.raises(Exception): - prc + get_label() - - prc = get_parcels() - other_prc = get_parcels() - prc + other_prc - - other_prc = get_parcels() - other_prc.affine = np.eye(4) * 2 - with pytest.raises(ValueError): - prc + other_prc - - other_prc = get_parcels() - other_prc.volume_shape = (20, 3, 4) - with pytest.raises(ValueError): - prc + other_prc - - # test parcel equalities - prc = get_parcels() - assert prc != get_scalar() - - prc_other = deepcopy(prc) - assert prc == prc_other - assert prc != prc_other[:2] - assert prc == prc_other[:] - prc_other.affine[0, 0] = 10 - assert prc != prc_other - - prc_other = deepcopy(prc) - prc_other.affine = None - assert prc != prc_other - assert prc_other != prc - assert (prc + prc_other).affine is not None - assert (prc_other + prc).affine is not None - - prc_other = deepcopy(prc) - prc_other.volume_shape = (10, 3, 4) - assert prc != prc_other - with pytest.raises(ValueError): - prc + prc_other - - prc_other = deepcopy(prc) - prc_other.nvertices['CIFTI_STRUCTURE_CORTEX_LEFT'] = 80 - assert prc != prc_other - with pytest.raises(ValueError): - prc + prc_other - - prc_other = deepcopy(prc) - prc_other.voxels[0] = np.ones((2, 3), dtype='i4') - assert prc != prc_other - - prc_other = deepcopy(prc) - prc_other.voxels[0] = prc_other.voxels * 2 - assert prc != prc_other - - prc_other = deepcopy(prc) - prc_other.vertices[0]['CIFTI_STRUCTURE_CORTEX_LEFT'] = np.ones((8,), dtype='i4') - assert prc != prc_other - - prc_other = deepcopy(prc) - prc_other.vertices[0]['CIFTI_STRUCTURE_CORTEX_LEFT'] *= 2 - assert prc != prc_other - - prc_other = deepcopy(prc) - prc_other.name[0] = 'new_name' - assert prc != prc_other - - # test direct initialisation - test_parcel = axes.ParcelsAxis( - voxels=[np.ones((3, 2), dtype=int)], - vertices=[{}], - name=['single_voxel'], - affine=np.eye(4), - volume_shape=(2, 3, 4), - ) - assert len(test_parcel) == 1 - - # test direct initialisation with multiple parcels - test_parcel = axes.ParcelsAxis( - voxels=[np.ones((3, 2), dtype=int), np.zeros((3, 2), dtype=int)], - vertices=[{}, {}], - name=['first_parcel', 'second_parcel'], - affine=np.eye(4), - volume_shape=(2, 3, 4), - ) - assert len(test_parcel) == 2 - - # test direct initialisation with ragged voxel/vertices array - test_parcel = axes.ParcelsAxis( - voxels=[np.ones((3, 2), dtype=int), np.zeros((5, 2), dtype=int)], - vertices=[{}, {}], - name=['first_parcel', 'second_parcel'], - affine=np.eye(4), - volume_shape=(2, 3, 4), - ) - assert len(test_parcel) == 2 - - with pytest.raises(ValueError): - axes.ParcelsAxis( - voxels=[np.ones((3, 2), dtype=int)], - vertices=[{}], - name=[['single_voxel']], # wrong shape name array - affine=np.eye(4), - volume_shape=(2, 3, 4), - ) - - -def test_scalar(): - """ - Test the introspection and creation of CIFTI-2 ScalarAxis axes - """ - sc = get_scalar() - assert len(sc) == 3 - assert isinstance(sc, axes.ScalarAxis) - assert (sc.name == ['one', 'two', 'three']).all() - assert (sc.meta == [{}] * 3).all() - assert sc[1] == ('two', {}) - sc2 = sc + sc - assert len(sc2) == 6 - assert (sc2.name == ['one', 'two', 'three', 'one', 'two', 'three']).all() - assert (sc2.meta == [{}] * 6).all() - assert sc2[:3] == sc - assert sc2[3:] == sc - - sc.meta[1]['a'] = 3 - assert 'a' not in sc.meta - - # test equalities - assert sc != get_label() - with pytest.raises(Exception): - sc + get_label() - - sc_other = deepcopy(sc) - assert sc == sc_other - assert sc != sc_other[:2] - assert sc == sc_other[:] - sc_other.name[0] = 'new_name' - assert sc != sc_other - - sc_other = deepcopy(sc) - sc_other.meta[0]['new_key'] = 'new_entry' - assert sc != sc_other - sc.meta[0]['new_key'] = 'new_entry' - assert sc == sc_other - - # test constructor - assert axes.ScalarAxis(['scalar_name'], [{}]) == axes.ScalarAxis(['scalar_name']) - - with pytest.raises(ValueError): - axes.ScalarAxis([['scalar_name']]) # wrong shape - - with pytest.raises(ValueError): - axes.ScalarAxis(['scalar_name'], [{}, {}]) # wrong size - - -def test_label(): - """ - Test the introspection and creation of CIFTI-2 ScalarAxis axes - """ - lab = get_label() - assert len(lab) == 3 - assert isinstance(lab, axes.LabelAxis) - assert (lab.name == ['one', 'two', 'three']).all() - assert (lab.meta == [{}] * 3).all() - assert (lab.label == [use_label] * 3).all() - assert lab[1] == ('two', use_label, {}) - lab2 = lab + lab - assert len(lab2) == 6 - assert (lab2.name == ['one', 'two', 'three', 'one', 'two', 'three']).all() - assert (lab2.meta == [{}] * 6).all() - assert (lab2.label == [use_label] * 6).all() - assert lab2[:3] == lab - assert lab2[3:] == lab - - # test equalities - lab = get_label() - assert lab != get_scalar() - with pytest.raises(Exception): - lab + get_scalar() - - other_lab = deepcopy(lab) - assert lab != other_lab[:2] - assert lab == other_lab[:] - other_lab.name[0] = 'new_name' - assert lab != other_lab - - other_lab = deepcopy(lab) - other_lab.meta[0]['new_key'] = 'new_item' - assert 'new_key' not in other_lab.meta[1] - assert lab != other_lab - lab.meta[0]['new_key'] = 'new_item' - assert lab == other_lab - - other_lab = deepcopy(lab) - other_lab.label[0][20] = ('new_label', (0, 0, 0, 1)) - assert lab != other_lab - assert 20 not in other_lab.label[1] - lab.label[0][20] = ('new_label', (0, 0, 0, 1)) - assert lab == other_lab - - # test constructor - assert axes.LabelAxis(['scalar_name'], [{}], [{}]) == axes.LabelAxis(['scalar_name'], [{}]) - - with pytest.raises(ValueError): - axes.LabelAxis([['scalar_name']], [{}]) # wrong shape - - with pytest.raises(ValueError): - axes.LabelAxis(['scalar_name'], [{}, {}]) # wrong size - - -def test_series(): - """ - Test the introspection and creation of CIFTI-2 SeriesAxis axes - """ - sr = list(get_series()) - assert sr[0].unit == 'SECOND' - assert sr[1].unit == 'SECOND' - assert sr[2].unit == 'SECOND' - assert sr[3].unit == 'HERTZ' - sr[0].unit = 'hertz' - assert sr[0].unit == 'HERTZ' - with pytest.raises(ValueError): - sr[0].unit = 'non_existent' - - sr = list(get_series()) - assert (sr[0].time == np.arange(4) * 10 + 3).all() - assert (sr[1].time == np.arange(3) * 10 + 8).all() - assert (sr[2].time == np.arange(4) * 2 + 3).all() - assert ((sr[0] + sr[1]).time == np.arange(7) * 10 + 3).all() - assert ((sr[1] + sr[0]).time == np.arange(7) * 10 + 8).all() - assert ((sr[1] + sr[0] + sr[0]).time == np.arange(11) * 10 + 8).all() - assert sr[1][2] == 28 - assert sr[1][-2] == sr[1].time[-2] - - with pytest.raises(ValueError): - sr[0] + sr[2] - with pytest.raises(ValueError): - sr[2] + sr[1] - with pytest.raises(ValueError): - sr[0] + sr[3] - with pytest.raises(ValueError): - sr[3] + sr[1] - with pytest.raises(ValueError): - sr[3] + sr[2] - - # test slicing - assert (sr[0][1:3].time == sr[0].time[1:3]).all() - assert (sr[0][1:].time == sr[0].time[1:]).all() - assert (sr[0][:-2].time == sr[0].time[:-2]).all() - assert (sr[0][1:-1].time == sr[0].time[1:-1]).all() - assert (sr[0][1:-1:2].time == sr[0].time[1:-1:2]).all() - assert (sr[0][::2].time == sr[0].time[::2]).all() - assert (sr[0][:10:2].time == sr[0].time[::2]).all() - assert (sr[0][10:].time == sr[0].time[10:]).all() - assert (sr[0][10:12].time == sr[0].time[10:12]).all() - assert (sr[0][10::-1].time == sr[0].time[10::-1]).all() - assert (sr[0][3:1:-1].time == sr[0].time[3:1:-1]).all() - assert (sr[0][1:3:-1].time == sr[0].time[1:3:-1]).all() - - with pytest.raises(IndexError): - assert sr[0][[0, 1]] - with pytest.raises(IndexError): - assert sr[0][20] - with pytest.raises(IndexError): - assert sr[0][-20] - - # test_equalities - sr = next(get_series()) - with pytest.raises(Exception): - sr + get_scalar() - assert sr != sr[:2] - assert sr == sr[:] - - for key, value in ( - ('start', 20), - ('step', 7), - ('size', 14), - ('unit', 'HERTZ'), - ): - sr_other = deepcopy(sr) - assert sr == sr_other - setattr(sr_other, key, value) - assert sr != sr_other - - -def test_writing(): - """ - Tests the writing and reading back in of custom created CIFTI-2 axes - """ - for ax1 in get_axes(): - for ax2 in get_axes(): - arr = np.random.randn(len(ax1), len(ax2)) - check_rewrite(arr, (ax1, ax2)) - - -def test_common_interface(): - """ - Tests the common interface for all custom created CIFTI-2 axes - """ - for axis1, axis2 in zip(get_axes(), get_axes()): - assert axis1 == axis2 - concatenated = axis1 + axis2 - assert axis1 != concatenated - assert axis1 == concatenated[: axis1.size] - if isinstance(axis1, axes.SeriesAxis): - assert axis2 != concatenated[axis1.size :] - else: - assert axis2 == concatenated[axis1.size :] - - assert len(axis1) == axis1.size diff --git a/nibabel/cifti2/tests/test_cifti2.py b/nibabel/cifti2/tests/test_cifti2.py deleted file mode 100644 index 6382dab9d6..0000000000 --- a/nibabel/cifti2/tests/test_cifti2.py +++ /dev/null @@ -1,450 +0,0 @@ -"""Testing CIFTI-2 objects""" - -import collections -from xml.etree import ElementTree - -import numpy as np -import pytest - -from nibabel import cifti2 as ci -from nibabel.cifti2.cifti2 import _float_01, _value_if_klass -from nibabel.nifti2 import Nifti2Header -from nibabel.tests.test_dataobj_images import TestDataobjAPI as _TDA -from nibabel.tests.test_image_api import DtypeOverrideMixin, SerializeMixin - - -def compare_xml_leaf(str1, str2): - x1 = ElementTree.fromstring(str1) - x2 = ElementTree.fromstring(str2) - if len(x1) > 0 or len(x2) > 0: - raise ValueError - - test = (x1.tag == x2.tag) and (x1.attrib == x2.attrib) and (x1.text == x2.text) - print((x1.tag, x1.attrib, x1.text)) - print((x2.tag, x2.attrib, x2.text)) - return test - - -def test_value_if_klass(): - assert _value_if_klass(None, list) is None - assert _value_if_klass([1], list) == [1] - with pytest.raises(ValueError): - _value_if_klass(1, list) - - -def test_cifti2_metadata(): - md = ci.Cifti2MetaData({'a': 'aval'}) - assert len(md) == 1 - assert list(iter(md)) == ['a'] - assert md['a'] == 'aval' - assert md.data == {'a': 'aval'} - - with pytest.warns(FutureWarning): - md = ci.Cifti2MetaData(metadata={'a': 'aval'}) - assert md == {'a': 'aval'} - - with pytest.warns(FutureWarning): - md = ci.Cifti2MetaData(None) - assert md == {} - - md = ci.Cifti2MetaData() - assert len(md) == 0 - assert list(iter(md)) == [] - assert md.data == {} - with pytest.raises(ValueError): - md.difference_update(None) - - md['a'] = 'aval' - assert md['a'] == 'aval' - assert len(md) == 1 - assert md.data == {'a': 'aval'} - - del md['a'] - assert len(md) == 0 - - metadata_test = [('a', 'aval'), ('b', 'bval')] - md.update(metadata_test) - assert md.data == dict(metadata_test) - - assert list(iter(md)) == list(iter(collections.OrderedDict(metadata_test))) - - md.update({'a': 'aval', 'b': 'bval'}) - assert md.data == dict(metadata_test) - - md.update({'a': 'aval', 'd': 'dval'}) - assert md.data == dict(metadata_test + [('d', 'dval')]) - - md.difference_update({'a': 'aval', 'd': 'dval'}) - assert md.data == dict(metadata_test[1:]) - - with pytest.raises(KeyError): - md.difference_update({'a': 'aval', 'd': 'dval'}) - assert md.to_xml() == b'bbval' - - -def test__float_01(): - assert _float_01(0) == 0 - assert _float_01(1) == 1 - assert _float_01('0') == 0 - assert _float_01('0.2') == 0.2 - with pytest.raises(ValueError): - _float_01(1.1) - with pytest.raises(ValueError): - _float_01(-0.1) - with pytest.raises(ValueError): - _float_01(2) - with pytest.raises(ValueError): - _float_01(-1) - with pytest.raises(ValueError): - _float_01('foo') - - -def test_cifti2_labeltable(): - lt = ci.Cifti2LabelTable() - assert len(lt) == 0 - with pytest.raises(ci.Cifti2HeaderError): - lt.to_xml() - with pytest.raises(ci.Cifti2HeaderError): - lt._to_xml_element() - - label = ci.Cifti2Label(label='Test', key=0) - lt[0] = label - assert len(lt) == 1 - assert dict(lt) == {label.key: label} - - lt.clear() - lt.append(label) - assert len(lt) == 1 - assert dict(lt) == {label.key: label} - - lt.clear() - test_tuple = (label.label, label.red, label.green, label.blue, label.alpha) - lt[label.key] = test_tuple - assert len(lt) == 1 - v = lt[label.key] - assert (v.label, v.red, v.green, v.blue, v.alpha) == test_tuple - - with pytest.raises(ValueError): - lt[1] = label - - with pytest.raises(ValueError): - lt[0] = test_tuple[:-1] - - with pytest.raises(ValueError): - lt[0] = ('foo', 1.1, 0, 0, 1) - - with pytest.raises(ValueError): - lt[0] = ('foo', 1.0, -1, 0, 1) - - with pytest.raises(ValueError): - lt[0] = ('foo', 1.0, 0, -0.1, 1) - - -def test_cifti2_label(): - lb = ci.Cifti2Label() - lb.label = 'Test' - lb.key = 0 - assert lb.rgba == (0, 0, 0, 0) - assert compare_xml_leaf( - lb.to_xml().decode('utf-8'), - "", - ) - - lb.red = 0 - lb.green = 0.1 - lb.blue = 0.2 - lb.alpha = 0.3 - assert lb.rgba == (0, 0.1, 0.2, 0.3) - - assert compare_xml_leaf( - lb.to_xml().decode('utf-8'), - "", - ) - - lb.red = 10 - with pytest.raises(ci.Cifti2HeaderError): - lb.to_xml() - lb.red = 0 - - lb.key = 'a' - with pytest.raises(ci.Cifti2HeaderError): - lb.to_xml() - lb.key = 0 - - -def test_cifti2_parcel(): - pl = ci.Cifti2Parcel() - with pytest.raises(ci.Cifti2HeaderError): - pl.to_xml() - - with pytest.raises(TypeError): - pl.append_cifti_vertices(None) - - with pytest.raises(ValueError): - ci.Cifti2Parcel(vertices=[1, 2, 3]) - - pl = ci.Cifti2Parcel( - name='region', - voxel_indices_ijk=ci.Cifti2VoxelIndicesIJK([[1, 2, 3]]), - vertices=[ci.Cifti2Vertices([0, 1, 2])], - ) - pl.pop_cifti2_vertices(0) - - assert len(pl.vertices) == 0 - assert ( - pl.to_xml() == b'1 2 3' - ) - - -def test_cifti2_vertices(): - vs = ci.Cifti2Vertices() - with pytest.raises(ci.Cifti2HeaderError): - vs.to_xml() - - vs.brain_structure = 'CIFTI_STRUCTURE_OTHER' - - assert vs.to_xml() == b'' - - assert len(vs) == 0 - vs.extend(np.array([0, 1, 2])) - assert len(vs) == 3 - with pytest.raises(ValueError): - vs[1] = 'a' - with pytest.raises(ValueError): - vs.insert(1, 'a') - - assert vs.to_xml() == b'0 1 2' - - vs[0] = 10 - assert vs[0] == 10 - assert len(vs) == 3 - vs = ci.Cifti2Vertices(vertices=[0, 1, 2]) - assert len(vs) == 3 - - -def test_cifti2_transformationmatrixvoxelindicesijktoxyz(): - tr = ci.Cifti2TransformationMatrixVoxelIndicesIJKtoXYZ() - with pytest.raises(ci.Cifti2HeaderError): - tr.to_xml() - - -def test_cifti2_surface(): - s = ci.Cifti2Surface() - with pytest.raises(ci.Cifti2HeaderError): - s.to_xml() - - -def test_cifti2_volume(): - vo = ci.Cifti2Volume() - with pytest.raises(ci.Cifti2HeaderError): - vo.to_xml() - - -def test_cifti2_vertexindices(): - vi = ci.Cifti2VertexIndices() - assert len(vi) == 0 - with pytest.raises(ci.Cifti2HeaderError): - vi.to_xml() - vi.extend(np.array([0, 1, 2])) - assert len(vi) == 3 - assert vi.to_xml() == b'0 1 2' - - with pytest.raises(ValueError): - vi[0] = 'a' - - vi[0] = 10 - assert vi[0] == 10 - assert len(vi) == 3 - - -def test_cifti2_voxelindicesijk(): - vi = ci.Cifti2VoxelIndicesIJK() - with pytest.raises(ci.Cifti2HeaderError): - vi.to_xml() - - vi = ci.Cifti2VoxelIndicesIJK() - assert len(vi) == 0 - - with pytest.raises(ci.Cifti2HeaderError): - vi.to_xml() - vi.extend(np.array([[0, 1, 2]])) - - assert len(vi) == 1 - assert vi[0] == [0, 1, 2] - vi.append([3, 4, 5]) - assert len(vi) == 2 - vi.append([6, 7, 8]) - assert len(vi) == 3 - del vi[-1] - assert len(vi) == 2 - - assert vi[1] == [3, 4, 5] - vi[1] = [3, 4, 6] - assert vi[1] == [3, 4, 6] - with pytest.raises(ValueError): - vi['a'] = [1, 2, 3] - - with pytest.raises(TypeError): - vi[[1, 2]] = [1, 2, 3] - - with pytest.raises(ValueError): - vi[1] = [2, 3] - - assert vi[1, 1] == 4 - - with pytest.raises(ValueError): - vi[[1, 1]] = 'a' - - assert vi[0, 1:] == [1, 2] - vi[0, 1] = 10 - assert vi[0, 1] == 10 - vi[0, 1] = 1 - - # test for vi[:, 0] and other slices - with pytest.raises(NotImplementedError): - vi[:, 0] - with pytest.raises(NotImplementedError): - vi[:, 0] = 0 - with pytest.raises(NotImplementedError): - # Don't know how to use remove with slice - del vi[:, 0] - with pytest.raises(ValueError): - vi[0, 0, 0] - - with pytest.raises(ValueError): - vi[0, 0, 0] = 0 - - assert vi.to_xml().decode('utf-8') == '0 1 2\n3 4 6' - - with pytest.raises(TypeError): - ci.Cifti2VoxelIndicesIJK([0, 1]) - - vi = ci.Cifti2VoxelIndicesIJK([[1, 2, 3]]) - assert len(vi) == 1 - - -def test_matrixindicesmap(): - mim = ci.Cifti2MatrixIndicesMap(0, 'CIFTI_INDEX_TYPE_LABELS') - volume = ci.Cifti2Volume() - volume2 = ci.Cifti2Volume() - parcel = ci.Cifti2Parcel() - - assert mim.volume is None - mim.extend((volume, parcel)) - - assert mim.volume == volume - with pytest.raises(ci.Cifti2HeaderError): - mim.insert(0, volume) - - with pytest.raises(ci.Cifti2HeaderError): - mim[1] = volume - - mim[0] = volume2 - assert mim.volume == volume2 - - del mim.volume - assert mim.volume is None - with pytest.raises(ValueError): - del mim.volume - - mim.volume = volume - assert mim.volume == volume - mim.volume = volume2 - assert mim.volume == volume2 - - with pytest.raises(ValueError): - mim.volume = parcel - - -def test_matrix(): - m = ci.Cifti2Matrix() - - with pytest.raises(ValueError): - m.metadata = ci.Cifti2Parcel() - - with pytest.raises(TypeError): - m[0] = ci.Cifti2Parcel() - - with pytest.raises(TypeError): - m.insert(0, ci.Cifti2Parcel()) - - mim_none = ci.Cifti2MatrixIndicesMap(None, 'CIFTI_INDEX_TYPE_LABELS') - mim_0 = ci.Cifti2MatrixIndicesMap(0, 'CIFTI_INDEX_TYPE_LABELS') - mim_1 = ci.Cifti2MatrixIndicesMap(1, 'CIFTI_INDEX_TYPE_LABELS') - mim_01 = ci.Cifti2MatrixIndicesMap([0, 1], 'CIFTI_INDEX_TYPE_LABELS') - - with pytest.raises(ci.Cifti2HeaderError): - m.insert(0, mim_none) - - assert m.mapped_indices == [] - - h = ci.Cifti2Header(matrix=m) - assert m.mapped_indices == [] - m.insert(0, mim_0) - assert h.mapped_indices == [0] - assert h.number_of_mapped_indices == 1 - with pytest.raises(ci.Cifti2HeaderError): - m.insert(0, mim_0) - - with pytest.raises(ci.Cifti2HeaderError): - m.insert(0, mim_01) - - m[0] = mim_1 - assert list(m.mapped_indices) == [1] - m.insert(0, mim_0) - assert sorted(m.mapped_indices) == [0, 1] - assert h.number_of_mapped_indices == 2 - assert h.get_index_map(0) == mim_0 - assert h.get_index_map(1) == mim_1 - with pytest.raises(ci.Cifti2HeaderError): - h.get_index_map(2) - - -def test_underscoring(): - # Pairs taken from inflection tests - # https://github.com/jpvanhal/inflection/blob/663982e/test_inflection.py#L113-L125 - pairs = ( - ('Product', 'product'), - ('SpecialGuest', 'special_guest'), - ('ApplicationController', 'application_controller'), - ('Area51Controller', 'area51_controller'), - ('HTMLTidy', 'html_tidy'), - ('HTMLTidyGenerator', 'html_tidy_generator'), - ('FreeBSD', 'free_bsd'), - ('HTML', 'html'), - ) - - for camel, underscored in pairs: - assert ci.cifti2._underscore(camel) == underscored - - -class TestCifti2ImageAPI(_TDA, SerializeMixin, DtypeOverrideMixin): - """Basic validation for Cifti2Image instances""" - - # A callable returning an image from ``image_maker(data, header)`` - image_maker = ci.Cifti2Image - # A callable returning a header from ``header_maker()`` - header_maker = ci.Cifti2Header - # A callable returning a nifti header - ni_header_maker = Nifti2Header - example_shapes = ((2,), (2, 3), (2, 3, 4)) - standard_extension = '.nii' - storable_dtypes = ( - np.int8, - np.uint8, - np.int16, - np.uint16, - np.int32, - np.uint32, - np.int64, - np.uint64, - np.float32, - np.float64, - ) - - def make_imaker(self, arr, header=None, ni_header=None): - for idx, sz in enumerate(arr.shape): - maps = [ci.Cifti2NamedMap(str(value)) for value in range(sz)] - mim = ci.Cifti2MatrixIndicesMap((idx,), 'CIFTI_INDEX_TYPE_SCALARS', maps=maps) - header.matrix.append(mim) - return lambda: self.image_maker(arr.copy(), header, ni_header) diff --git a/nibabel/cifti2/tests/test_cifti2io_axes.py b/nibabel/cifti2/tests/test_cifti2io_axes.py deleted file mode 100644 index 2f5e781e44..0000000000 --- a/nibabel/cifti2/tests/test_cifti2io_axes.py +++ /dev/null @@ -1,237 +0,0 @@ -import os -import tempfile - -import numpy as np - -import nibabel as nib -from nibabel.cifti2 import cifti2, cifti2_axes -from nibabel.tests.nibabel_data import get_nibabel_data, needs_nibabel_data - -test_directory = os.path.join(get_nibabel_data(), 'nitest-cifti2') - -hcp_labels = [ - 'CortexLeft', - 'CortexRight', - 'AccumbensLeft', - 'AccumbensRight', - 'AmygdalaLeft', - 'AmygdalaRight', - 'brain_stem', - 'CaudateLeft', - 'CaudateRight', - 'CerebellumLeft', - 'CerebellumRight', - 'Diencephalon_ventral_left', - 'Diencephalon_ventral_right', - 'HippocampusLeft', - 'HippocampusRight', - 'PallidumLeft', - 'PallidumRight', - 'PutamenLeft', - 'PutamenRight', - 'ThalamusLeft', - 'ThalamusRight', -] - -hcp_n_elements = [ - 29696, - 29716, - 135, - 140, - 315, - 332, - 3472, - 728, - 755, - 8709, - 9144, - 706, - 712, - 764, - 795, - 297, - 260, - 1060, - 1010, - 1288, - 1248, -] - -hcp_affine = np.array( - [[-2.0, 0.0, 0.0, 90.0], [0.0, 2.0, 0.0, -126.0], [0.0, 0.0, 2.0, -72.0], [0.0, 0.0, 0.0, 1.0]] -) - - -def check_hcp_grayordinates(brain_model): - """Checks that a BrainModelAxis matches the expected 32k HCP grayordinates""" - assert isinstance(brain_model, cifti2_axes.BrainModelAxis) - structures = list(brain_model.iter_structures()) - assert len(structures) == len(hcp_labels) - idx_start = 0 - for idx, (name, _, bm), label, nel in zip( - range(len(structures)), structures, hcp_labels, hcp_n_elements - ): - if idx < 2: - assert name in bm.nvertices.keys() - assert (bm.voxel == -1).all() - assert (bm.vertex != -1).any() - assert bm.nvertices[name] == 32492 - else: - assert name not in bm.nvertices.keys() - assert (bm.voxel != -1).any() - assert (bm.vertex == -1).all() - assert (bm.affine == hcp_affine).all() - assert bm.volume_shape == (91, 109, 91) - assert name == cifti2_axes.BrainModelAxis.to_cifti_brain_structure_name(label) - assert len(bm) == nel - assert (bm.name == brain_model.name[idx_start : idx_start + nel]).all() - assert (bm.voxel == brain_model.voxel[idx_start : idx_start + nel]).all() - assert (bm.vertex == brain_model.vertex[idx_start : idx_start + nel]).all() - idx_start += nel - assert idx_start == len(brain_model) - - assert (brain_model.vertex[:5] == np.arange(5)).all() - assert structures[0][2].vertex[-1] == 32491 - assert structures[1][2].vertex[0] == 0 - assert structures[1][2].vertex[-1] == 32491 - assert structures[-1][2].name[-1] == brain_model.name[-1] - assert (structures[-1][2].voxel[-1] == brain_model.voxel[-1]).all() - assert structures[-1][2].vertex[-1] == brain_model.vertex[-1] - assert (brain_model.voxel[-1] == [38, 55, 46]).all() - assert (brain_model.voxel[70000] == [56, 22, 19]).all() - - -def check_Conte69(brain_model): - """Checks that the BrainModelAxis matches the expected Conte69 surface coordinates""" - assert isinstance(brain_model, cifti2_axes.BrainModelAxis) - structures = list(brain_model.iter_structures()) - assert len(structures) == 2 - assert structures[0][0] == 'CIFTI_STRUCTURE_CORTEX_LEFT' - assert structures[0][2].surface_mask.all() - assert structures[1][0] == 'CIFTI_STRUCTURE_CORTEX_RIGHT' - assert structures[1][2].surface_mask.all() - assert (brain_model.voxel == -1).all() - - assert (brain_model.vertex[:5] == np.arange(5)).all() - assert structures[0][2].vertex[-1] == 32491 - assert structures[1][2].vertex[0] == 0 - assert structures[1][2].vertex[-1] == 32491 - - -def check_rewrite(arr, axes, extension='.nii'): - """ - Checks whether writing the Cifti2 array to disc and reading it back in gives the same object - - Parameters - ---------- - arr : array - N-dimensional array of data - axes : Sequence[cifti2_axes.Axis] - sequence of length N with the meaning of the rows/columns along each dimension - extension : str - custom extension to use - """ - (fd, name) = tempfile.mkstemp(extension) - cifti2.Cifti2Image(arr, header=axes).to_filename(name) - img = nib.load(name) - arr2 = img.get_fdata() - assert np.allclose(arr, arr2) - for idx in range(len(img.shape)): - assert axes[idx] == img.header.get_axis(idx) - return img - - -@needs_nibabel_data('nitest-cifti2') -def test_read_ones(): - img = nib.load(os.path.join(test_directory, 'ones.dscalar.nii')) - arr = img.get_fdata() - axes = [img.header.get_axis(dim) for dim in range(2)] - assert (arr == 1).all() - assert isinstance(axes[0], cifti2_axes.ScalarAxis) - assert len(axes[0]) == 1 - assert axes[0].name[0] == 'ones' - assert axes[0].meta[0] == {} - check_hcp_grayordinates(axes[1]) - img = check_rewrite(arr, axes) - check_hcp_grayordinates(img.header.get_axis(1)) - - -@needs_nibabel_data('nitest-cifti2') -def test_read_conte69_dscalar(): - img = nib.load( - os.path.join(test_directory, 'Conte69.MyelinAndCorrThickness.32k_fs_LR.dscalar.nii') - ) - arr = img.get_fdata() - axes = [img.header.get_axis(dim) for dim in range(2)] - assert isinstance(axes[0], cifti2_axes.ScalarAxis) - assert len(axes[0]) == 2 - assert axes[0].name[0] == 'MyelinMap_BC_decurv' - assert axes[0].name[1] == 'corrThickness' - assert axes[0].meta[0] == { - 'PaletteColorMapping': '\n MODE_AUTO_SCALE_PERCENTAGE\n 98.000000 2.000000 2.000000 98.000000\n -100.000000 0.000000 0.000000 100.000000\n ROY-BIG-BL\n true\n true\n false\n true\n THRESHOLD_TEST_SHOW_OUTSIDE\n THRESHOLD_TYPE_OFF\n false\n -1.000000 1.000000\n -1.000000 1.000000\n -1.000000 1.000000\n \n PALETTE_THRESHOLD_RANGE_MODE_MAP\n' - } - check_Conte69(axes[1]) - check_rewrite(arr, axes) - - -@needs_nibabel_data('nitest-cifti2') -def test_read_conte69_dtseries(): - img = nib.load( - os.path.join(test_directory, 'Conte69.MyelinAndCorrThickness.32k_fs_LR.dtseries.nii') - ) - arr = img.get_fdata() - axes = [img.header.get_axis(dim) for dim in range(2)] - assert isinstance(axes[0], cifti2_axes.SeriesAxis) - assert len(axes[0]) == 2 - assert axes[0].start == 0 - assert axes[0].step == 1 - assert axes[0].size == arr.shape[0] - assert (axes[0].time == [0, 1]).all() - check_Conte69(axes[1]) - check_rewrite(arr, axes) - - -@needs_nibabel_data('nitest-cifti2') -def test_read_conte69_dlabel(): - img = nib.load( - os.path.join(test_directory, 'Conte69.parcellations_VGD11b.32k_fs_LR.dlabel.nii') - ) - arr = img.get_fdata() - axes = [img.header.get_axis(dim) for dim in range(2)] - assert isinstance(axes[0], cifti2_axes.LabelAxis) - assert len(axes[0]) == 3 - assert ( - axes[0].name - == [ - 'Composite Parcellation-lh (FRB08_OFP03_retinotopic)', - 'Brodmann lh (from colin.R via pals_R-to-fs_LR)', - 'MEDIAL WALL lh (fs_LR)', - ] - ).all() - assert axes[0].label[1][70] == ('19_B05', (1.0, 0.867, 0.467, 1.0)) - assert (axes[0].meta == [{}] * 3).all() - check_Conte69(axes[1]) - check_rewrite(arr, axes) - - -@needs_nibabel_data('nitest-cifti2') -def test_read_conte69_ptseries(): - img = nib.load( - os.path.join(test_directory, 'Conte69.MyelinAndCorrThickness.32k_fs_LR.ptseries.nii') - ) - arr = img.get_fdata() - axes = [img.header.get_axis(dim) for dim in range(2)] - assert isinstance(axes[0], cifti2_axes.SeriesAxis) - assert len(axes[0]) == 2 - assert axes[0].start == 0 - assert axes[0].step == 1 - assert axes[0].size == arr.shape[0] - assert (axes[0].time == [0, 1]).all() - - assert len(axes[1]) == 54 - voxels, vertices = axes[1]['ER_FRB08'] - assert voxels.shape == (0, 3) - assert len(vertices) == 2 - assert vertices['CIFTI_STRUCTURE_CORTEX_LEFT'].shape == (206 // 2,) - assert vertices['CIFTI_STRUCTURE_CORTEX_RIGHT'].shape == (206 // 2,) - check_rewrite(arr, axes) diff --git a/nibabel/cifti2/tests/test_cifti2io_header.py b/nibabel/cifti2/tests/test_cifti2io_header.py deleted file mode 100644 index ecdf0c69a7..0000000000 --- a/nibabel/cifti2/tests/test_cifti2io_header.py +++ /dev/null @@ -1,455 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## - -from os.path import dirname -from os.path import join as pjoin - -import numpy as np -import pytest -from numpy.testing import assert_array_almost_equal -from packaging.version import Version - -import nibabel as nib -from nibabel import cifti2 as ci -from nibabel.cifti2.parse_cifti2 import _Cifti2AsNiftiHeader -from nibabel.tests import test_nifti2 as tn2 -from nibabel.tests.nibabel_data import get_nibabel_data, needs_nibabel_data -from nibabel.tmpdirs import InTemporaryDirectory - -NIBABEL_TEST_DATA = pjoin(dirname(nib.__file__), 'tests', 'data') -NIFTI2_DATA = pjoin(NIBABEL_TEST_DATA, 'example_nifti2.nii.gz') - -CIFTI2_DATA = pjoin(get_nibabel_data(), 'nitest-cifti2') - -DATA_FILE1 = pjoin(CIFTI2_DATA, '') -DATA_FILE2 = pjoin(CIFTI2_DATA, 'Conte69.MyelinAndCorrThickness.32k_fs_LR.dscalar.nii') -DATA_FILE3 = pjoin(CIFTI2_DATA, 'Conte69.MyelinAndCorrThickness.32k_fs_LR.dtseries.nii') -DATA_FILE4 = pjoin(CIFTI2_DATA, 'Conte69.MyelinAndCorrThickness.32k_fs_LR.ptseries.nii') -DATA_FILE5 = pjoin(CIFTI2_DATA, 'Conte69.parcellations_VGD11b.32k_fs_LR.dlabel.nii') -DATA_FILE6 = pjoin(CIFTI2_DATA, 'ones.dscalar.nii') -datafiles = [DATA_FILE2, DATA_FILE3, DATA_FILE4, DATA_FILE5, DATA_FILE6] - - -def test_space_separated_affine(): - ci.Cifti2Image.from_filename(pjoin(NIBABEL_TEST_DATA, 'row_major.dconn.nii')) - - -def test_read_nifti2(): - # Error trying to read a CIFTI-2 image from a NIfTI2-only image. - filemap = ci.Cifti2Image.make_file_map() - for k in filemap: - filemap[k].fileobj = open(NIFTI2_DATA) - with pytest.raises(ValueError): - ci.Cifti2Image.from_file_map(filemap) - - -@needs_nibabel_data('nitest-cifti2') -def test_read_internal(): - img2 = ci.load(DATA_FILE6) - assert isinstance(img2.header, ci.Cifti2Header) - assert img2.shape == (1, 91282) - - -@needs_nibabel_data('nitest-cifti2') -def test_read_and_proxies(): - img2 = nib.load(DATA_FILE6) - assert isinstance(img2.header, ci.Cifti2Header) - assert img2.shape == (1, 91282) - # While we cannot reshape arrayproxies, all images are in-memory - assert not img2.in_memory - data = img2.get_fdata() - assert data is not img2.dataobj - # Uncaching has no effect, images are always array images - img2.uncache() - assert data is not img2.get_fdata() - - -@needs_nibabel_data('nitest-cifti2') -def test_version(): - for dat in datafiles: - img = nib.load(dat) - assert Version(img.header.version) == Version('2') - - -@needs_nibabel_data('nitest-cifti2') -def test_readwritedata(): - with InTemporaryDirectory(): - for name in datafiles: - img = ci.load(name) - ci.save(img, 'test.nii') - img2 = ci.load('test.nii') - assert len(img.header.matrix) == len(img2.header.matrix) - # Order should be preserved in load/save - for mim1, mim2 in zip(img.header.matrix, img2.header.matrix): - named_maps1 = [m_ for m_ in mim1 if isinstance(m_, ci.Cifti2NamedMap)] - named_maps2 = [m_ for m_ in mim2 if isinstance(m_, ci.Cifti2NamedMap)] - assert len(named_maps1) == len(named_maps2) - for map1, map2 in zip(named_maps1, named_maps2): - assert map1.map_name == map2.map_name - if map1.label_table is None: - assert map2.label_table is None - else: - assert len(map1.label_table) == len(map2.label_table) - - assert_array_almost_equal(img.dataobj, img2.dataobj) - - -@needs_nibabel_data('nitest-cifti2') -def test_nibabel_readwritedata(): - with InTemporaryDirectory(): - for name in datafiles: - img = nib.load(name) - nib.save(img, 'test.nii') - img2 = nib.load('test.nii') - assert len(img.header.matrix) == len(img2.header.matrix) - # Order should be preserved in load/save - for mim1, mim2 in zip(img.header.matrix, img2.header.matrix): - named_maps1 = [m_ for m_ in mim1 if isinstance(m_, ci.Cifti2NamedMap)] - named_maps2 = [m_ for m_ in mim2 if isinstance(m_, ci.Cifti2NamedMap)] - assert len(named_maps1) == len(named_maps2) - for map1, map2 in zip(named_maps1, named_maps2): - assert map1.map_name == map2.map_name - if map1.label_table is None: - assert map2.label_table is None - else: - assert len(map1.label_table) == len(map2.label_table) - assert_array_almost_equal(img.dataobj, img2.dataobj) - - -@needs_nibabel_data('nitest-cifti2') -def test_cifti2types(): - """Check that we instantiate Cifti2 classes correctly, and that our - test files exercise all classes""" - counter = { - ci.Cifti2LabelTable: 0, - ci.Cifti2Label: 0, - ci.Cifti2NamedMap: 0, - ci.Cifti2Surface: 0, - ci.Cifti2VoxelIndicesIJK: 0, - ci.Cifti2Vertices: 0, - ci.Cifti2Parcel: 0, - ci.Cifti2TransformationMatrixVoxelIndicesIJKtoXYZ: 0, - ci.Cifti2Volume: 0, - ci.Cifti2VertexIndices: 0, - ci.Cifti2BrainModel: 0, - ci.Cifti2MatrixIndicesMap: 0, - } - - for name in datafiles: - hdr = ci.load(name).header - # Matrix and MetaData aren't conditional, so don't bother counting - assert isinstance(hdr.matrix, ci.Cifti2Matrix) - assert isinstance(hdr.matrix.metadata, ci.Cifti2MetaData) - for mim in hdr.matrix: - assert isinstance(mim, ci.Cifti2MatrixIndicesMap) - counter[ci.Cifti2MatrixIndicesMap] += 1 - for map_ in mim: - print(map_) - if isinstance(map_, ci.Cifti2BrainModel): - counter[ci.Cifti2BrainModel] += 1 - if isinstance(map_.vertex_indices, ci.Cifti2VertexIndices): - counter[ci.Cifti2VertexIndices] += 1 - if isinstance(map_.voxel_indices_ijk, ci.Cifti2VoxelIndicesIJK): - counter[ci.Cifti2VoxelIndicesIJK] += 1 - elif isinstance(map_, ci.Cifti2NamedMap): - counter[ci.Cifti2NamedMap] += 1 - assert isinstance(map_.metadata, ci.Cifti2MetaData) - if isinstance(map_.label_table, ci.Cifti2LabelTable): - counter[ci.Cifti2LabelTable] += 1 - for label in map_.label_table: - assert isinstance(map_.label_table[label], ci.Cifti2Label) - counter[ci.Cifti2Label] += 1 - elif isinstance(map_, ci.Cifti2Parcel): - counter[ci.Cifti2Parcel] += 1 - if isinstance(map_.voxel_indices_ijk, ci.Cifti2VoxelIndicesIJK): - counter[ci.Cifti2VoxelIndicesIJK] += 1 - assert isinstance(map_.vertices, list) - for vtcs in map_.vertices: - assert isinstance(vtcs, ci.Cifti2Vertices) - counter[ci.Cifti2Vertices] += 1 - elif isinstance(map_, ci.Cifti2Surface): - counter[ci.Cifti2Surface] += 1 - elif isinstance(map_, ci.Cifti2Volume): - counter[ci.Cifti2Volume] += 1 - if isinstance( - map_.transformation_matrix_voxel_indices_ijk_to_xyz, - ci.Cifti2TransformationMatrixVoxelIndicesIJKtoXYZ, - ): - counter[ci.Cifti2TransformationMatrixVoxelIndicesIJKtoXYZ] += 1 - - assert list(mim.named_maps) == [m_ for m_ in mim if isinstance(m_, ci.Cifti2NamedMap)] - assert list(mim.surfaces) == [m_ for m_ in mim if isinstance(m_, ci.Cifti2Surface)] - assert list(mim.parcels) == [m_ for m_ in mim if isinstance(m_, ci.Cifti2Parcel)] - assert list(mim.brain_models) == [ - m_ for m_ in mim if isinstance(m_, ci.Cifti2BrainModel) - ] - assert ([mim.volume] if mim.volume else []) == [ - m_ for m_ in mim if isinstance(m_, ci.Cifti2Volume) - ] - - for klass, count in counter.items(): - assert count > 0, 'No exercise of ' + klass.__name__ - - -@needs_nibabel_data('nitest-cifti2') -def test_read_geometry(): - img = ci.Cifti2Image.from_filename(DATA_FILE6) - geometry_mapping = img.header.matrix.get_index_map(1) - - # For every brain model in ones.dscalar.nii defines: - # brain structure name, number of grayordinates, first vertex or voxel, last vertex or voxel - expected_geometry = [ - ('CIFTI_STRUCTURE_CORTEX_LEFT', 29696, 0, 32491), - ('CIFTI_STRUCTURE_CORTEX_RIGHT', 29716, 0, 32491), - ('CIFTI_STRUCTURE_ACCUMBENS_LEFT', 135, [49, 66, 28], [48, 72, 35]), - ('CIFTI_STRUCTURE_ACCUMBENS_RIGHT', 140, [40, 66, 29], [43, 66, 36]), - ('CIFTI_STRUCTURE_AMYGDALA_LEFT', 315, [55, 61, 21], [56, 58, 31]), - ('CIFTI_STRUCTURE_AMYGDALA_RIGHT', 332, [34, 62, 20], [36, 61, 31]), - ('CIFTI_STRUCTURE_BRAIN_STEM', 3472, [42, 41, 0], [46, 50, 36]), - ('CIFTI_STRUCTURE_CAUDATE_LEFT', 728, [50, 72, 32], [53, 60, 49]), - ('CIFTI_STRUCTURE_CAUDATE_RIGHT', 755, [40, 68, 33], [37, 62, 49]), - ('CIFTI_STRUCTURE_CEREBELLUM_LEFT', 8709, [49, 35, 4], [46, 37, 37]), - ('CIFTI_STRUCTURE_CEREBELLUM_RIGHT', 9144, [38, 35, 4], [44, 38, 36]), - ('CIFTI_STRUCTURE_DIENCEPHALON_VENTRAL_LEFT', 706, [52, 53, 26], [56, 49, 35]), - ('CIFTI_STRUCTURE_DIENCEPHALON_VENTRAL_RIGHT', 712, [39, 54, 26], [35, 49, 36]), - ('CIFTI_STRUCTURE_HIPPOCAMPUS_LEFT', 764, [55, 60, 21], [54, 44, 39]), - ('CIFTI_STRUCTURE_HIPPOCAMPUS_RIGHT', 795, [33, 60, 21], [38, 45, 39]), - ('CIFTI_STRUCTURE_PALLIDUM_LEFT', 297, [56, 59, 32], [55, 61, 39]), - ('CIFTI_STRUCTURE_PALLIDUM_RIGHT', 260, [36, 62, 32], [35, 62, 39]), - ('CIFTI_STRUCTURE_PUTAMEN_LEFT', 1060, [51, 66, 28], [58, 64, 43]), - ('CIFTI_STRUCTURE_PUTAMEN_RIGHT', 1010, [34, 66, 29], [31, 62, 43]), - ('CIFTI_STRUCTURE_THALAMUS_LEFT', 1288, [55, 47, 33], [52, 53, 46]), - ('CIFTI_STRUCTURE_THALAMUS_RIGHT', 1248, [32, 47, 34], [38, 55, 46]), - ] - current_index = 0 - for from_file, expected in zip(geometry_mapping.brain_models, expected_geometry): - assert from_file.model_type in ('CIFTI_MODEL_TYPE_SURFACE', 'CIFTI_MODEL_TYPE_VOXELS') - assert from_file.brain_structure == expected[0] - assert from_file.index_offset == current_index - assert from_file.index_count == expected[1] - current_index += from_file.index_count - - if from_file.model_type == 'CIFTI_MODEL_TYPE_SURFACE': - assert from_file.voxel_indices_ijk is None - assert len(from_file.vertex_indices) == expected[1] - assert from_file.vertex_indices[0] == expected[2] - assert from_file.vertex_indices[-1] == expected[3] - assert from_file.surface_number_of_vertices == 32492 - else: - assert from_file.vertex_indices is None - assert from_file.surface_number_of_vertices is None - assert len(from_file.voxel_indices_ijk) == expected[1] - assert from_file.voxel_indices_ijk[0] == expected[2] - assert from_file.voxel_indices_ijk[-1] == expected[3] - assert current_index == img.shape[1] - - expected_affine = [ - [-2, 0, 0, 90], - [0, 2, 0, -126], - [0, 0, 2, -72], - [0, 0, 0, 1], - ] - expected_dimensions = (91, 109, 91) - assert np.array_equal( - geometry_mapping.volume.transformation_matrix_voxel_indices_ijk_to_xyz.matrix, - expected_affine, - ) - assert geometry_mapping.volume.volume_dimensions == expected_dimensions - - -@needs_nibabel_data('nitest-cifti2') -def test_read_parcels(): - img = ci.Cifti2Image.from_filename(DATA_FILE4) - parcel_mapping = img.header.matrix.get_index_map(1) - - expected_parcels = [ - ('MEDIAL.WALL', ((719, 20, 28550), (810, 21, 28631))), - ('BA2_FRB08', ((516, 6757, 17888), (461, 6757, 17887))), - ('BA1_FRB08', ((211, 5029, 17974), (214, 3433, 17934))), - ('BA3b_FRB08', ((444, 3436, 18065), (397, 3436, 18065))), - ('BA4p_FRB08', ((344, 3445, 18164), (371, 3443, 18175))), - ('BA3a_FRB08', ((290, 3441, 18140), (289, 3440, 18140))), - ('BA4a_FRB08', ((471, 3446, 18181), (455, 3446, 19759))), - ('BA6_FRB08', ((1457, 2, 30951), (1400, 2, 30951))), - ('BA17_V1_FRB08', ((629, 23155, 25785), (635, 23155, 25759))), - ('BA45_FRB08', ((245, 10100, 18774), (214, 10103, 18907))), - ('BA44_FRB08', ((226, 10118, 19240), (273, 10119, 19270))), - ('hOc5_MT_FRB08', ((104, 15019, 23329), (80, 15023, 23376))), - ('BA18_V2_FRB08', ((702, 95, 25902), (651, 98, 25903))), - ('V3A_SHM07', ((82, 4, 25050), (82, 4, 25050))), - ('V3B_SHM07', ((121, 13398, 23303), (121, 13398, 23303))), - ('LO1_KPO10', ((54, 15007, 23543), (54, 15007, 23543))), - ('LO2_KPO10', ((79, 15013, 23636), (79, 15013, 23636))), - ('PITd_KPO10', ((53, 15018, 23769), (65, 15018, 23769))), - ('PITv_KPO10', ((72, 23480, 23974), (72, 23480, 23974))), - ('OP1_BSW08', ((470, 8421, 18790), (470, 8421, 18790))), - ('OP2_BSW08', ((67, 10, 31060), (67, 10, 31060))), - ('OP3_BSW08', ((119, 10137, 18652), (119, 10137, 18652))), - ('OP4_BSW08', ((191, 16613, 19429), (192, 16613, 19429))), - ('IPS1_SHM07', ((54, 11775, 14496), (54, 11775, 14496))), - ('IPS2_SHM07', ((71, 11771, 14587), (71, 11771, 14587))), - ('IPS3_SHM07', ((114, 11764, 14783), (114, 11764, 14783))), - ('IPS4_SHM07', ((101, 11891, 12653), (101, 11891, 12653))), - ('V7_SHM07', ((140, 11779, 14002), (140, 11779, 14002))), - ('V4v_SHM07', ((81, 23815, 24557), (90, 23815, 24557))), - ('V3d_KPO10', ((90, 23143, 25192), (115, 23143, 25192))), - ('14c_OFP03', ((22, 19851, 21311), (22, 19851, 21311))), - ('13a_OFP03', ((20, 20963, 21154), (20, 20963, 21154))), - ('47s_OFP03', ((211, 10182, 20343), (211, 10182, 20343))), - ('14r_OFP03', ((54, 21187, 21324), (54, 21187, 21324))), - ('13m_OFP03', ((103, 20721, 21075), (103, 20721, 21075))), - ('13l_OFP03', ((101, 20466, 20789), (101, 20466, 20789))), - ('32pl_OFP03', ((14, 19847, 21409), (14, 19847, 21409))), - ('25_OFP03', ((8, 19844, 27750), (8, 19844, 27750))), - ('47m_OFP03', ((200, 10174, 20522), (200, 10174, 20522))), - ('47l_OFP03', ((142, 10164, 19969), (160, 10164, 19969))), - ('Iai_OFP03', ((153, 10188, 20199), (153, 10188, 20199))), - ('10r_OFP03', ((138, 19811, 28267), (138, 19811, 28267))), - ('11m_OFP03', ((92, 20850, 21165), (92, 20850, 21165))), - ('11l_OFP03', ((200, 20275, 21029), (200, 20275, 21029))), - ('47r_OFP03', ((259, 10094, 20535), (259, 10094, 20535))), - ('10m_OFP03', ((102, 19825, 21411), (102, 19825, 21411))), - ('Iam_OFP03', ((15, 20346, 20608), (15, 20346, 20608))), - ('Ial_OFP03', ((89, 10194, 11128), (89, 10194, 11128))), - ('24_OFP03', ((39, 19830, 28279), (36, 19830, 28279))), - ('Iapm_OFP03', ((7, 20200, 20299), (7, 20200, 20299))), - ('10p_OFP03', ((480, 19780, 28640), (480, 19780, 28640))), - ('V6_PHG06', ((72, 12233, 12869), (72, 12233, 12869))), - ('ER_FRB08', ((103, 21514, 26470), (103, 21514, 26470))), - ('13b_OFP03', ((60, 21042, 21194), (71, 21040, 21216))), - ] - - assert img.shape[1] == len(expected_parcels) - assert len(list(parcel_mapping.parcels)) == len(expected_parcels) - - for (name, expected_surfaces), parcel in zip(expected_parcels, parcel_mapping.parcels): - assert parcel.name == name - assert len(parcel.vertices) == 2 - for vertices, orientation, (length, first_element, last_element) in zip( - parcel.vertices, ('LEFT', 'RIGHT'), expected_surfaces - ): - assert len(vertices) == length - assert vertices[0] == first_element - assert vertices[-1] == last_element - assert vertices.brain_structure == f'CIFTI_STRUCTURE_CORTEX_{orientation}' - - -@needs_nibabel_data('nitest-cifti2') -def test_read_scalar(): - img = ci.Cifti2Image.from_filename(DATA_FILE2) - scalar_mapping = img.header.matrix.get_index_map(0) - - expected_names = ('MyelinMap_BC_decurv', 'corrThickness') - assert img.shape[0] == len(expected_names) - assert len(list(scalar_mapping.named_maps)) == len(expected_names) - - expected_meta = [('PaletteColorMapping', '\n ', - version='%prog ' + nib.__version__, - ) - - p.add_options( - [ - Option( - '-v', - '--verbose', - action='http://github.com/count', - dest='verbose', - default=0, - help='make noise. Could be specified multiple times', - ), - ] - ) - - p.add_options( - [ - Option( - '-L', - '--follow-links', - action='http://github.com/store_true', - dest='followlinks', - default=False, - help='Follow symbolic links in DICOM directory', - ), - ] - ) - return p - - -def main(args=None): - parser = get_opt_parser() - (opts, files) = parser.parse_args(args=args) - - if opts.verbose: - logger.addHandler(logging.StreamHandler(sys.stdout)) - 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') - sys.exit(1) - - fs = DICOMFS( - dash_s_do='setsingle', followlinks=opts.followlinks, dicom_path=files[0].decode(encoding) - ) - fs.parse(['-f', '-s', files[1]]) - try: - fs.main() - except fuse.FuseError: - # fuse prints the error message - sys.exit(1) - - sys.exit(0) diff --git a/nibabel/cmdline/diff.py b/nibabel/cmdline/diff.py deleted file mode 100755 index 6a44f3ce55..0000000000 --- a/nibabel/cmdline/diff.py +++ /dev/null @@ -1,381 +0,0 @@ -#!python -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -""" -Quick summary of the differences among a set of neuroimaging files - -Notes: - - difference in data types for header fields will be detected, but - endianness difference will not be detected. It is done so to compare files - with native endianness used in data files. -""" - -import hashlib -import os -import re -import sys -from collections import OrderedDict -from optparse import Option, OptionParser - -import numpy as np - -import nibabel as nib -import nibabel.cmdline.utils - - -def get_opt_parser(): - # use module docstring for help output - p = OptionParser( - usage=f'{sys.argv[0]} [OPTIONS] [FILE ...]\n\n' + __doc__, - version='%prog ' + nib.__version__, - ) - - p.add_options( - [ - Option( - '-v', - '--verbose', - action='http://github.com/count', - dest='verbose', - default=0, - help='Make more noise. Could be specified multiple times', - ), - Option( - '-H', - '--header-fields', - dest='header_fields', - default='all', - help='Header fields (comma separated) to be printed as well (if present)', - ), - Option( - '--ma', - '--data-max-abs-diff', - dest='data_max_abs_diff', - type=float, - default=0.0, - help='Maximal absolute difference in data between files to tolerate.', - ), - Option( - '--mr', - '--data-max-rel-diff', - dest='data_max_rel_diff', - type=float, - default=0.0, - help='Maximal relative difference in data between files to' - ' tolerate. If --data-max-abs-diff is also specified,' - ' only the data points with absolute difference greater' - ' than that value would be considered for relative' - ' difference check.', - ), - Option( - '--dt', - '--datatype', - dest='dtype', - default=np.float64, - help="Enter a numpy datatype such as 'float32'.", - ), - ] - ) - - return p - - -def are_values_different(*values): - """Generically compare values, return True if different - - Note that comparison is targeting reporting of comparison of the headers - so has following specifics: - - even a difference in data types is considered a difference, i.e. 1 != 1.0 - - nans are considered to be the "same", although generally nan != nan - """ - value0 = values[0] - - # to not recompute over again - if isinstance(value0, np.ndarray): - try: - # np.asarray for elderly numpys, e.g. 1.7.1 where for - # degenerate arrays (shape ()) it would return a pure scalar - value0_nans = np.asanyarray(np.isnan(value0)) - value0_nonnans = np.asanyarray(np.logical_not(value0_nans)) - # if value0_nans.size == 1: - # import pdb; pdb.set_trace() - if not np.any(value0_nans): - value0_nans = None - except TypeError as exc: - str_exc = str(exc) - # Not implemented in numpy 1.7.1 - if 'not supported' in str_exc or 'not implemented' in str_exc: - value0_nans = None - else: - raise - - for value in values[1:]: - if type(value0) != type(value): # if types are different, then we consider them different - return True - elif isinstance(value0, np.ndarray): - # use .dtype.type to provide endianness agnostic comparison - if value0.dtype.type != value.dtype.type or value0.shape != value.shape: - return True - # there might be nans and they need special treatment - if value0_nans is not None: - value_nans = np.isnan(value) - if np.any(value0_nans != value_nans): - return True - if np.any(value0[value0_nonnans] != value[value0_nonnans]): - return True - elif np.any(value0 != value): - return True - elif value0 is np.nan: - if value is not np.nan: - return True - elif value0 != value: - return True - - return False - - -def get_headers_diff(file_headers, names=None): - """Get difference between headers - - Parameters - ---------- - file_headers: list of actual headers (dicts) from files - names: list of header fields to test - - Returns - ------- - dict - str: list for each header field which differs, return list of - values per each file - """ - difference = OrderedDict() - fields = names - - if names is None: - fields = file_headers[0].keys() - - # for each header field - for field in fields: - values = [header.get(field) for header in file_headers] # get corresponding value - - # if these values are different, store them in a dictionary - if are_values_different(*values): - difference[field] = values - - return difference - - -def get_data_hash_diff(files, dtype=np.float64): - """Get difference between md5 values of data - - Parameters - ---------- - files: list of actual files - - Returns - ------- - list - np.array: md5 values of respective files - """ - - md5sums = [ - hashlib.md5(np.ascontiguousarray(nib.load(f).get_fdata(dtype=dtype))).hexdigest() - for f in files - ] - - if len(set(md5sums)) == 1: - return [] - - return md5sums - - -def get_data_diff(files, max_abs=0, max_rel=0, dtype=np.float64): - """Get difference between data - - Parameters - ---------- - files: list of (str or ndarray) - If list of strings is provided -- they must be existing file names - max_abs: float, optional - Maximal absolute difference to tolerate. - max_rel: float, optional - Maximal relative (`abs(diff)/mean(diff)`) difference to tolerate. - If `max_abs` is specified, then those data points with lesser than that - absolute difference, are not considered for relative difference testing - dtype: np, optional - Datatype to be used when extracting data from files - - Returns - ------- - diffs: OrderedDict - An ordered dict with a record per each file which has differences - with other files subsequent detected. Each record is a list of - difference records, one per each file pair. - Each difference record is an Ordered Dict with possible keys - 'abs' or 'rel' showing maximal absolute or relative differences - in the file or the record ('CMP': 'incompat') if file shapes - are incompatible. - """ - - # we are doomed to keep them in RAM now - data = [f if isinstance(f, np.ndarray) else nib.load(f).get_fdata(dtype=dtype) for f in files] - diffs = OrderedDict() - for i, d1 in enumerate(data[:-1]): - # populate empty entries for non-compared - diffs1 = [None] * (i + 1) - - for j, d2 in enumerate(data[i + 1 :], i + 1): - if d1.shape == d2.shape: - abs_diff = np.abs(d1 - d2) - mean_abs = (np.abs(d1) + np.abs(d2)) * 0.5 - candidates = np.logical_or(mean_abs != 0, abs_diff != 0) - - if max_abs: - candidates[abs_diff <= max_abs] = False - - max_abs_diff = np.max(abs_diff) - if np.any(candidates): - rel_diff = abs_diff[candidates] / mean_abs[candidates] - if max_rel: - sub_thr = rel_diff <= max_rel - # Since we operated on sub-selected values already, we need - # to plug them back in - candidates[tuple(indexes[sub_thr] for indexes in np.where(candidates))] = ( - False - ) - max_rel_diff = np.max(rel_diff) - else: - max_rel_diff = 0 - - if np.any(candidates): - diff_rec = OrderedDict() # so that abs goes before relative - - diff_rec['abs'] = max_abs_diff.astype(dtype) - diff_rec['rel'] = max_rel_diff.astype(dtype) - diffs1.append(diff_rec) - else: - diffs1.append(None) - - else: - diffs1.append({'CMP': 'incompat'}) - - if any(diffs1): - diffs[f'DATA(diff {i + 1}:)'] = diffs1 - - return diffs - - -def display_diff(files, diff): - """Format header differences into a nice string - - Parameters - ---------- - files: list of files that were compared so we can print their names - diff: dict of different valued header fields - - Returns - ------- - str - string-formatted table of differences - """ - output = '' - field_width = '{:<15}' - filename_width = '{:<53}' - value_width = '{:<55}' - - output += 'These files are different.\n' - output += field_width.format('Field/File') - - for i, f in enumerate(files, 1): - output += f'{i}:{filename_width.format(os.path.basename(f))}' - - output += '\n' - - for key, value in diff.items(): - output += field_width.format(key) - - for item in value: - if isinstance(item, dict): - item_str = ', '.join('{}: {}'.format(*i) for i in item.items()) - elif item is None: - item_str = '-' - else: - item_str = str(item) - # Value might start/end with some invisible spacing characters so we - # would "condition" it on both ends a bit - item_str = re.sub(r'^[ \t]+', '<', item_str) - item_str = re.sub(r'[ \t]+$', '>', item_str) - # and also replace some other invisible symbols with a question - # mark - item_str = re.sub(r'[\x00]', '?', item_str) - output += value_width.format(item_str) - - output += '\n' - - return output - - -def diff( - files, header_fields='all', data_max_abs_diff=None, data_max_rel_diff=None, dtype=np.float64 -): - assert len(files) >= 2, 'Please enter at least two files' - - file_headers = [nib.load(f).header for f in files] - - # signals "all fields" - if header_fields == 'all': - # TODO: header fields might vary across file types, - # thus prior sensing would be needed - header_fields = file_headers[0].keys() - else: - header_fields = header_fields.split(',') - - diff = get_headers_diff(file_headers, header_fields) - - data_md5_diffs = get_data_hash_diff(files, dtype) - if data_md5_diffs: - # provide details, possibly triggering the ignore of the difference - # in data - data_diffs = get_data_diff( - files, max_abs=data_max_abs_diff, max_rel=data_max_rel_diff, dtype=dtype - ) - if data_diffs: - diff['DATA(md5)'] = data_md5_diffs - diff.update(data_diffs) - - return diff - - -def main(args=None, out=None): - """Getting the show on the road""" - - out = out or sys.stdout - parser = get_opt_parser() - (opts, files) = parser.parse_args(args) - - nibabel.cmdline.utils.verbose_level = opts.verbose - - if nibabel.cmdline.utils.verbose_level < 3: - # suppress nibabel format-compliance warnings - nib.imageglobals.logger.level = 50 - - files_diff = diff( - files, - header_fields=opts.header_fields, - data_max_abs_diff=opts.data_max_abs_diff, - data_max_rel_diff=opts.data_max_rel_diff, - dtype=opts.dtype, - ) - - if files_diff: - out.write(display_diff(files, files_diff)) - raise SystemExit(1) - else: - out.write('These files are identical.\n') - raise SystemExit(0) diff --git a/nibabel/cmdline/ls.py b/nibabel/cmdline/ls.py deleted file mode 100755 index 8ddc37869b..0000000000 --- a/nibabel/cmdline/ls.py +++ /dev/null @@ -1,200 +0,0 @@ -#!python -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -""" -Output a summary table for neuroimaging files (resolution, dimensionality, etc.) -""" - -import sys -from optparse import Option, OptionParser - -import numpy as np - -import nibabel as nib -import nibabel.cmdline.utils -from nibabel.cmdline.utils import _err, ap, safe_get, table2string, verbose - -__copyright__ = 'Copyright (c) 2011-18 Yaroslav Halchenko and NiBabel contributors' -__license__ = 'MIT' - - -MAX_UNIQUE = 1000 # maximal number of unique values to report for --counts - - -def get_opt_parser(): - # use module docstring for help output - p = OptionParser( - usage=f'{sys.argv[0]} [OPTIONS] [FILE ...]\n\n' + __doc__, - version='%prog ' + nib.__version__, - ) - - p.add_options( - [ - Option( - '-v', - '--verbose', - action='http://github.com/count', - dest='verbose', - default=0, - help='Make more noise. Could be specified multiple times', - ), - Option( - '-H', - '--header-fields', - dest='header_fields', - default='', - help='Header fields (comma separated) to be printed as well (if present)', - ), - Option( - '-s', - '--stats', - action='http://github.com/store_true', - dest='stats', - default=False, - help='Output basic data statistics', - ), - Option( - '-c', - '--counts', - action='http://github.com/store_true', - dest='counts', - default=False, - help='Output counts - number of entries for each numeric value ' - '(useful for int ROI maps)', - ), - Option( - '--all-counts', - action='http://github.com/store_true', - dest='all_counts', - default=False, - help=f'Output all counts, even if number of unique values > {MAX_UNIQUE}', - ), - Option( - '-z', - '--zeros', - action='http://github.com/store_true', - dest='stats_zeros', - default=False, - help='Include zeros into output basic data statistics (--stats, --counts)', - ), - ] - ) - - return p - - -def proc_file(f, opts): - verbose(1, f'Loading {f}') - - row = [f'@l{f}'] - try: - vol = nib.load(f) - h = vol.header - except Exception as e: - row += ['failed'] - verbose(2, f'Failed to gather information -- {e}') - return row - - row += [ - str(safe_get(h, 'data_dtype')), - f'@l[{ap(safe_get(h, "data_shape"), "%3g")}]', - f'@l{ap(safe_get(h, "zooms"), "%.2f", "x")}', - ] - # Slope - if ( - hasattr(h, 'has_data_slope') - and (h.has_data_slope or h.has_data_intercept) - and not h.get_slope_inter() in ((1.0, 0.0), (None, None)) - ): - row += ['@l*{:.3g}+{:.3g}'.format(*h.get_slope_inter())] - else: - row += [''] - - if hasattr(h, 'extensions') and len(h.extensions): - row += [f'@l#exts: {len(h.extensions)}'] - else: - row += [''] - - if opts.header_fields: - # signals "all fields" - if opts.header_fields == 'all': - # TODO: might vary across file types, thus prior sensing - # would be needed - header_fields = h.keys() - else: - header_fields = opts.header_fields.split(',') - - for f in header_fields: - if not f: # skip empty - continue - try: - row += [str(h[f])] - except (KeyError, ValueError): - row += [_err()] - - try: - if ( - hasattr(h, 'get_qform') - and hasattr(h, 'get_sform') - and (h.get_qform() != h.get_sform()).any() - ): - row += ['sform'] - else: - row += [''] - except Exception as e: - verbose(2, f'Failed to obtain qform or sform -- {e}') - if isinstance(h, nib.AnalyzeHeader): - row += [''] - else: - row += [_err()] - - if opts.stats or opts.counts: - # We are doomed to load data - try: - d = np.asarray(vol.dataobj) - if not opts.stats_zeros: - d = d[np.nonzero(d)] - else: - # at least flatten it -- functionality below doesn't - # depend on the original shape, so let's use a flat view - d = d.reshape(-1) - if opts.stats: - # just # of elements - row += [f'@l[{np.prod(d.shape)}]'] - # stats - row += [f'@l[{np.min(d):.2g}, {np.max(d):.2g}]' if len(d) else '-'] - if opts.counts: - items, inv = np.unique(d, return_inverse=True) - if len(items) > 1000 and not opts.all_counts: - counts = _err(f'{len(items)} uniques. Use --all-counts') - else: - freq = np.bincount(inv) - counts = ' '.join(f'{i:g}:{f}' for i, f in zip(items, freq)) - row += ['@l' + counts] - except OSError as e: - verbose(2, f'Failed to obtain stats/counts -- {e}') - row += [_err()] - return row - - -def main(args=None): - """Show must go on""" - - parser = get_opt_parser() - (opts, files) = parser.parse_args(args=args) - - nibabel.cmdline.utils.verbose_level = opts.verbose - - if nibabel.cmdline.utils.verbose_level < 3: - # suppress nibabel format-compliance warnings - nib.imageglobals.logger.level = 50 - - rows = [proc_file(f, opts) for f in files] - - print(table2string(rows)) diff --git a/nibabel/cmdline/nifti_dx.py b/nibabel/cmdline/nifti_dx.py deleted file mode 100644 index eb917a04b8..0000000000 --- a/nibabel/cmdline/nifti_dx.py +++ /dev/null @@ -1,48 +0,0 @@ -#!python -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Print nifti diagnostics for header files""" - -from argparse import ArgumentParser - -import nibabel as nib - -__author__ = 'Matthew Brett' -__copyright__ = 'Copyright (c) 2011-18 Matthew Brett and NiBabel contributors' -__license__ = 'MIT' - - -def main(args=None): - """Go go team""" - parser = ArgumentParser(description=__doc__) - parser.add_argument('--version', action='http://github.com/version', version=f'%(prog)s {nib.__version__}') - parser.add_argument( - '-1', - '--nifti1', - dest='header_class', - action='http://github.com/store_const', - const=nib.Nifti1Header, - default=nib.Nifti1Header, - ) - parser.add_argument( - '-2', '--nifti2', dest='header_class', action='http://github.com/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 args.files: - with nib.openers.ImageOpener(fname) as fobj: - 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') - else: - print(f'Header for "{fname}" is clean') diff --git a/nibabel/cmdline/parrec2nii.py b/nibabel/cmdline/parrec2nii.py deleted file mode 100644 index 0ae6b3fb40..0000000000 --- a/nibabel/cmdline/parrec2nii.py +++ /dev/null @@ -1,433 +0,0 @@ -"""Code for PAR/REC to NIfTI converter command""" - -import csv -import os -import sys -from optparse import Option, OptionParser - -import numpy as np -import numpy.linalg as npl - -import nibabel -import nibabel.nifti1 as nifti1 -import nibabel.parrec as pr -from nibabel.affines import apply_affine, from_matvec, to_matvec -from nibabel.filename_parser import splitext_addext -from nibabel.mriutils import MRIError, calculate_dwell_time -from nibabel.orientations import apply_orientation, inv_ornt_aff, io_orientation -from nibabel.parrec import one_line -from nibabel.volumeutils import fname_ext_ul_case - - -def get_opt_parser(): - # use module docstring for help output - p = OptionParser( - usage=f'{sys.argv[0]} [OPTIONS] \n\n' + __doc__, - version='%prog ' + nibabel.__version__, - ) - p.add_option( - Option( - '-v', - '--verbose', - action='http://github.com/store_true', - dest='verbose', - default=False, - help="""Make some noise.""", - ) - ) - p.add_option( - Option( - '-o', - '--output-dir', - action='http://github.com/store', - type='string', - dest='outdir', - default=None, - help='Destination directory for NIfTI files. Default: current directory.', - ) - ) - p.add_option( - Option( - '-c', - '--compressed', - action='http://github.com/store_true', - dest='compressed', - default=False, - help='Whether to write compressed NIfTI files or not.', - ) - ) - p.add_option( - Option( - '-p', - '--permit-truncated', - action='http://github.com/store_true', - dest='permit_truncated', - default=False, - help=one_line( - """Permit conversion of truncated recordings. Support for - this is experimental, and results *must* be checked - afterward for validity.""" - ), - ) - ) - p.add_option( - Option( - '-b', - '--bvs', - action='http://github.com/store_true', - dest='bvs', - default=False, - help='Output bvals/bvecs files in addition to NIFTI image.', - ) - ) - p.add_option( - Option( - '-d', - '--dwell-time', - action='http://github.com/store_true', - default=False, - dest='dwell_time', - help=one_line( - """Calculate the scan dwell time. If supplied, the magnetic - field strength should also be supplied using - --field-strength (default 3). The field strength must be - supplied because it is not encoded in the PAR/REC - format.""" - ), - ) - ) - p.add_option( - Option( - '--field-strength', - action='http://github.com/store', - type='float', - dest='field_strength', - help=one_line( - """The magnetic field strength of the recording, only needed - for --dwell-time. The field strength must be supplied - because it is not encoded in the PAR/REC format.""" - ), - ) - ) - p.add_option( - Option( - '-i', - '--volume-info', - action='http://github.com/store_true', - dest='vol_info', - default=False, - help=one_line( - """Export .PAR volume labels corresponding to the fourth - dimension of the data. The dimension info will be stored in - CSV format with the first row containing dimension labels - and the subsequent rows (one per volume), the corresponding - indices. Only labels that vary along the 4th dimension are - exported (e.g. for a single volume structural scan there - are no dynamic labels and no output file will be created). - """ - ), - ) - ) - p.add_option( - Option( - '--origin', - action='http://github.com/store', - dest='origin', - default='scanner', - help=one_line( - """Reference point of the q-form transformation of the NIfTI - image. If 'scanner' the (0,0,0) coordinates will refer to - the scanner's iso center. If 'fov', this coordinate will be - the center of the recorded volume (field of view). Default: - 'scanner'.""" - ), - ) - ) - p.add_option( - Option( - '--minmax', - action='http://github.com/store', - nargs=2, - dest='minmax', - help=one_line( - """Minimum and maximum settings to be stored in the NIfTI - header. If any of them is set to 'parse', the scaled data is - scanned for the actual minimum and maximum. To bypass this - potentially slow and memory intensive step (the data has to - be scaled and fully loaded into memory), fixed values can be - provided as space-separated pair, e.g. '5.4 120.4'. It is - possible to set a fixed minimum as scan for the actual - maximum (and vice versa). Default: 'parse parse'.""" - ), - ) - ) - p.set_defaults(minmax=('parse', 'parse')) - p.add_option( - Option( - '--store-header', - action='http://github.com/store_true', - dest='store_header', - default=False, - help=one_line( - """If set, all information from the PAR header is stored in - an extension of the NIfTI file header. Default: off""" - ), - ) - ) - p.add_option( - Option( - '--scaling', - action='http://github.com/store', - dest='scaling', - default='dv', - help=one_line( - """Choose data scaling setting. The PAR header defines two - different data scaling settings: 'dv' (values displayed on - console) and 'fp' (floating point values). Either one can be - chosen, or scaling can be disabled completely ('off'). Note - that neither method will actually scale the data, but just - store the corresponding settings in the NIfTI header, unless - non-uniform scaling is used, in which case the data is - stored in the file in scaled form. Default: 'dv'""" - ), - ) - ) - p.add_option( - Option( - '--keep-trace', - action='http://github.com/store_true', - dest='keep_trace', - default=False, - help=one_line( - """Do not discard the diagnostic Philips DTI - trace volume, if it exists in the data.""" - ), - ) - ) - p.add_option( - Option( - '--overwrite', - action='http://github.com/store_true', - dest='overwrite', - default=False, - help='Overwrite file if it exists. Default: False', - ) - ) - p.add_option( - Option( - '--strict-sort', - action='http://github.com/store_true', - dest='strict_sort', - default=False, - help=one_line( - """Use additional keys in determining the order - to sort the slices within the .REC file. This may be necessary - for more complicated scans with multiple echos, - cardiac phases, ASL label states, etc.""" - ), - ) - ) - return p - - -def verbose(msg, indent=0): - if verbose.switch: - print(' ' * indent + msg) - - -def error(msg, exit_code): - sys.stderr.write(msg + '\n') - sys.exit(exit_code) - - -def proc_file(infile, opts): - # figure out the output filename, and see if it exists - basefilename = splitext_addext(os.path.basename(infile))[0] - if opts.outdir is not None: - # set output path - basefilename = os.path.join(opts.outdir, basefilename) - - # prep a file - if opts.compressed: - verbose('Using gzip compression') - outfilename = basefilename + '.nii.gz' - else: - outfilename = basefilename + '.nii' - if os.path.isfile(outfilename) and not opts.overwrite: - raise OSError(f'Output file "{outfilename}" exists, use --overwrite to overwrite it') - - # load the PAR header and data - scaling = 'dv' if opts.scaling == 'off' else opts.scaling - infile = fname_ext_ul_case(infile) - pr_img = pr.load( - infile, - permit_truncated=opts.permit_truncated, - scaling=scaling, - strict_sort=opts.strict_sort, - ) - pr_hdr = pr_img.header - affine = pr_hdr.get_affine(origin=opts.origin) - slope, intercept = pr_hdr.get_data_scaling(scaling) - if opts.scaling != 'off': - verbose(f'Using data scaling "{opts.scaling}"') - # get original scaling, and decide if we scale in-place or not - if opts.scaling == 'off': - slope = np.array([1.0]) - intercept = np.array([0.0]) - in_data = pr_img.dataobj.get_unscaled() - out_dtype = pr_hdr.get_data_dtype() - elif not np.any(np.diff(slope)) and not np.any(np.diff(intercept)): - # Single scalefactor case - slope = slope.ravel()[0] - intercept = intercept.ravel()[0] - in_data = pr_img.dataobj.get_unscaled() - out_dtype = pr_hdr.get_data_dtype() - else: - # Multi scalefactor case - slope = np.array([1.0]) - intercept = np.array([0.0]) - in_data = np.array(pr_img.dataobj) - out_dtype = np.float64 - # Reorient data block to LAS+ if necessary - ornt = io_orientation(np.diag([-1, 1, 1, 1]).dot(affine)) - if np.array_equal( - ornt, - [ - [0, 1], - [1, 1], - [2, 1], - ], - ): # already in LAS+ - t_aff = np.eye(4) - else: # Not in LAS+ - t_aff = inv_ornt_aff(ornt, pr_img.shape) - affine = np.dot(affine, t_aff) - in_data = apply_orientation(in_data, ornt) - - bvals, bvecs = pr_hdr.get_bvals_bvecs() - if not opts.keep_trace: # discard Philips DTI trace if present - if bvecs is not None: - bad_mask = np.logical_and(bvals != 0, (bvecs == 0).all(axis=1)) - if bad_mask.sum() > 0: - pl = 's' if bad_mask.sum() != 1 else '' - verbose(f'Removing {bad_mask.sum()} DTI trace volume{pl}') - good_mask = ~bad_mask - in_data = in_data[..., good_mask] - bvals = bvals[good_mask] - bvecs = bvecs[good_mask] - - # Make corresponding NIfTI image - nimg = nifti1.Nifti1Image(in_data, affine, pr_hdr) - nhdr = nimg.header - nhdr.set_data_dtype(out_dtype) - nhdr.set_slope_inter(slope, intercept) - nhdr.set_sform(affine, code=1) - nhdr.set_qform(affine, code=1) - - if 'parse' in opts.minmax: - # need to get the scaled data - verbose('Loading (and scaling) the data to determine value range') - if opts.minmax[0] == 'parse': - nhdr['cal_min'] = in_data.min() * slope + intercept - else: - nhdr['cal_min'] = float(opts.minmax[0]) - if opts.minmax[1] == 'parse': - nhdr['cal_max'] = in_data.max() * slope + intercept - else: - nhdr['cal_max'] = float(opts.minmax[1]) - - # container for potential NIfTI1 header extensions - if opts.store_header: - # dump the full PAR header content into an extension - with open(infile, 'rb') as fobj: # contents must be bytes - hdr_dump = fobj.read() - dump_ext = nifti1.Nifti1Extension('comment', hdr_dump) - nhdr.extensions.append(dump_ext) - - verbose(f'Writing {outfilename}') - nibabel.save(nimg, outfilename) - - # write out bvals/bvecs if requested - if opts.bvs: - if bvals is None and bvecs is None: - verbose('No DTI volumes detected, bvals and bvecs not written') - elif bvecs is None: - verbose( - 'DTI volumes detected, but no diffusion direction info was' - 'found. Writing .bvals file only.' - ) - with open(basefilename + '.bvals', 'w') as fid: - # np.savetxt could do this, but it's just a loop anyway - for val in bvals: - fid.write(f'{val} ') - fid.write('\n') - else: - verbose('Writing .bvals and .bvecs files') - # Transform bvecs with reorientation affine - orig2new = npl.inv(t_aff) - bv_reorient = from_matvec(to_matvec(orig2new)[0], [0, 0, 0]) - bvecs = apply_affine(bv_reorient, bvecs) - with open(basefilename + '.bvals', 'w') as fid: - # np.savetxt could do this, but it's just a loop anyway - for val in bvals: - fid.write(f'{val} ') - fid.write('\n') - with open(basefilename + '.bvecs', 'w') as fid: - for row in bvecs.T: - for val in row: - fid.write(f'{val} ') - fid.write('\n') - - # export data labels varying along the 4th dimensions if requested - if opts.vol_info: - labels = pr_img.header.get_volume_labels() - if len(labels) > 0: - vol_keys = list(labels.keys()) - with open(basefilename + '.ordering.csv', 'w', newline='') as csvfile: - csvwriter = csv.writer(csvfile, delimiter=',') - csvwriter.writerow(vol_keys) - for vals in zip(*[labels[k] for k in vol_keys]): - csvwriter.writerow(vals) - - # write out dwell time if requested - if opts.dwell_time: - try: - dwell_time = calculate_dwell_time( - pr_hdr.get_water_fat_shift(), pr_hdr.get_echo_train_length(), opts.field_strength - ) - except MRIError: - verbose('No EPI factors, dwell time not written') - else: - verbose( - f'Writing dwell time ({dwell_time!r} sec) ' - f'calculated assuming {opts.field_strength}T magnet' - ) - with open(basefilename + '.dwell_time', 'w') as fid: - fid.write(f'{dwell_time!r}\n') - # done - - -def main(): - parser = get_opt_parser() - (opts, infiles) = parser.parse_args() - - verbose.switch = opts.verbose - - if opts.origin not in ('scanner', 'fov'): - error(f"Unrecognized value for --origin: '{opts.origin}'.", 1) - if opts.dwell_time and opts.field_strength is None: - error('Need --field-strength for dwell time calculation', 1) - - # store any exceptions - errs = [] - for infile in infiles: - verbose(f'Processing {infile}') - try: - proc_file(infile, opts) - except Exception as e: - errs.append(f'{infile}: {e}') - - if len(errs): - error(f'Caught {len(errs)} exceptions. Dump follows:\n\n' + '\n'.join(errs), 1) - else: - verbose('Done') diff --git a/nibabel/cmdline/roi.py b/nibabel/cmdline/roi.py deleted file mode 100644 index ea47970043..0000000000 --- a/nibabel/cmdline/roi.py +++ /dev/null @@ -1,92 +0,0 @@ -import argparse -import os -import sys - -import nibabel as nb - - -def lossless_slice(img, slicers): - if not nb.imageclasses.spatial_axes_first(img): - raise ValueError('Cannot slice an image that is not known to have spatial axes first') - - scaling = hasattr(img.header, 'set_slope_inter') - - data = img.dataobj._get_unscaled(slicers) if scaling else img.dataobj[slicers] - roi_img = img.__class__(data, affine=img.slicer.slice_affine(slicers), header=img.header) - - if scaling: - roi_img.header.set_slope_inter(img.dataobj.slope, img.dataobj.inter) - return roi_img - - -def parse_slice(crop, allow_step=True): - if crop is None: - return slice(None) - start, stop, *extra = (int(val) if val else None for val in crop.split(':')) - if len(extra) > 1: - raise ValueError(f'Cannot parse specification: {crop}') - if not allow_step and extra and extra[0] not in (1, None): - raise ValueError(f'Step entry not permitted: {crop}') - - step = extra[0] if extra else None - if step not in (1, -1, None): - raise ValueError(f'Downsampling is not supported: {crop}') - - return slice(start, stop, step) - - -def sanitize(args): - # Argparse likes to treat "-1:..." as a flag - return [f' {arg}' if arg[0] == '-' and ':' in arg else arg for arg in args] - - -def main(args=None): - if args is None: - args = sys.argv[1:] - parser = argparse.ArgumentParser( - description='Crop images to a region of interest', - epilog='If a start or stop value is omitted, the start or end of the axis is assumed.', - ) - parser.add_argument('--version', action='http://github.com/version', version=nb.__version__) - parser.add_argument( - '-i', metavar='I1:I2[:-1]', help='Start/stop [flip] along first axis (0-indexed)' - ) - parser.add_argument( - '-j', metavar='J1:J2[:-1]', help='Start/stop [flip] along second axis (0-indexed)' - ) - parser.add_argument( - '-k', metavar='K1:K2[:-1]', help='Start/stop [flip] along third axis (0-indexed)' - ) - parser.add_argument('-t', metavar='T1:T2', help='Start/stop along fourth axis (0-indexed)') - parser.add_argument('in_file', help='Image file to crop') - parser.add_argument('out_file', help='Output file name') - - opts = parser.parse_args(args=sanitize(args)) - - try: - islice = parse_slice(opts.i) - jslice = parse_slice(opts.j) - kslice = parse_slice(opts.k) - tslice = parse_slice(opts.t, allow_step=False) - except ValueError as err: - print(f'Could not parse input arguments. Reason follows.\n{err}') - return 1 - - kwargs = {} - if os.path.realpath(opts.in_file) == os.path.realpath(opts.out_file): - kwargs['mmap'] = False - img = nb.load(opts.in_file, **kwargs) - - slicers = (islice, jslice, kslice, tslice)[: img.ndim] - expected_shape = nb.fileslice.predict_shape(slicers, img.shape) - if any(dim == 0 for dim in expected_shape): - print(f'Cannot take zero-length slices. Predicted shape {expected_shape}.') - return 1 - - try: - sliced_img = lossless_slice(img, slicers) - except Exception: - print('Could not slice image. Full traceback follows.') - raise - nb.save(sliced_img, opts.out_file) - return 0 diff --git a/nibabel/cmdline/stats.py b/nibabel/cmdline/stats.py deleted file mode 100644 index 0a6fc14aeb..0000000000 --- a/nibabel/cmdline/stats.py +++ /dev/null @@ -1,55 +0,0 @@ -#!python -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -""" -Compute image statistics -""" - -import argparse - -from nibabel.imagestats import count_nonzero_voxels, mask_volume -from nibabel.loadsave import load - - -def _get_parser(): - """Return command-line argument parser.""" - p = argparse.ArgumentParser(description=__doc__) - p.add_argument('infile', help='Neuroimaging volume to compute statistics on.') - p.add_argument( - '-V', - '--Volume', - action='http://github.com/store_true', - required=False, - help='Compute mask volume of a given mask image.', - ) - p.add_argument( - '--units', - default='mm3', - required=False, - choices=('mm3', 'vox'), - help='Preferred output units', - ) - return p - - -def main(args=None): - """Main program function.""" - parser = _get_parser() - opts = parser.parse_args(args) - from_img = load(opts.infile) - - if opts.Volume: - if opts.units == 'mm3': - computed_volume = mask_volume(from_img) - elif opts.units == 'vox': - computed_volume = count_nonzero_voxels(from_img) - else: - raise ValueError(f'{opts.units} is not a valid unit. Choose "mm3" or "vox".') - print(computed_volume) - return 0 diff --git a/nibabel/cmdline/tck2trk.py b/nibabel/cmdline/tck2trk.py deleted file mode 100644 index a73540c446..0000000000 --- a/nibabel/cmdline/tck2trk.py +++ /dev/null @@ -1,56 +0,0 @@ -""" -Convert tractograms (TCK -> TRK). -""" - -import argparse -import os - -import nibabel as nib -from nibabel.orientations import aff2axcodes -from nibabel.streamlines import Field - - -def parse_args(): - DESCRIPTION = 'Convert tractograms (TCK -> TRK).' - parser = argparse.ArgumentParser(description=DESCRIPTION) - parser.add_argument('anatomy', help='reference anatomical image (.nii|.nii.gz.') - parser.add_argument( - 'tractograms', metavar='tractogram', nargs='+', help='list of tractograms (.tck).' - ) - parser.add_argument( - '-f', '--force', action='http://github.com/store_true', help='overwrite existing output files.' - ) - - args = parser.parse_args() - return args, parser - - -def main(): - args, parser = parse_args() - - try: - nii = nib.load(args.anatomy) - except Exception: - parser.error('Expecting anatomical image as first argument.') - - for tractogram in args.tractograms: - tractogram_format = nib.streamlines.detect_format(tractogram) - if tractogram_format is not nib.streamlines.TckFile: - print(f"Skipping non TCK file: '{tractogram}'") - continue - - filename, _ = os.path.splitext(tractogram) - output_filename = filename + '.trk' - if os.path.isfile(output_filename) and not args.force: - print(f"Skipping existing file: '{output_filename}'. Use -f to overwrite.") - continue - - # Build header using infos from the anatomical image. - header = {} - header[Field.VOXEL_TO_RASMM] = nii.affine.copy() - header[Field.VOXEL_SIZES] = nii.header.get_zooms()[:3] - header[Field.DIMENSIONS] = nii.shape[:3] - header[Field.VOXEL_ORDER] = ''.join(aff2axcodes(nii.affine)) - - tck = nib.streamlines.load(tractogram) - nib.streamlines.save(tck.tractogram, output_filename, header=header) diff --git a/nibabel/cmdline/tests/__init__.py b/nibabel/cmdline/tests/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/nibabel/cmdline/tests/test_conform.py b/nibabel/cmdline/tests/test_conform.py deleted file mode 100644 index 48014e52e4..0000000000 --- a/nibabel/cmdline/tests/test_conform.py +++ /dev/null @@ -1,58 +0,0 @@ -#!python -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## - -import unittest - -import pytest - -import nibabel as nib -from nibabel.cmdline.conform import main -from nibabel.optpkg import optional_package -from nibabel.testing import get_test_data - -_, have_scipy, _ = optional_package('scipy.ndimage') -needs_scipy = unittest.skipUnless(have_scipy, 'These tests need scipy') - - -@needs_scipy -def test_default(tmpdir): - infile = get_test_data(fname='anatomical.nii') - outfile = tmpdir / 'output.nii.gz' - main([str(infile), str(outfile)]) - assert outfile.isfile() - c = nib.load(outfile) - assert c.shape == (256, 256, 256) - assert c.header.get_zooms() == (1, 1, 1) - assert nib.orientations.aff2axcodes(c.affine) == ('R', 'A', 'S') - - with pytest.raises(FileExistsError): - main([str(infile), str(outfile)]) - - main([str(infile), str(outfile), '--force']) - assert outfile.isfile() - - -@needs_scipy -def test_nondefault(tmpdir): - infile = get_test_data(fname='anatomical.nii') - outfile = tmpdir / 'output.nii.gz' - out_shape = (100, 100, 150) - voxel_size = (1, 2, 4) - orientation = 'LAS' - args = ( - f'{infile} {outfile} --out-shape {" ".join(map(str, out_shape))} ' - f'--voxel-size {" ".join(map(str, voxel_size))} --orientation {orientation}' - ) - main(args.split()) - assert outfile.isfile() - c = nib.load(outfile) - assert c.shape == out_shape - assert c.header.get_zooms() == voxel_size - assert nib.orientations.aff2axcodes(c.affine) == tuple(orientation) diff --git a/nibabel/cmdline/tests/test_convert.py b/nibabel/cmdline/tests/test_convert.py deleted file mode 100644 index d500a717a3..0000000000 --- a/nibabel/cmdline/tests/test_convert.py +++ /dev/null @@ -1,170 +0,0 @@ -#!python -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## - -import numpy as np -import pytest - -import nibabel as nib -from nibabel.cmdline import convert -from nibabel.testing import get_test_data - - -def test_convert_noop(tmp_path): - infile = get_test_data(fname='anatomical.nii') - outfile = tmp_path / 'output.nii.gz' - - orig = nib.load(infile) - assert not outfile.exists() - - convert.main([str(infile), str(outfile)]) - assert outfile.is_file() - - converted = nib.load(outfile) - assert np.allclose(converted.affine, orig.affine) - assert converted.shape == orig.shape - assert converted.get_data_dtype() == orig.get_data_dtype() - - infile = get_test_data(fname='resampled_anat_moved.nii') - - with pytest.raises(FileExistsError): - convert.main([str(infile), str(outfile)]) - - convert.main([str(infile), str(outfile), '--force']) - assert outfile.is_file() - - # Verify that we did overwrite - converted2 = nib.load(outfile) - assert not ( - converted2.shape == converted.shape - and np.allclose(converted2.affine, converted.affine) - and np.allclose(converted2.get_fdata(), converted.get_fdata()) - ) - - -@pytest.mark.parametrize('data_dtype', ('u1', 'i2', 'float32', 'float', 'int64')) -def test_convert_dtype(tmp_path, data_dtype): - infile = get_test_data(fname='anatomical.nii') - outfile = tmp_path / 'output.nii.gz' - - orig = nib.load(infile) - assert not outfile.exists() - - # np.dtype() will give us the dtype for the system endianness if that - # mismatches the data file, we will fail equality, so get the dtype that - # matches the requested precision but in the endianness of the file - expected_dtype = np.dtype(data_dtype).newbyteorder(orig.header.endianness) - - convert.main([str(infile), str(outfile), '--out-dtype', data_dtype]) - assert outfile.is_file() - - converted = nib.load(outfile) - assert np.allclose(converted.affine, orig.affine) - assert converted.shape == orig.shape - assert converted.get_data_dtype() == expected_dtype - - -@pytest.mark.parametrize( - ('ext', 'img_class'), - [ - ('mgh', nib.MGHImage), - ('img', nib.Nifti1Pair), - ], -) -def test_convert_by_extension(tmp_path, ext, img_class): - infile = get_test_data(fname='anatomical.nii') - outfile = tmp_path / f'output.{ext}' - - orig = nib.load(infile) - assert not outfile.exists() - - convert.main([str(infile), str(outfile)]) - assert outfile.is_file() - - converted = nib.load(outfile) - assert np.allclose(converted.affine, orig.affine) - assert converted.shape == orig.shape - assert converted.__class__ == img_class - - -@pytest.mark.parametrize( - ('ext', 'img_class'), - [ - ('mgh', nib.MGHImage), - ('img', nib.Nifti1Pair), - ('nii', nib.Nifti2Image), - ], -) -def test_convert_imgtype(tmp_path, ext, img_class): - infile = get_test_data(fname='anatomical.nii') - outfile = tmp_path / f'output.{ext}' - - orig = nib.load(infile) - assert not outfile.exists() - - convert.main([str(infile), str(outfile), '--image-type', img_class.__name__]) - assert outfile.is_file() - - converted = nib.load(outfile) - assert np.allclose(converted.affine, orig.affine) - assert converted.shape == orig.shape - assert converted.__class__ == img_class - - -def test_convert_nifti_int_fail(tmp_path): - infile = get_test_data(fname='anatomical.nii') - outfile = tmp_path / 'output.nii' - - orig = nib.load(infile) - assert not outfile.exists() - - with pytest.raises(ValueError): - convert.main([str(infile), str(outfile), '--out-dtype', 'int']) - assert not outfile.exists() - - with pytest.warns(UserWarning): - convert.main([str(infile), str(outfile), '--out-dtype', 'int', '--force']) - assert outfile.is_file() - - converted = nib.load(outfile) - assert np.allclose(converted.affine, orig.affine) - assert converted.shape == orig.shape - # Note: '--force' ignores the error, but can't interpret it enough to do - # the cast anyway - assert converted.get_data_dtype() == orig.get_data_dtype() - - -@pytest.mark.parametrize( - ('orig_dtype', 'alias', 'expected_dtype'), - [ - ('int64', 'mask', 'uint8'), - ('int64', 'compat', 'int32'), - ('int64', 'smallest', 'uint8'), - ('float64', 'mask', 'uint8'), - ('float64', 'compat', 'float32'), - ], -) -def test_convert_aliases(tmp_path, orig_dtype, alias, expected_dtype): - orig_fname = tmp_path / 'orig.nii' - out_fname = tmp_path / 'out.nii' - - arr = np.arange(24).reshape((2, 3, 4)) - img = nib.Nifti1Image(arr, np.eye(4), dtype=orig_dtype) - img.to_filename(orig_fname) - - assert orig_fname.exists() - assert not out_fname.exists() - - convert.main([str(orig_fname), str(out_fname), '--out-dtype', alias]) - assert out_fname.is_file() - - expected_dtype = np.dtype(expected_dtype).newbyteorder(img.header.endianness) - - converted = nib.load(out_fname) - assert converted.get_data_dtype() == expected_dtype diff --git a/nibabel/cmdline/tests/test_parrec2nii.py b/nibabel/cmdline/tests/test_parrec2nii.py deleted file mode 100644 index ccedafb74b..0000000000 --- a/nibabel/cmdline/tests/test_parrec2nii.py +++ /dev/null @@ -1,98 +0,0 @@ -"""Tests for the parrec2nii exe code""" - -from os.path import basename, isfile, join -from unittest.mock import MagicMock, Mock, patch - -import numpy -from numpy import array as npa -from numpy.testing import assert_almost_equal, assert_array_equal - -import nibabel -from nibabel.cmdline import parrec2nii -from nibabel.tests.test_parrec import EG_PAR, VARY_PAR -from nibabel.tmpdirs import InTemporaryDirectory - -AN_OLD_AFFINE = numpy.array( - [ - [-3.64994708, 0.0, 1.83564171, 123.66276611], - [0.0, -3.75, 0.0, 115.617], - [0.86045705, 0.0, 7.78655376, -27.91161211], - [0.0, 0.0, 0.0, 1.0], - ] -) - -PAR_AFFINE = numpy.array( - [ - [-3.64994708, 0.0, 1.83564171, 107.63076611], - [0.0, 3.75, 0.0, -118.125], - [0.86045705, 0.0, 7.78655376, -58.25061211], - [0.0, 0.0, 0.0, 1.0], - ] -) - - -@patch('nibabel.cmdline.parrec2nii.verbose') -@patch('nibabel.cmdline.parrec2nii.io_orientation') -@patch('nibabel.cmdline.parrec2nii.nifti1') -@patch('nibabel.cmdline.parrec2nii.pr') -def test_parrec2nii_sets_qform_sform_code1(*args): - # Check that set_sform(), set_qform() are called on the new header. - parrec2nii.verbose.switch = False - - parrec2nii.io_orientation.return_value = [[0, 1], [1, 1], [2, 1]] # LAS+ - - nimg = Mock() - nhdr = MagicMock() - nimg.header = nhdr - parrec2nii.nifti1.Nifti1Image.return_value = nimg - - pr_img = Mock() - pr_hdr = Mock() - pr_hdr.get_data_scaling.return_value = (npa([]), npa([])) - pr_hdr.get_bvals_bvecs.return_value = (None, None) - pr_hdr.get_affine.return_value = AN_OLD_AFFINE - pr_img.header = pr_hdr - parrec2nii.pr.load.return_value = pr_img - - opts = Mock() - opts.outdir = None - opts.scaling = 'off' - opts.minmax = [1, 1] - opts.store_header = False - opts.bvs = False - opts.vol_info = False - opts.dwell_time = False - - infile = 'nonexistent.PAR' - parrec2nii.proc_file(infile, opts) - nhdr.set_qform.assert_called_with(AN_OLD_AFFINE, code=1) - nhdr.set_sform.assert_called_with(AN_OLD_AFFINE, code=1) - - -@patch('nibabel.cmdline.parrec2nii.verbose') -def test_parrec2nii_save_load_qform_code(*args): - # Tests that after parrec2nii saves file, it has the sform and qform 'code' - # set to '1', which means 'scanner', so that other software, e.g. FSL picks - # up the qform. - parrec2nii.verbose.switch = False - - opts = Mock() - opts.outdir = None - opts.scaling = 'off' - opts.minmax = [1, 1] - opts.store_header = False - opts.bvs = False - opts.vol_info = False - opts.dwell_time = False - opts.compressed = False - - with InTemporaryDirectory() as pth: - opts.outdir = pth - for fname in [EG_PAR, VARY_PAR]: - parrec2nii.proc_file(fname, opts) - outfname = join(pth, basename(fname)).replace('.PAR', '.nii') - assert isfile(outfname) - img = nibabel.load(outfname) - assert_almost_equal(img.affine, PAR_AFFINE, 4) - assert img.header['qform_code'] == 1 - assert_array_equal(img.header['sform_code'], 1) diff --git a/nibabel/cmdline/tests/test_roi.py b/nibabel/cmdline/tests/test_roi.py deleted file mode 100644 index 4692bbb038..0000000000 --- a/nibabel/cmdline/tests/test_roi.py +++ /dev/null @@ -1,152 +0,0 @@ -import os -from unittest import mock - -import numpy as np -import pytest - -import nibabel as nb -from nibabel.cmdline.roi import lossless_slice, main, parse_slice -from nibabel.testing import data_path - - -def test_parse_slice(): - assert parse_slice(None) == slice(None) - assert parse_slice('1:5') == slice(1, 5) - assert parse_slice('1:') == slice(1, None) - assert parse_slice(':5') == slice(None, 5) - assert parse_slice(':-1') == slice(None, -1) - assert parse_slice('-5:-1') == slice(-5, -1) - assert parse_slice('1:5:') == slice(1, 5, None) - assert parse_slice('1::') == slice(1, None, None) - assert parse_slice(':5:') == slice(None, 5, None) - assert parse_slice(':-1:') == slice(None, -1, None) - assert parse_slice('-5:-1:') == slice(-5, -1, None) - assert parse_slice('1:5:1') == slice(1, 5, 1) - assert parse_slice('1::1') == slice(1, None, 1) - assert parse_slice(':5:1') == slice(None, 5, 1) - assert parse_slice(':-1:1') == slice(None, -1, 1) - assert parse_slice('-5:-1:1') == slice(-5, -1, 1) - assert parse_slice('5:1:-1') == slice(5, 1, -1) - assert parse_slice(':1:-1') == slice(None, 1, -1) - assert parse_slice('5::-1') == slice(5, None, -1) - assert parse_slice('-1::-1') == slice(-1, None, -1) - assert parse_slice('-1:-5:-1') == slice(-1, -5, -1) - - # Max of start:stop:step - with pytest.raises(ValueError): - parse_slice('1:2:3:4') - # Integers only - with pytest.raises(ValueError): - parse_slice('abc:2:3') - with pytest.raises(ValueError): - parse_slice('1.2:2:3') - # Unit steps only - with pytest.raises(ValueError): - parse_slice('1:5:2') - - -def test_parse_slice_disallow_step(): - # Permit steps of 1 - assert parse_slice('1:5', False) == slice(1, 5) - assert parse_slice('1:5:', False) == slice(1, 5) - assert parse_slice('1:5:1', False) == slice(1, 5, 1) - # Disable other steps - with pytest.raises(ValueError): - parse_slice('1:5:-1', False) - with pytest.raises(ValueError): - parse_slice('1:5:-2', False) - - -def test_lossless_slice_unknown_axes(): - img = nb.load(os.path.join(data_path, 'minc1_4d.mnc')) - with pytest.raises(ValueError): - lossless_slice(img, (slice(None), slice(None), slice(None))) - - -def test_lossless_slice_scaling(tmp_path): - fname = tmp_path / 'image.nii' - img = nb.Nifti1Image(np.random.uniform(-20000, 20000, (5, 5, 5, 5)), affine=np.eye(4)) - img.header.set_data_dtype('int16') - img.to_filename(fname) - img1 = nb.load(fname) - sliced_fname = tmp_path / 'sliced.nii' - lossless_slice(img1, (slice(None), slice(None), slice(2, 4))).to_filename(sliced_fname) - img2 = nb.load(sliced_fname) - - assert np.array_equal(img1.get_fdata()[:, :, 2:4], img2.get_fdata()) - assert np.array_equal(img1.dataobj.get_unscaled()[:, :, 2:4], img2.dataobj.get_unscaled()) - assert img1.dataobj.slope == img2.dataobj.slope - assert img1.dataobj.inter == img2.dataobj.inter - - -def test_lossless_slice_noscaling(tmp_path): - fname = tmp_path / 'image.mgh' - img = nb.MGHImage( - np.random.uniform(-20000, 20000, (5, 5, 5, 5)).astype('float32'), affine=np.eye(4) - ) - img.to_filename(fname) - img1 = nb.load(fname) - sliced_fname = tmp_path / 'sliced.mgh' - lossless_slice(img1, (slice(None), slice(None), slice(2, 4))).to_filename(sliced_fname) - img2 = nb.load(sliced_fname) - - assert np.array_equal(img1.get_fdata()[:, :, 2:4], img2.get_fdata()) - assert np.array_equal(img1.dataobj.get_unscaled()[:, :, 2:4], img2.dataobj.get_unscaled()) - assert img1.dataobj.slope == img2.dataobj.slope - assert img1.dataobj.inter == img2.dataobj.inter - - -@pytest.mark.parametrize('inplace', (True, False)) -def test_nib_roi(tmp_path, inplace): - in_file = os.path.join(data_path, 'functional.nii') - out_file = str(tmp_path / 'sliced.nii') - in_img = nb.load(in_file) - - if inplace: - in_img.to_filename(out_file) - in_file = out_file - - retval = main([in_file, out_file, '-i', '1:-1', '-j', '-1:1:-1', '-k', '::', '-t', ':5']) - assert retval == 0 - - out_img = nb.load(out_file) - in_data = in_img.dataobj[:] - in_sliced = in_img.slicer[1:-1, -1:1:-1, :, :5] - assert out_img.shape == in_sliced.shape - assert np.array_equal(in_data[1:-1, -1:1:-1, :, :5], out_img.dataobj) - assert np.allclose(in_sliced.dataobj, out_img.dataobj) - assert np.allclose(in_sliced.affine, out_img.affine) - - -@pytest.mark.parametrize( - ('args', 'errmsg'), - ( - (('-i', '1:1'), 'Cannot take zero-length slice'), - (('-j', '1::2'), 'Downsampling is not supported'), - (('-t', '5::-1'), 'Step entry not permitted'), - ), -) -def test_nib_roi_bad_slices(capsys, args, errmsg): - in_file = os.path.join(data_path, 'functional.nii') - - retval = main([in_file, os.devnull, *args]) - assert retval != 0 - captured = capsys.readouterr() - assert errmsg in captured.out - - -def test_entrypoint(capsys): - # Check that we handle missing args as expected - with mock.patch('sys.argv', ['nib-roi', '--help']): - with pytest.raises(SystemExit): - main() - captured = capsys.readouterr() - assert captured.out.startswith('usage: nib-roi') - - -def test_nib_roi_unknown_axes(capsys): - in_file = os.path.join(data_path, 'minc1_4d.mnc') - with pytest.raises(ValueError): - main([in_file, os.devnull, '-i', ':']) - captured = capsys.readouterr() - assert 'Could not slice image.' in captured.out diff --git a/nibabel/cmdline/tests/test_stats.py b/nibabel/cmdline/tests/test_stats.py deleted file mode 100644 index 905114e31b..0000000000 --- a/nibabel/cmdline/tests/test_stats.py +++ /dev/null @@ -1,34 +0,0 @@ -#!python -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## - -import numpy as np - -from nibabel import Nifti1Image -from nibabel.cmdline.stats import main -from nibabel.loadsave import save - - -def test_volume(tmpdir, capsys): - mask_data = np.zeros((20, 20, 20), dtype='u1') - mask_data[5:15, 5:15, 5:15] = 1 - img = Nifti1Image(mask_data, np.eye(4)) - - infile = tmpdir / 'input.nii' - save(img, infile) - - args = f'{infile} --Volume' - main(args.split()) - vol_mm3 = capsys.readouterr() - args = f'{infile} --Volume --units vox' - main(args.split()) - vol_vox = capsys.readouterr() - - assert float(vol_mm3[0]) == 1000.0 - assert int(vol_vox[0]) == 1000 diff --git a/nibabel/cmdline/tests/test_utils.py b/nibabel/cmdline/tests/test_utils.py deleted file mode 100644 index 954a3a2573..0000000000 --- a/nibabel/cmdline/tests/test_utils.py +++ /dev/null @@ -1,327 +0,0 @@ -# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -"""Test scripts - -Test running scripts -""" - -from io import StringIO -from os.path import join as pjoin - -import numpy as np -import pytest - -import nibabel as nib -from nibabel.cmdline.diff import ( - display_diff, - get_data_diff, - get_data_hash_diff, - get_headers_diff, - main, -) -from nibabel.cmdline.utils import ( - ap, - safe_get, - table2string, -) -from nibabel.testing import data_path - - -def test_table2string(): - # Trivial case should do something sensible - assert table2string([]) == '\n' - assert ( - table2string( - [['A', 'B', 'C', 'D'], - ['E', 'F', 'G', 'H']] - ) == ( - 'A B C D\n' - 'E F G H\n' - ) - ) # fmt: skip - assert ( - table2string( - [["Let's", 'Make', 'Tests', 'And'], - ['Have', 'Lots', 'Of', 'Fun'], - ['With', 'Python', 'Guys', '!']] - ) == ( - "Let's Make Tests And\n" - 'Have Lots Of Fun\n' - 'With Python Guys !\n' - ) - ) # fmt: skip - assert ( - table2string( - [['This', 'Table', '@lIs', 'Ragged'], - ['And', '@rit', 'uses', '@csome', 'alignment', 'markup']] - ) == ( - 'This Table Is Ragged\n' - 'And it uses some alignment markup\n' - ) - ) # fmt: skip - - -def test_ap(): - assert ap([1, 2], '%2d') == ' 1, 2' - assert ap([1, 2], '%3d') == ' 1, 2' - assert ap([1, 2], '%-2d') == '1 , 2 ' - assert ap([1, 2], '%d', '+') == '1+2' - assert ap([1, 2, 3], '%d', '-') == '1-2-3' - - -def test_safe_get(): - class TestObject: - def __init__(self, test=None): - self.test = test - - def get_test(self): - return self.test - - test = TestObject() - test.test = 2 - - assert safe_get(test, 'test') == 2 - assert safe_get(test, 'failtest') == '-' - - -def test_get_headers_diff(): - fnames = [pjoin(data_path, f) for f in ('standard.nii.gz', 'example4d.nii.gz')] - actual_difference = get_headers_diff([nib.load(f).header for f in fnames]) - expected_difference = { - 'regular': [np.asarray(b''), np.asarray(b'r')], - 'dim_info': [np.asarray(0, 'uint8'), np.asarray(57, 'uint8')], - 'dim': [ - np.array([3, 4, 5, 7, 1, 1, 1, 1], 'int16'), - np.array([4, 128, 96, 24, 2, 1, 1, 1], 'int16'), - ], - 'datatype': [np.array(2, 'uint8'), np.array(4, 'uint8')], - 'bitpix': [np.array(8, 'uint8'), np.array(16, 'uint8')], - 'pixdim': [ - np.array([1.0, 1.0, 3.0, 2.0, 1.0, 1.0, 1.0, 1.0], 'float32'), - np.array( - [ - -1.00000000e00, - 2.00000000e00, - 2.00000000e00, - 2.19999909e00, - 2.00000000e03, - 1.00000000e00, - 1.00000000e00, - 1.00000000e00, - ], - 'float32', - ), - ], - 'slice_end': [np.array(0, 'uint8'), np.array(23, 'uint8')], - 'xyzt_units': [np.array(0, 'uint8'), np.array(10, 'uint8')], - 'cal_max': [ - np.array(0.0, 'float32'), - np.asarray(1162.0, 'float32'), - ], - 'descrip': [ - np.array(b'', 'S80'), - np.array(b'FSL3.3\x00 v2.25 NIfTI-1 Single file format', 'S80'), - ], - 'qform_code': [np.array(0, 'int16'), np.array(1, 'int16')], - 'sform_code': [np.array(2, 'int16'), np.array(1, 'int16')], - 'quatern_b': [ - np.array(0.0, 'float32'), - np.array(-1.9451068140294884e-26, 'float32'), - ], - 'quatern_c': [ - np.array(0.0, 'float32'), - np.array(-0.9967085123062134, 'float32'), - ], - 'quatern_d': [ - np.array(0.0, 'float32'), - np.array(-0.0810687392950058, 'float32'), - ], - 'qoffset_x': [ - np.array(0.0, 'float32'), - np.array(117.8551025390625, 'float32'), - ], - 'qoffset_y': [ - np.array(0.0, 'float32'), - np.array(-35.72294235229492, 'float32'), - ], - 'qoffset_z': [ - np.array(0.0, 'float32'), - np.array(-7.248798370361328, 'float32'), - ], - 'srow_x': [ - np.array([1.0, 0.0, 0.0, 0.0], 'float32'), - np.array([-2.00000000e00, 6.71471565e-19, 9.08102451e-18, 1.17855103e02], 'float32'), - ], - 'srow_y': [ - np.array([0.0, 3.0, 0.0, 0.0], 'float32'), - np.array([-6.71471565e-19, 1.97371149e00, -3.55528235e-01, -3.57229424e01], 'float32'), - ], - 'srow_z': [ - np.array([0.0, 0.0, 2.0, 0.0], 'float32'), - np.array([8.25548089e-18, 3.23207617e-01, 2.17108178e00, -7.24879837e00], 'float32'), - ], - } - - np.testing.assert_equal(actual_difference, expected_difference) - - -def test_display_diff(): - bogus_names = ['hellokitty.nii.gz', 'privettovarish.nii.gz'] - - dict_values = { - 'datatype': [np.array(2, 'uint8'), np.array(4, 'uint8')], - 'bitpix': [np.array(8, 'uint8'), np.array(16, 'uint8')], - } - - expected_output = """\ -These files are different. -Field/File \ -1:hellokitty.nii.gz \ -2:privettovarish.nii.gz \n\ -datatype \ -2 \ -4 \n\ -bitpix \ -8 \ -16 \n""" - - assert display_diff(bogus_names, dict_values) == expected_output - - -def test_get_data_diff(): - # testing for identical files specifically as md5 may vary by computer - test_names = [pjoin(data_path, f) for f in ('standard.nii.gz', 'standard.nii.gz')] - assert get_data_hash_diff(test_names) == [] - - # testing the maximum relative and absolute differences' different use cases - test_array = np.arange(16).reshape(4, 4) - test_array_2 = np.arange(1, 17).reshape(4, 4) - test_array_3 = np.arange(2, 18).reshape(4, 4) - test_array_4 = np.arange(100).reshape(10, 10) - test_array_5 = np.arange(64).reshape(8, 8) - - # same shape, 2 files - assert get_data_diff([test_array, test_array_2]) == { - 'DATA(diff 1:)': [None, {'abs': 1, 'rel': 2.0}] - } - - # same shape, 3 files - assert get_data_diff([test_array, test_array_2, test_array_3]) == { - 'DATA(diff 1:)': [ - None, - {'abs': 1, 'rel': 2.0}, - {'abs': 2, 'rel': 2.0}, - ], - 'DATA(diff 2:)': [None, None, {'abs': 1, 'rel': 0.66666666666666663}], - } - - # same shape, 2 files, modified maximum abs/rel - assert get_data_diff([test_array, test_array_2], max_abs=2, max_rel=2) == {} - - # different shape, 2 files - assert get_data_diff([test_array_2, test_array_4]) == { - 'DATA(diff 1:)': [None, {'CMP': 'incompat'}] - } - - # different shape, 3 files - assert get_data_diff([test_array_4, test_array_5, test_array_2]) == { - 'DATA(diff 1:)': [None, {'CMP': 'incompat'}, {'CMP': 'incompat'}], - 'DATA(diff 2:)': [None, None, {'CMP': 'incompat'}], - } - - test_return = get_data_diff([test_array, test_array_2], dtype=np.float32) - assert type(test_return['DATA(diff 1:)'][1]['abs']) is np.float32 - assert type(test_return['DATA(diff 1:)'][1]['rel']) is np.float32 - - test_return_2 = get_data_diff([test_array, test_array_2, test_array_3]) - assert type(test_return_2['DATA(diff 1:)'][1]['abs']) is np.float64 - assert type(test_return_2['DATA(diff 1:)'][1]['rel']) is np.float64 - assert type(test_return_2['DATA(diff 2:)'][2]['abs']) is np.float64 - assert type(test_return_2['DATA(diff 2:)'][2]['rel']) is np.float64 - - -def test_main(): - test_names = [pjoin(data_path, f) for f in ('standard.nii.gz', 'example4d.nii.gz')] - expected_difference = { - 'regular': [np.asarray(b''), np.asarray(b'r')], - 'dim_info': [np.asarray(0, 'uint8'), np.asarray(57, 'uint8')], - 'dim': [ - np.array([3, 4, 5, 7, 1, 1, 1, 1], 'int16'), - np.array([4, 128, 96, 24, 2, 1, 1, 1], 'int16'), - ], - 'datatype': [np.array(2, 'uint8'), np.array(4, 'uint8')], - 'bitpix': [np.array(8, 'uint8'), np.array(16, 'uint8')], - 'pixdim': [ - np.array([1.0, 1.0, 3.0, 2.0, 1.0, 1.0, 1.0, 1.0], 'float32'), - np.array( - [ - -1.00000000e00, - 2.00000000e00, - 2.00000000e00, - 2.19999909e00, - 2.00000000e03, - 1.00000000e00, - 1.00000000e00, - 1.00000000e00, - ], - 'float32', - ), - ], - 'slice_end': [np.array(0, 'uint8'), np.array(23, 'uint8')], - 'xyzt_units': [np.array(0, 'uint8'), np.array(10, 'uint8')], - 'cal_max': [ - np.array(0.0, 'float32'), - np.asarray(1162.0, 'float32'), - ], - 'descrip': [ - np.array(b'', 'S80'), - np.array(b'FSL3.3\x00 v2.25 NIfTI-1 Single file format', 'S80'), - ], - 'qform_code': [np.array(0, 'int16'), np.array(1, 'int16')], - 'sform_code': [np.array(2, 'int16'), np.array(1, 'int16')], - 'quatern_b': [ - np.array(0.0, 'float32'), - np.array(-1.9451068140294884e-26, 'float32'), - ], - 'quatern_c': [ - np.array(0.0, 'float32'), - np.array(-0.9967085123062134, 'float32'), - ], - 'quatern_d': [ - np.array(0.0, 'float32'), - np.array(-0.0810687392950058, 'float32'), - ], - 'qoffset_x': [ - np.array(0.0, 'float32'), - np.array(117.8551025390625, 'float32'), - ], - 'qoffset_y': [ - np.array(0.0, 'float32'), - np.array(-35.72294235229492, 'float32'), - ], - 'qoffset_z': [ - np.array(0.0, 'float32'), - np.array(-7.248798370361328, 'float32'), - ], - 'srow_x': [ - np.array([1.0, 0.0, 0.0, 0.0], 'float32'), - np.array([-2.00000000e00, 6.71471565e-19, 9.08102451e-18, 1.17855103e02], 'float32'), - ], - 'srow_y': [ - np.array([0.0, 3.0, 0.0, 0.0], 'float32'), - np.array([-6.71471565e-19, 1.97371149e00, -3.55528235e-01, -3.57229424e01], 'float32'), - ], - 'srow_z': [ - np.array([0.0, 0.0, 2.0, 0.0], 'float32'), - np.array([8.25548089e-18, 3.23207617e-01, 2.17108178e00, -7.24879837e00], 'float32'), - ], - 'DATA(md5)': ['0a2576dd6badbb25bfb3b12076df986b', 'b0abbc492b4fd533b2c80d82570062cf'], - } - - with pytest.raises(SystemExit): - np.testing.assert_equal(main(test_names, StringIO()), expected_difference) - - test_names_2 = [pjoin(data_path, f) for f in ('standard.nii.gz', 'standard.nii.gz')] - - with pytest.raises(SystemExit): - assert main(test_names_2, StringIO()) == 'These files are identical.' diff --git a/nibabel/cmdline/trk2tck.py b/nibabel/cmdline/trk2tck.py deleted file mode 100644 index 6bfc2c8c3a..0000000000 --- a/nibabel/cmdline/trk2tck.py +++ /dev/null @@ -1,40 +0,0 @@ -""" -Convert tractograms (TRK -> TCK). -""" - -import argparse -import os - -import nibabel as nib - - -def parse_args(): - DESCRIPTION = 'Convert tractograms (TRK -> TCK).' - parser = argparse.ArgumentParser(description=DESCRIPTION) - parser.add_argument( - 'tractograms', metavar='tractogram', nargs='+', help='list of tractograms (.trk).' - ) - parser.add_argument( - '-f', '--force', action='http://github.com/store_true', help='overwrite existing output files.' - ) - - args = parser.parse_args() - return args, parser - - -def main(): - args, parser = parse_args() - for tractogram in args.tractograms: - tractogram_format = nib.streamlines.detect_format(tractogram) - if tractogram_format is not nib.streamlines.TrkFile: - print(f"Skipping non TRK file: '{tractogram}'") - continue - - filename, _ = os.path.splitext(tractogram) - output_filename = filename + '.tck' - if os.path.isfile(output_filename) and not args.force: - print(f"Skipping existing file: '{output_filename}'. Use -f to overwrite.") - continue - - trk = nib.streamlines.load(tractogram) - nib.streamlines.save(trk.tractogram, output_filename) diff --git a/nibabel/cmdline/utils.py b/nibabel/cmdline/utils.py deleted file mode 100644 index 824ed677a1..0000000000 --- a/nibabel/cmdline/utils.py +++ /dev/null @@ -1,97 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -""" -Helper utilities to be used in cmdline applications -""" - -# global verbosity switch -import re - -verbose_level = 0 - - -def _err(msg=None): - """To return a string to signal "error" in output table""" - if msg is None: - msg = 'error' - return '!' + msg - - -def verbose(thing, msg): - """Print `s` if `thing` is less than the `verbose_level`""" - # TODO: consider using nibabel's logger - if thing <= verbose_level: - print(' ' * thing + msg) - - -def table2string(table, out=None): - """Given list of lists figure out their common widths and print to out - - Parameters - ---------- - table : list of lists of strings - What is aimed to be printed - out : None or stream - Where to print. If None, return string - - Returns - ------- - string if out was None - """ - - # equalize number of elements in each row - nelements_max = len(table) and max(len(x) for x in table) - - table = [row + [''] * (nelements_max - len(row)) for row in table] - for i, table_ in enumerate(table): - table[i] += [''] * (nelements_max - len(table_)) - - # eat whole entry while computing width for @w (for wide) - markup_strip = re.compile(r'^@([lrc]|w.*)') - col_width = [max(len(markup_strip.sub('', x)) for x in column) for column in zip(*table)] - trans = str.maketrans('lrcw', '<>^^') - lines = [] - for row in table: - line = [] - for item, width in zip(row, col_width): - item = str(item) - if item.startswith('@'): - align = item[1] - item = item[2:] - if align not in ('l', 'r', 'c', 'w'): - raise ValueError(f'Unknown alignment {align}. Known are l,r,c') - else: - align = 'c' - - line.append(f'{item:{align.translate(trans)}{width}}') - lines.append(' '.join(line).rstrip()) - - ret = '\n'.join(lines) + '\n' - if out is not None: - out.write(ret) - else: - return ret - - -def ap(helplist, format_, sep=', '): - """Little helper to enforce consistency""" - if helplist == '-': - return helplist - ls = [format_ % x for x in helplist] - return sep.join(ls) - - -def safe_get(obj, name): - """A getattr which would return '-' if getattr fails""" - try: - f = getattr(obj, 'get_' + name) - return f() - except Exception as e: - verbose(2, f'get_{name}() failed -- {e}') - return '-' diff --git a/nibabel/conftest.py b/nibabel/conftest.py deleted file mode 100644 index 1d7389e867..0000000000 --- a/nibabel/conftest.py +++ /dev/null @@ -1,28 +0,0 @@ -import sys - -import numpy as np -import pytest - -# Ignore warning requesting help with nicom -with pytest.warns(UserWarning): - import nibabel.nicom # noqa: F401 - - -@pytest.fixture(scope='session', autouse=True) -def legacy_printoptions(): - np.set_printoptions(legacy='1.21') - - -@pytest.fixture -def max_digits(): - # Set maximum number of digits for int/str conversion for - # duration of a test - try: - orig_max_str_digits = sys.get_int_max_str_digits() - yield sys.set_int_max_str_digits - sys.set_int_max_str_digits(orig_max_str_digits) - except AttributeError: # PY310 # pragma: no cover - # Nothing to do for versions of Python that lack these methods - # They were added as DoS protection in Python 3.11 and backported to - # some other versions. - yield lambda x: None diff --git a/nibabel/data.py b/nibabel/data.py deleted file mode 100644 index 510b4127bc..0000000000 --- a/nibabel/data.py +++ /dev/null @@ -1,355 +0,0 @@ -# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -"""Utilities to find files from NIPY data packages""" - -import configparser -import glob -import os -import sys -from os.path import join as pjoin - -from packaging.version import Version - -from .environment import get_nipy_system_dir, get_nipy_user_dir - -DEFAULT_INSTALL_HINT = 'If you have the package, have you set the path to the package correctly?' - - -class DataError(Exception): - pass - - -class BomberError(DataError, AttributeError): - """Error when trying to access Bomber instance - - Should be instance of AttributeError to allow Python 3 inspect to do - various ``hasattr`` checks without raising an error - """ - - pass - - -class Datasource: - """Simple class to add base path to relative path""" - - def __init__(self, base_path): - """Initialize datasource - - Parameters - ---------- - base_path : str - path to prepend to all relative paths - - Examples - -------- - >>> from os.path import join as pjoin - >>> repo = Datasource(pjoin('a', 'path')) - >>> fname = repo.get_filename('somedir', 'afile.txt') - >>> fname == pjoin('a', 'path', 'somedir', 'afile.txt') - True - """ - self.base_path = base_path - - def get_filename(self, *path_parts): - """Prepend base path to `*path_parts` - - We make no check whether the returned path exists. - - Parameters - ---------- - *path_parts : sequence of strings - - Returns - ------- - fname : str - result of ``os.path.join(*path_parts), with - ``self.base_path`` prepended - - """ - return pjoin(self.base_path, *path_parts) - - def list_files(self, relative=True): - """Recursively list the files in the data source directory. - - Parameters - ---------- - relative: bool, optional - If True, path returned are relative to the base path of - the data source. - - Returns - ------- - file_list: list of strings - List of the paths of all the files in the data source. - - """ - out_list = list() - for base, dirs, files in os.walk(self.base_path): - if relative: - base = base[len(self.base_path) + 1 :] - out_list.extend(pjoin(base, filename) for filename in files) - return out_list - - -class VersionedDatasource(Datasource): - """Datasource with version information in config file""" - - def __init__(self, base_path, config_filename=None): - """Initialize versioned datasource - - We assume that there is a configuration file with version - information in datasource directory tree. - - The configuration file contains an entry like:: - - [DEFAULT] - version = 0.3 - - The version should have at least a major and a minor version - number in the form above. - - Parameters - ---------- - base_path : str - path to prepend to all relative paths - config_filaname : None or str - relative path to configuration file containing version - - """ - Datasource.__init__(self, base_path) - if config_filename is None: - config_filename = 'config.ini' - self.config = configparser.ConfigParser() - cfg_file = self.get_filename(config_filename) - readfiles = self.config.read(cfg_file) - if not readfiles: - raise DataError(f'Could not read config file {cfg_file}') - try: - self.version = self.config.get('DEFAULT', 'version') - except configparser.Error: - raise DataError(f'Could not get version from {cfg_file}') - version_parts = self.version.split('.') - self.major_version = int(version_parts[0]) - self.minor_version = int(version_parts[1]) - self.version_no = float(f'{self.major_version}.{self.minor_version}') - - -def _cfg_value(fname, section='DATA', value='path'): - """Utility function to fetch value from config file""" - configp = configparser.ConfigParser() - readfiles = configp.read(fname) - if not readfiles: - return '' - try: - return configp.get(section, value) - except configparser.Error: - return '' - - -def get_data_path(): - """Return specified or guessed locations of NIPY data files - - The algorithm is to return paths, extracted from strings, where - strings are found in the following order: - - #. The contents of environment variable ``NIPY_DATA_PATH`` - #. Any section = ``DATA``, key = ``path`` value in a ``config.ini`` - file in your nipy user directory (found with - ``get_nipy_user_dir()``) - #. Any section = ``DATA``, key = ``path`` value in any files found - with a ``sorted(glob.glob(os.path.join(sys_dir, '*.ini')))`` - search, where ``sys_dir`` is found with ``get_nipy_system_dir()`` - #. If ``sys.prefix`` is ``/usr``, we add - ``/usr/local/share/nipy``. We need this because Python 2.6 in - Debian / Ubuntu does default installs to ``/usr/local``. - #. The result of ``get_nipy_user_dir()`` - - Therefore, any paths found in ``NIPY_DATA_PATH`` will be searched - before paths found in the user directory ``config.ini`` - - Parameters - ---------- - None - - Returns - ------- - paths : sequence of paths - - Examples - -------- - >>> pth = get_data_path() - - Notes - ----- - We have to add ``/usr/local/share/nipy`` if sys.prefix is ``/usr``, - because Debian has patched distutils in Python 2.6 to do default - distutils installs there: - - * https://www.debian.org/doc/packaging-manuals/python-policy/ap-packaging_tools.html#s-distutils - * https://www.mail-archive.com/debian-python@lists.debian.org/msg05084.html - """ - paths = [] - try: - var = os.environ['NIPY_DATA_PATH'] - except KeyError: - pass - else: - if var: - paths = var.split(os.path.pathsep) - np_cfg = pjoin(get_nipy_user_dir(), 'config.ini') - np_etc = get_nipy_system_dir() - config_files = sorted(glob.glob(pjoin(np_etc, '*.ini'))) - for fname in [np_cfg] + config_files: - var = _cfg_value(fname) - if var: - paths += var.split(os.path.pathsep) - paths.append(pjoin(sys.prefix, 'share', 'nipy')) - if sys.prefix == '/usr': - paths.append(pjoin('/usr/local', 'share', 'nipy')) - paths.append(pjoin(get_nipy_user_dir())) - return paths - - -def find_data_dir(root_dirs, *names): - """Find relative path given path prefixes to search - - We raise a DataError if we can't find the relative path - - Parameters - ---------- - root_dirs : sequence of strings - sequence of paths in which to search for data directory - *names : sequence of strings - sequence of strings naming directory to find. The name to search - for is given by ``os.path.join(*names)`` - - Returns - ------- - data_dir : str - full path (root path added to `*names` above) - - """ - ds_relative = pjoin(*names) - for path in root_dirs: - pth = pjoin(path, ds_relative) - if os.path.isdir(pth): - return pth - raise DataError( - f'Could not find datasource "{ds_relative}" in ' - f'data path "{os.path.pathsep.join(root_dirs)}"' - ) - - -def make_datasource(pkg_def, **kwargs): - """Return datasource defined by `pkg_def` as found in `data_path` - - `data_path` is the only allowed keyword argument. - - `pkg_def` is a dictionary with at least one key - 'relpath'. 'relpath' is - a relative path with unix forward slash separators. - - The relative path to the data is found with:: - - names = pkg_def['name'].split('/') - rel_path = os.path.join(names) - - We search for this relative path in the list of paths given by `data_path`. - By default `data_path` is given by ``get_data_path()`` in this module. - - If we can't find the relative path, raise a DataError - - Parameters - ---------- - pkg_def : dict - dict containing at least the key 'relpath'. 'relpath' is the data path - of the package relative to `data_path`. It is in unix path format - (using forward slashes as directory separators). `pkg_def` can also - contain optional keys 'name' (the name of the package), and / or a key - 'install hint' that we use in the returned error message from trying to - use the resulting datasource - data_path : sequence of strings or None, optional - sequence of paths in which to search for data. If None (the - default), then use ``get_data_path()`` - - Returns - ------- - datasource : ``VersionedDatasource`` - An initialized ``VersionedDatasource`` instance - """ - if any(key for key in kwargs if key != 'data_path'): - raise ValueError('Unexpected keyword argument(s)') - data_path = kwargs.get('data_path') - if data_path is None: - data_path = get_data_path() - unix_relpath = pkg_def['relpath'] - names = unix_relpath.split('/') - try: - pth = find_data_dir(data_path, *names) - except DataError as e: - pth = [pjoin(this_data_path, *names) for this_data_path in data_path] - pkg_hint = pkg_def.get('install hint', DEFAULT_INSTALL_HINT) - msg = f'{e}; Is it possible you have not installed a data package?' - if 'name' in pkg_def: - msg += f'\n\nYou may need the package "{pkg_def["name"]}"' - if pkg_hint is not None: - msg += f'\n\n{pkg_hint}' - raise DataError(msg) - return VersionedDatasource(pth) - - -class Bomber: - """Class to raise an informative error when used""" - - def __init__(self, name, msg): - self.name = name - self.msg = msg - - def __getattr__(self, attr_name): - """Raise informative error accessing not-found attributes""" - raise BomberError( - f'Trying to access attribute "{attr_name}" of ' - f'non-existent data "{self.name}"\n\n{self.msg}\n' - ) - - -def datasource_or_bomber(pkg_def, **options): - """Return a viable datasource or a Bomber - - This is to allow module level creation of datasource objects. We - create the objects, so that, if the data exist, and are the correct - version, the objects are valid datasources, otherwise, they - raise an error on access, warning about the lack of data or the - version numbers. - - The parameters are as for ``make_datasource`` in this module. - - Parameters - ---------- - pkg_def : dict - dict containing at least key 'relpath'. Can optionally have keys 'name' - (package name), 'install hint' (for helpful error messages) and 'min - version' giving the minimum necessary version string for the package. - data_path : sequence of strings or None, optional - - Returns - ------- - ds : datasource or ``Bomber`` instance - """ - unix_relpath = pkg_def['relpath'] - version = pkg_def.get('min version') - pkg_hint = pkg_def.get('install hint', DEFAULT_INSTALL_HINT) - names = unix_relpath.split('/') - sys_relpath = os.path.sep.join(names) - try: - ds = make_datasource(pkg_def, **options) - except DataError as e: - return Bomber(sys_relpath, str(e)) - # check version - if version is None or Version(ds.version) >= Version(version): - return ds - if 'name' in pkg_def: - pkg_name = pkg_def['name'] - else: - pkg_name = 'data at ' + unix_relpath - msg = f'{pkg_name} is version {ds.version} but we need version >= {version}\n\n{pkg_hint}' - return Bomber(sys_relpath, DataError(msg)) diff --git a/nibabel/dataobj_images.py b/nibabel/dataobj_images.py deleted file mode 100644 index 3224376d4a..0000000000 --- a/nibabel/dataobj_images.py +++ /dev/null @@ -1,505 +0,0 @@ -"""File-based images that have data arrays - -The class:`DataObjImage` class defines an image that extends the -:class:`FileBasedImage` by adding an array-like object, named ``dataobj``. -This can either be an actual numpy array, or an object that: - -* returns an array from ``numpy.asanyarray(obj)``; -* has an attribute or property ``shape``. -""" - -from __future__ import annotations - -import typing as ty - -import numpy as np - -from .deprecated import deprecate_with_version -from .filebasedimages import FileBasedHeader, FileBasedImage - -if ty.TYPE_CHECKING: - import numpy.typing as npt - - from ._typing import Self - from .arrayproxy import ArrayLike - from .fileholders import FileMap - from .filename_parser import FileSpec - - -class DataobjImage(FileBasedImage): - """Template class for images that have dataobj data stores""" - - _data_cache: np.ndarray | None - _fdata_cache: np.ndarray[ty.Any, np.dtype[np.floating]] | None - - def __init__( - self, - dataobj: ArrayLike, - header: FileBasedHeader | ty.Mapping | None = None, - extra: ty.Mapping | None = None, - file_map: FileMap | None = None, - ): - """Initialize dataobj image - - The datobj image is a combination of (dataobj, header), with optional - metadata in `extra`, and filename / file-like objects contained in the - `file_map` mapping. - - Parameters - ---------- - dataobj : object - Object containing image data. It should be some object that returns - an array from ``np.asanyarray``. It should have ``shape`` and - ``ndim`` attributes or properties - header : None or mapping or header instance, optional - metadata for this image format - extra : None or mapping, optional - metadata to associate with image that cannot be stored in the - metadata of this image type - file_map : mapping, optional - mapping giving file information for this image format - """ - super().__init__(header=header, extra=extra, file_map=file_map) - self._dataobj = dataobj - self._data_cache = None - self._fdata_cache = None - - @property - def dataobj(self) -> ArrayLike: - return self._dataobj - - @deprecate_with_version( - 'get_data() is deprecated in favor of get_fdata(), which has a more predictable return ' - 'type. To obtain get_data() behavior going forward, use numpy.asanyarray(img.dataobj).', - '3.0', - '5.0', - ) - def get_data(self, caching='fill'): - """Return image data from image with any necessary scaling applied - - .. WARNING:: - - We recommend you use the ``get_fdata`` method instead of the - ``get_data`` method, because it is easier to predict the return - data type. ``get_data`` will be deprecated around November 2019 - and removed around November 2021. - - If you don't care about the predictability of the return data type, - and you want the minimum possible data size in memory, you can - replicate the array that would be returned by ``img.get_data()`` by - using ``np.asanyarray(img.dataobj)``. - - The image ``dataobj`` property can be an array proxy or an array. An - array proxy is an object that knows how to load the image data from - disk. An image with an array proxy ``dataobj`` is a *proxy image*; an - image with an array in ``dataobj`` is an *array image*. - - The default behavior for ``get_data()`` on a proxy image is to read the - data from the proxy, and store in an internal cache. Future calls to - ``get_data`` will return the cached array. This is the behavior - selected with `caching` == "fill". - - Once the data has been cached and returned from an array proxy, if you - modify the returned array, you will also modify the cached array - (because they are the same array). Regardless of the `caching` flag, - this is always true of an array image. - - Parameters - ---------- - caching : {'fill', 'unchanged'}, optional - See the Notes section for a detailed explanation. This argument - specifies whether the image object should fill in an internal - cached reference to the returned image data array. "fill" specifies - that the image should fill an internal cached reference if - currently empty. Future calls to ``get_data`` will return this - cached reference. You might prefer "fill" to save the image object - from having to reload the array data from disk on each call to - ``get_data``. "unchanged" means that the image should not fill in - the internal cached reference if the cache is currently empty. You - might prefer "unchanged" to "fill" if you want to make sure that - the call to ``get_data`` does not create an extra (cached) - reference to the returned array. In this case it is easier for - Python to free the memory from the returned array. - - Returns - ------- - data : array - array of image data - - See also - -------- - uncache: empty the array data cache - - Notes - ----- - All images have a property ``dataobj`` that represents the image array - data. Images that have been loaded from files usually do not load the - array data from file immediately, in order to reduce image load time - and memory use. For these images, ``dataobj`` is an *array proxy*; an - object that knows how to load the image array data from file. - - By default (`caching` == "fill"), when you call ``get_data`` on a - proxy image, we load the array data from disk, store (cache) an - internal reference to this array data, and return the array. The next - time you call ``get_data``, you will get the cached reference to the - array, so we don't have to load the array data from disk again. - - Array images have a ``dataobj`` property that already refers to an - array in memory, so there is no benefit to caching, and the `caching` - keywords have no effect. - - For proxy images, you may not want to fill the cache after reading the - data from disk because the cache will hold onto the array memory until - the image object is deleted, or you use the image ``uncache`` method. - If you don't want to fill the cache, then always use - ``get_data(caching='unchanged')``; in this case ``get_data`` will not - fill the cache (store the reference to the array) if the cache is empty - (no reference to the array). If the cache is full, "unchanged" leaves - the cache full and returns the cached array reference. - - The cache can affect the behavior of the image, because if the cache is - full, or you have an array image, then modifying the returned array - will modify the result of future calls to ``get_data()``. For example - you might do this: - - >>> import os - >>> import nibabel as nib - >>> from nibabel.testing import data_path - >>> img_fname = os.path.join(data_path, 'example4d.nii.gz') - - >>> img = nib.load(img_fname) # This is a proxy image - >>> nib.is_proxy(img.dataobj) - True - - The array is not yet cached by a call to "get_data", so: - - >>> img.in_memory - False - - After we call ``get_data`` using the default `caching` == 'fill', the - cache contains a reference to the returned array ``data``: - - >>> data = img.get_data() - >>> img.in_memory - True - - We modify an element in the returned data array: - - >>> data[0, 0, 0, 0] - 0 - >>> data[0, 0, 0, 0] = 99 - >>> data[0, 0, 0, 0] - 99 - - The next time we call 'get_data', the method returns the cached - reference to the (modified) array: - - >>> data_again = img.get_data() - >>> data_again is data - True - >>> data_again[0, 0, 0, 0] - 99 - - If you had *initially* used `caching` == 'unchanged' then the returned - ``data`` array would have been loaded from file, but not cached, and: - - >>> img = nib.load(img_fname) # a proxy image again - >>> data = img.get_data(caching='unchanged') - >>> img.in_memory - False - >>> data[0, 0, 0] = 99 - >>> data_again = img.get_data(caching='unchanged') - >>> data_again is data - False - >>> data_again[0, 0, 0, 0] - 0 - """ - if caching not in ('fill', 'unchanged'): - raise ValueError('caching value should be "fill" or "unchanged"') - if self._data_cache is not None: - return self._data_cache - data = np.asanyarray(self._dataobj) - if caching == 'fill': - self._data_cache = data - return data - - def get_fdata( - self, - caching: ty.Literal['fill', 'unchanged'] = 'fill', - dtype: npt.DTypeLike = np.float64, - ) -> np.ndarray[ty.Any, np.dtype[np.floating]]: - """Return floating point image data with necessary scaling applied - - The image ``dataobj`` property can be an array proxy or an array. An - array proxy is an object that knows how to load the image data from - disk. An image with an array proxy ``dataobj`` is a *proxy image*; an - image with an array in ``dataobj`` is an *array image*. - - The default behavior for ``get_fdata()`` on a proxy image is to read - the data from the proxy, and store in an internal cache. Future calls - to ``get_fdata`` will return the cached array. This is the behavior - selected with `caching` == "fill". - - Once the data has been cached and returned from an array proxy, if you - modify the returned array, you will also modify the cached array - (because they are the same array). Regardless of the `caching` flag, - this is always true of an array image. - - Parameters - ---------- - caching : {'fill', 'unchanged'}, optional - See the Notes section for a detailed explanation. This argument - specifies whether the image object should fill in an internal - cached reference to the returned image data array. "fill" specifies - that the image should fill an internal cached reference if - currently empty. Future calls to ``get_fdata`` will return this - cached reference. You might prefer "fill" to save the image object - from having to reload the array data from disk on each call to - ``get_fdata``. "unchanged" means that the image should not fill in - the internal cached reference if the cache is currently empty. You - might prefer "unchanged" to "fill" if you want to make sure that - the call to ``get_fdata`` does not create an extra (cached) - reference to the returned array. In this case it is easier for - Python to free the memory from the returned array. - dtype : numpy dtype specifier - A numpy dtype specifier specifying a floating point type. Data is - returned as this floating point type. Default is ``np.float64``. - - Returns - ------- - fdata : array - Array of image data of data type `dtype`. - - See also - -------- - uncache: empty the array data cache - - Notes - ----- - All images have a property ``dataobj`` that represents the image array - data. Images that have been loaded from files usually do not load the - array data from file immediately, in order to reduce image load time - and memory use. For these images, ``dataobj`` is an *array proxy*; an - object that knows how to load the image array data from file. - - By default (`caching` == "fill"), when you call ``get_fdata`` on a - proxy image, we load the array data from disk, store (cache) an - internal reference to this array data, and return the array. The next - time you call ``get_fdata``, you will get the cached reference to the - array, so we don't have to load the array data from disk again. - - Array images have a ``dataobj`` property that already refers to an - array in memory, so there is no benefit to caching, and the `caching` - keywords have no effect. - - For proxy images, you may not want to fill the cache after reading the - data from disk because the cache will hold onto the array memory until - the image object is deleted, or you use the image ``uncache`` method. - If you don't want to fill the cache, then always use - ``get_fdata(caching='unchanged')``; in this case ``get_fdata`` will not - fill the cache (store the reference to the array) if the cache is empty - (no reference to the array). If the cache is full, "unchanged" leaves - the cache full and returns the cached array reference. - - The cache can effect the behavior of the image, because if the cache is - full, or you have an array image, then modifying the returned array - will modify the result of future calls to ``get_fdata()``. For example - you might do this: - - >>> import os - >>> import nibabel as nib - >>> from nibabel.testing import data_path - >>> img_fname = os.path.join(data_path, 'example4d.nii.gz') - - >>> img = nib.load(img_fname) # This is a proxy image - >>> nib.is_proxy(img.dataobj) - True - - The array is not yet cached by a call to "get_fdata", so: - - >>> img.in_memory - False - - After we call ``get_fdata`` using the default `caching` == 'fill', the - cache contains a reference to the returned array ``data``: - - >>> data = img.get_fdata() - >>> img.in_memory - True - - We modify an element in the returned data array: - - >>> data[0, 0, 0, 0] - 0.0 - >>> data[0, 0, 0, 0] = 99 - >>> data[0, 0, 0, 0] - 99.0 - - The next time we call 'get_fdata', the method returns the cached - reference to the (modified) array: - - >>> data_again = img.get_fdata() - >>> data_again is data - True - >>> data_again[0, 0, 0, 0] - 99.0 - - If you had *initially* used `caching` == 'unchanged' then the returned - ``data`` array would have been loaded from file, but not cached, and: - - >>> img = nib.load(img_fname) # a proxy image again - >>> data = img.get_fdata(caching='unchanged') - >>> img.in_memory - False - >>> data[0, 0, 0] = 99 - >>> data_again = img.get_fdata(caching='unchanged') - >>> data_again is data - False - >>> data_again[0, 0, 0, 0] - 0.0 - """ - if caching not in ('fill', 'unchanged'): - raise ValueError('caching value should be "fill" or "unchanged"') - dtype = np.dtype(dtype) - if not issubclass(dtype.type, np.inexact): - raise ValueError(f'{dtype} should be floating point type') - # Return cache if cache present and of correct dtype. - if self._fdata_cache is not None: - if self._fdata_cache.dtype.type == dtype.type: - return self._fdata_cache - # Always return requested data type - # For array proxies, will attempt to confine data array to dtype - # during scaling - data = np.asanyarray(self._dataobj, dtype=dtype) - if caching == 'fill': - self._fdata_cache = data - return data - - @property - def in_memory(self) -> bool: - """True when any array data is in memory cache - - There are separate caches for `get_data` reads and `get_fdata` reads. - This property is True if either of those caches are set. - """ - return ( - isinstance(self._dataobj, np.ndarray) - or self._fdata_cache is not None - or self._data_cache is not None - ) - - def uncache(self) -> None: - """Delete any cached read of data from proxied data - - Remember there are two types of images: - - * *array images* where the data ``img.dataobj`` is an array - * *proxy images* where the data ``img.dataobj`` is a proxy object - - If you call ``img.get_fdata()`` on a proxy image, the result of reading - from the proxy gets cached inside the image object, and this cache is - what gets returned from the next call to ``img.get_fdata()``. If you - modify the returned data, as in:: - - data = img.get_fdata() - data[:] = 42 - - then the next call to ``img.get_fdata()`` returns the modified array, - whether the image is an array image or a proxy image:: - - assert np.all(img.get_fdata() == 42) - - When you uncache an array image, this has no effect on the return of - ``img.get_fdata()``, but when you uncache a proxy image, the result of - ``img.get_fdata()`` returns to its original value. - """ - self._fdata_cache = None - self._data_cache = None - - @property - def shape(self) -> tuple[int, ...]: - return self._dataobj.shape - - @property - def ndim(self) -> int: - return self._dataobj.ndim - - @classmethod - def from_file_map( - klass, - file_map: FileMap, - *, - mmap: bool | ty.Literal['c', 'r'] = True, - keep_file_open: bool | None = None, - ) -> Self: - """Class method to create image from mapping in ``file_map`` - - Parameters - ---------- - file_map : dict - Mapping with (key, value) pairs of (``file_type``, FileHolder - instance giving file-likes for each file needed for this image - type. - mmap : {True, False, 'c', 'r'}, optional, keyword only - `mmap` controls the use of numpy memory mapping for reading image - array data. If False, do not try numpy ``memmap`` for data array. - If one of {'c', 'r'}, try numpy memmap with ``mode=mmap``. A - `mmap` value of True gives the same behavior as ``mmap='c'``. If - image data file cannot be memory-mapped, ignore `mmap` value and - read array from file. - keep_file_open : { None, True, False }, optional, keyword only - `keep_file_open` controls whether a new file handle is created - every time the image is accessed, or a single file handle is - created and used for the lifetime of this ``ArrayProxy``. If - ``True``, a single file handle is created and used. If ``False``, - a new file handle is created every time the image is accessed. - If ``file_map`` refers to an open file handle, this setting has no - effect. The default value (``None``) will result in the value of - ``nibabel.arrayproxy.KEEP_FILE_OPEN_DEFAULT`` being used. - - Returns - ------- - img : DataobjImage instance - """ - raise NotImplementedError - - @classmethod - def from_filename( - klass, - filename: FileSpec, - *, - mmap: bool | ty.Literal['c', 'r'] = True, - keep_file_open: bool | None = None, - ) -> Self: - """Class method to create image from filename `filename` - - Parameters - ---------- - filename : str - Filename of image to load - mmap : {True, False, 'c', 'r'}, optional, keyword only - `mmap` controls the use of numpy memory mapping for reading image - array data. If False, do not try numpy ``memmap`` for data array. - If one of {'c', 'r'}, try numpy memmap with ``mode=mmap``. A - `mmap` value of True gives the same behavior as ``mmap='c'``. If - image data file cannot be memory-mapped, ignore `mmap` value and - read array from file. - keep_file_open : { None, True, False }, optional, keyword only - `keep_file_open` controls whether a new file handle is created - every time the image is accessed, or a single file handle is - created and used for the lifetime of this ``ArrayProxy``. If - ``True``, a single file handle is created and used. If ``False``, - a new file handle is created every time the image is accessed. - The default value (``None``) will result in the value of - ``nibabel.arrayproxy.KEEP_FILE_OPEN_DEFAULT`` being used. - - Returns - ------- - img : DataobjImage instance - """ - if mmap not in (True, False, 'c', 'r'): - raise ValueError("mmap should be one of {True, False, 'c', 'r'}") - file_map = klass.filespec_to_file_map(filename) - return klass.from_file_map(file_map, mmap=mmap, keep_file_open=keep_file_open) - - load = from_filename diff --git a/nibabel/deprecated.py b/nibabel/deprecated.py deleted file mode 100644 index 394fb0799a..0000000000 --- a/nibabel/deprecated.py +++ /dev/null @@ -1,121 +0,0 @@ -"""Module to help with deprecating objects and classes""" - -from __future__ import annotations - -import typing as ty -import warnings - -from ._typing import ParamSpec -from .deprecator import Deprecator -from .pkg_info import cmp_pkg_version - -P = ParamSpec('P') - - -class ModuleProxy: - """Proxy for module that may not yet have been imported - - Parameters - ---------- - module_name : str - Full module name e.g. ``nibabel.minc`` - - Examples - -------- - - :: - arr = np.arange(24).reshape((2, 3, 4)) - nifti1 = ModuleProxy('nibabel.nifti1') - nifti1_image = nifti1.Nifti1Image(arr, np.eye(4)) - - So, the ``nifti1`` object is a proxy that will import the required module - when you do attribute access and return the attributes of the imported - module. - """ - - def __init__(self, module_name: str) -> None: - self._module_name = module_name - - def __getattr__(self, key: str) -> ty.Any: - mod = __import__(self._module_name, fromlist=['']) - return getattr(mod, key) - - def __repr__(self) -> str: - return f'' - - -class FutureWarningMixin(ty.Generic[P]): - """Insert FutureWarning for object creation - - Examples - -------- - >>> class C: pass - >>> class D(FutureWarningMixin, C): - ... warn_message = "Please, don't use this class" - - Record the warning - - >>> with warnings.catch_warnings(record=True) as warns: - ... d = D() - ... warns[0].message.args[0] - "Please, don't use this class" - """ - - warn_message = 'This class will be removed in future versions' - - def __init__(self, *args: P.args, **kwargs: P.kwargs) -> None: - warnings.warn(self.warn_message, FutureWarning, stacklevel=2) - super().__init__(*args, **kwargs) - - -class VisibleDeprecationWarning(UserWarning): - """Deprecation warning that will be shown by default - - Python >= 2.7 does not show standard DeprecationWarnings by default: - - http://docs.python.org/dev/whatsnew/2.7.html#the-future-for-python-2-x - - Use this class for cases where we do want to show deprecations by default. - """ - - pass - - -deprecate_with_version = Deprecator(cmp_pkg_version) - - -def alert_future_error( - msg: str, - version: str, - *, - warning_class: type[Warning] = FutureWarning, - error_class: type[Exception] = RuntimeError, - warning_rec: str = '', - error_rec: str = '', - stacklevel: int = 2, -) -> None: - """Warn or error with appropriate messages for changing functionality. - - Parameters - ---------- - msg : str - Description of the condition that led to the alert - version : str - NiBabel version at which the warning will become an error - warning_class : subclass of Warning, optional - Warning class to emit before version - error_class : subclass of Exception, optional - Error class to emit after version - warning_rec : str, optional - Guidance for suppressing the warning and avoiding the future error - error_rec: str, optional - Guidance for resolving the error - stacklevel: int, optional - Warnings stacklevel to provide; note that this will be incremented by - 1, so provide the stacklevel you would provide directly to warnings.warn() - """ - if cmp_pkg_version(version) > 0: - msg = f'{msg} This will error in NiBabel {version}. {warning_rec}' - warnings.warn(msg.strip(), warning_class, stacklevel=stacklevel + 1) - else: - raise error_class(f'{msg} {error_rec}'.strip()) diff --git a/nibabel/deprecator.py b/nibabel/deprecator.py deleted file mode 100644 index 972e5f2a83..0000000000 --- a/nibabel/deprecator.py +++ /dev/null @@ -1,244 +0,0 @@ -"""Class for recording and reporting deprecations""" - -from __future__ import annotations - -import functools -import re -import sys -import typing as ty -import warnings -from textwrap import dedent - -if ty.TYPE_CHECKING: - T = ty.TypeVar('T') - P = ty.ParamSpec('P') - -_LEADING_WHITE = re.compile(r'^(\s*)') - - -def _dedent_docstring(docstring): - """Compatibility with Python 3.13+. - - xref: https://github.com/python/cpython/issues/81283 - """ - return '\n'.join([dedent(line) for line in docstring.split('\n')]) - - -TESTSETUP = """ - -.. testsetup:: - - >>> import pytest - >>> import warnings - >>> _suppress_warnings = pytest.deprecated_call() - >>> _ = _suppress_warnings.__enter__() - -""" - -TESTCLEANUP = """ - -.. testcleanup:: - - >>> warnings.warn("Avoid error if no doctests to run...", DeprecationWarning) - >>> _ = _suppress_warnings.__exit__(None, None, None) - -""" - -if sys.version_info >= (3, 13): - TESTSETUP = _dedent_docstring(TESTSETUP) - TESTCLEANUP = _dedent_docstring(TESTCLEANUP) - - -class ExpiredDeprecationError(RuntimeError): - """Error for expired deprecation - - Error raised when a called function or method has passed out of its - deprecation period. - """ - - pass - - -def _ensure_cr(text: str) -> str: - """Remove trailing whitespace and add carriage return - - Ensures that `text` always ends with a carriage return - """ - return text.rstrip() + '\n' - - -def _add_dep_doc( - old_doc: str, - dep_doc: str, - setup: str = '', - cleanup: str = '', -) -> str: - """Add deprecation message `dep_doc` to docstring in `old_doc` - - Parameters - ---------- - old_doc : str - Docstring from some object. - dep_doc : str - Deprecation warning to add to top of docstring, after initial line. - setup : str, optional - Doctest setup text - cleanup : str, optional - Doctest teardown text - - Returns - ------- - new_doc : str - `old_doc` with `dep_doc` inserted after any first lines of docstring. - """ - dep_doc = _ensure_cr(dep_doc) - if not old_doc: - return dep_doc - old_doc = _ensure_cr(old_doc) - old_lines = old_doc.splitlines() - new_lines = [] - for line_no, line in enumerate(old_lines): - if line.strip(): - new_lines.append(line) - else: - break - next_line = line_no + 1 - if next_line >= len(old_lines): - # nothing following first paragraph, just append message - return old_doc + '\n' + dep_doc - leading_white = _LEADING_WHITE.match(old_lines[next_line]) - assert leading_white is not None # Type narrowing, since this always matches - indent = leading_white.group() - setup_lines = [indent + L for L in setup.splitlines()] - dep_lines = [indent + L for L in [''] + dep_doc.splitlines() + ['']] - cleanup_lines = [indent + L for L in cleanup.splitlines()] - return '\n'.join( - new_lines + dep_lines + setup_lines + old_lines[next_line:] + cleanup_lines + [''] - ) - - -class Deprecator: - """Class to make decorator marking function or method as deprecated - - The decorated function / method will: - - * Raise the given `warning_class` warning when the function / method gets - called, up to (and including) version `until` (if specified); - * Raise the given `error_class` error when the function / method gets - called, when the package version is greater than version `until` (if - specified). - - Parameters - ---------- - version_comparator : callable - Callable accepting string as argument, and return 1 if string - represents a higher version than encoded in the `version_comparator`, 0 - if the version is equal, and -1 if the version is lower. For example, - the `version_comparator` may compare the input version string to the - current package version string. - warn_class : class, optional - Class of warning to generate for deprecation. - error_class : class, optional - Class of error to generate when `version_comparator` returns 1 for a - given argument of ``until`` in the ``__call__`` method (see below). - """ - - def __init__( - self, - version_comparator: ty.Callable[[str], int], - warn_class: type[Warning] = DeprecationWarning, - error_class: type[Exception] = ExpiredDeprecationError, - ) -> None: - self.version_comparator = version_comparator - self.warn_class = warn_class - self.error_class = error_class - - def is_bad_version(self, version_str: str) -> bool: - """Return True if `version_str` is too high - - Tests `version_str` with ``self.version_comparator`` - - Parameters - ---------- - version_str : str - String giving version to test - - Returns - ------- - is_bad : bool - True if `version_str` is for version below that expected by - ``self.version_comparator``, False otherwise. - """ - return self.version_comparator(version_str) == -1 - - def __call__( - self, - message: str, - since: str = '', - until: str = '', - warn_class: type[Warning] | None = None, - error_class: type[Exception] | None = None, - ) -> ty.Callable[[ty.Callable[P, T]], ty.Callable[P, T]]: - """Return decorator function function for deprecation warning / error - - Parameters - ---------- - message : str - Message explaining deprecation, giving possible alternatives. - since : str, optional - Released version at which object was first deprecated. - until : str, optional - Last released version at which this function will still raise a - deprecation warning. Versions higher than this will raise an - error. - warn_class : None or class, optional - Class of warning to generate for deprecation (overrides instance - default). - error_class : None or class, optional - Class of error to generate when `version_comparator` returns 1 for a - given argument of ``until`` (overrides class default). - - Returns - ------- - deprecator : func - Function returning a decorator. - """ - exception = error_class if error_class is not None else self.error_class - warning = warn_class if warn_class is not None else self.warn_class - messages = [message] - if (since, until) != ('', ''): - messages.append('') - if since: - messages.append('* deprecated from version: ' + since) - if until: - messages.append( - f'* {"Raises" if self.is_bad_version(until) else "Will raise"} ' - f'{exception} as of version: {until}' - ) - message = '\n'.join(messages) - - def deprecator(func: ty.Callable[P, T]) -> ty.Callable[P, T]: - @functools.wraps(func) - def deprecated_func(*args: P.args, **kwargs: P.kwargs) -> T: - if until and self.is_bad_version(until): - raise exception(message) - warnings.warn(message, warning, stacklevel=2) - return func(*args, **kwargs) - - keep_doc = deprecated_func.__doc__ - if keep_doc is None: - keep_doc = '' - setup = TESTSETUP - cleanup = TESTCLEANUP - # After expiration, remove all but the first paragraph. - # The details are no longer relevant, but any code will likely - # raise exceptions we don't need. - if keep_doc and until and self.is_bad_version(until): - lines = '\n'.join(line.rstrip() for line in keep_doc.splitlines()) - keep_doc = lines.split('\n\n', 1)[0] - setup = '' - cleanup = '' - deprecated_func.__doc__ = _add_dep_doc(keep_doc, message, setup, cleanup) - return deprecated_func - - return deprecator diff --git a/nibabel/dft.py b/nibabel/dft.py deleted file mode 100644 index 23108895b2..0000000000 --- a/nibabel/dft.py +++ /dev/null @@ -1,511 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# Copyright (C) 2011 Christian Haselgrove -"""DICOM filesystem tools""" - -import contextlib -import getpass -import logging -import os -import sqlite3 -import tempfile -import warnings -from io import BytesIO -from os.path import join as pjoin - -import numpy - -from nibabel.optpkg import optional_package - -from .nifti1 import Nifti1Header - -pydicom = optional_package('pydicom')[0] - -logger = logging.getLogger('nibabel.dft') - - -class DFTError(Exception): - """base class for DFT exceptions""" - - -class CachingError(DFTError): - """error while caching""" - - -class VolumeError(DFTError): - """unsupported volume parameter""" - - -class InstanceStackError(DFTError): - """bad series of instance numbers""" - - def __init__(self, series, i, si): - self.series = series - self.i = i - self.si = si - - def __str__(self): - fmt = 'expecting instance number %d, got %d' - return fmt % (self.i + 1, self.si.instance_number) - - -class _Study: - def __init__(self, d): - self.uid = d['uid'] - self.date = d['date'] - self.time = d['time'] - self.comments = d['comments'] - self.patient_name = d['patient_name'] - self.patient_id = d['patient_id'] - self.patient_birth_date = d['patient_birth_date'] - self.patient_sex = d['patient_sex'] - self.series = None - - def __getattribute__(self, name): - val = object.__getattribute__(self, name) - if name == 'series' and val is None: - val = [] - with DB.readonly_cursor() as c: - c.execute('SELECT * FROM series WHERE study = ?', (self.uid,)) - cols = [el[0] for el in c.description] - for row in c: - d = dict(zip(cols, row)) - val.append(_Series(d)) - self.series = val - return val - - def patient_name_or_uid(self): - if self.patient_name == '': - return self.uid - return self.patient_name - - -class _Series: - def __init__(self, d): - self.uid = d['uid'] - self.study = d['study'] - self.number = d['number'] - self.description = d['description'] - self.rows = d['rows'] - self.columns = d['columns'] - self.bits_allocated = d['bits_allocated'] - self.bits_stored = d['bits_stored'] - self.storage_instances = None - - def __getattribute__(self, name): - val = object.__getattribute__(self, name) - if name == 'storage_instances' and val is None: - val = [] - with DB.readonly_cursor() as c: - query = """SELECT * - FROM storage_instance - WHERE series = ? - ORDER BY instance_number""" - c.execute(query, (self.uid,)) - cols = [el[0] for el in c.description] - for row in c: - d = dict(zip(cols, row)) - val.append(_StorageInstance(d)) - self.storage_instances = val - return val - - def as_png(self, index=None, scale_to_slice=True): - import PIL.Image - - # For compatibility with older versions of PIL that did not - # have `frombytes`: - if hasattr(PIL.Image, 'frombytes'): - frombytes = PIL.Image.frombytes - else: - frombytes = PIL.Image.fromstring - - if index is None: - index = len(self.storage_instances) // 2 - d = self.storage_instances[index].dicom() - data = d.pixel_array.copy() - if self.bits_allocated != 16: - raise VolumeError('unsupported bits allocated') - if self.bits_stored != 12: - raise VolumeError('unsupported bits stored') - data = data / 16 - if scale_to_slice: - min = data.min() - max = data.max() - data = data * 255 / (max - min) - data = data.astype(numpy.uint8) - im = frombytes('L', (self.rows, self.columns), data.tobytes()) - - s = BytesIO() - im.save(s, 'PNG') - return s.getvalue() - - def png_size(self, index=None, scale_to_slice=True): - return len(self.as_png(index=index, scale_to_slice=scale_to_slice)) - - def as_nifti(self): - if len(self.storage_instances) < 2: - raise VolumeError('too few slices') - d = self.storage_instances[0].dicom() - if self.bits_allocated != 16: - raise VolumeError('unsupported bits allocated') - if self.bits_stored != 12: - raise VolumeError('unsupported bits stored') - data = numpy.ndarray( - (len(self.storage_instances), self.rows, self.columns), dtype=numpy.int16 - ) - for i, si in enumerate(self.storage_instances): - if i + 1 != si.instance_number: - raise InstanceStackError(self, i, si) - logger.info(f'reading {i + 1}/{len(self.storage_instances)}') - d = self.storage_instances[i].dicom() - data[i, :, :] = d.pixel_array - - d1 = self.storage_instances[0].dicom() - dn = self.storage_instances[-1].dicom() - - pdi = d1.PixelSpacing[0] - pdj = d1.PixelSpacing[0] - pdk = d1.SpacingBetweenSlices - - cosi = d1.ImageOrientationPatient[0:3] - cosi[0] = -1 * cosi[0] - cosi[1] = -1 * cosi[1] - cosj = d1.ImageOrientationPatient[3:6] - cosj[0] = -1 * cosj[0] - cosj[1] = -1 * cosj[1] - - pos_1 = numpy.array(d1.ImagePositionPatient) - pos_1[0] = -1 * pos_1[0] - pos_1[1] = -1 * pos_1[1] - pos_n = numpy.array(dn.ImagePositionPatient) - pos_n[0] = -1 * pos_n[0] - pos_n[1] = -1 * pos_n[1] - cosk = pos_n - pos_1 - cosk = cosk / numpy.linalg.norm(cosk) - - m = ( - (pdi * cosi[0], pdj * cosj[0], pdk * cosk[0], pos_1[0]), - (pdi * cosi[1], pdj * cosj[1], pdk * cosk[1], pos_1[1]), - (pdi * cosi[2], pdj * cosj[2], pdk * cosk[2], pos_1[2]), - (0, 0, 0, 1), - ) - - # Values are python Decimals in pydicom 0.9.7 - m = numpy.array(m, dtype=float) - - hdr = Nifti1Header(endianness='<') - hdr.set_intent(0) - hdr.set_qform(m, 1) - hdr.set_xyzt_units(2, 8) - hdr.set_data_dtype(numpy.int16) - hdr.set_data_shape((self.columns, self.rows, len(self.storage_instances))) - - s = BytesIO() - hdr.write_to(s) - - return s.getvalue() + data.tobytes() - - def nifti_size(self): - return 352 + 2 * len(self.storage_instances) * self.columns * self.rows - - -class _StorageInstance: - def __init__(self, d): - self.uid = d['uid'] - self.instance_number = d['instance_number'] - self.series = d['series'] - self.files = None - - def __getattribute__(self, name): - val = object.__getattribute__(self, name) - if name == 'files' and val is None: - with DB.readonly_cursor() as c: - query = """SELECT directory, name - FROM file - WHERE storage_instance = ? - ORDER BY directory, name""" - c.execute(query, (self.uid,)) - val = ['{}/{}'.format(*tuple(row)) for row in c] - self.files = val - return val - - def dicom(self): - return pydicom.dcmread(self.files[0]) - - -def _get_subdirs(base_dir, files_dict=None, followlinks=False): - dirs = [] - for dirpath, dirnames, filenames in os.walk(base_dir, followlinks=followlinks): - abs_dir = os.path.realpath(dirpath) - if abs_dir in dirs: - raise CachingError(f'link cycle detected under {base_dir}') - dirs.append(abs_dir) - if files_dict is not None: - files_dict[abs_dir] = filenames - return dirs - - -def update_cache(base_dir, followlinks=False): - mtimes = {} - files_by_dir = {} - dirs = _get_subdirs(base_dir, files_by_dir, followlinks) - for d in dirs: - os.stat(d) - mtimes[d] = os.stat(d).st_mtime - with DB.readwrite_cursor() as c: - c.execute('SELECT path, mtime FROM directory') - db_mtimes = dict(c) - c.execute('SELECT uid FROM study') - studies = [row[0] for row in c] - c.execute('SELECT uid FROM series') - series = [row[0] for row in c] - c.execute('SELECT uid FROM storage_instance') - storage_instances = [row[0] for row in c] - for dir in sorted(mtimes.keys()): - if dir in db_mtimes and mtimes[dir] <= db_mtimes[dir]: - continue - logger.debug(f'updating {dir}') - _update_dir(c, dir, files_by_dir[dir], studies, series, storage_instances) - if dir in db_mtimes: - query = 'UPDATE directory SET mtime = ? WHERE path = ?' - c.execute(query, (mtimes[dir], dir)) - else: - query = 'INSERT INTO directory (path, mtime) VALUES (?, ?)' - c.execute(query, (dir, mtimes[dir])) - - -def get_studies(base_dir=None, followlinks=False): - if base_dir is not None: - update_cache(base_dir, followlinks) - if base_dir is None: - with DB.readonly_cursor() as c: - c.execute('SELECT * FROM study') - studies = [] - cols = [el[0] for el in c.description] - for row in c: - d = dict(zip(cols, row)) - studies.append(_Study(d)) - return studies - query = """SELECT study - FROM series - WHERE uid IN (SELECT series - FROM storage_instance - WHERE uid IN (SELECT storage_instance - FROM file - WHERE directory = ?))""" - with DB.readonly_cursor() as c: - study_uids = {} - for dir in _get_subdirs(base_dir, followlinks=followlinks): - c.execute(query, (dir,)) - for row in c: - study_uids[row[0]] = None - studies = [] - for uid in study_uids: - c.execute('SELECT * FROM study WHERE uid = ?', (uid,)) - cols = [el[0] for el in c.description] - d = dict(zip(cols, c.fetchone())) - studies.append(_Study(d)) - return studies - - -def _update_dir(c, dir, files, studies, series, storage_instances): - logger.debug(f'Updating directory {dir}') - c.execute('SELECT name, mtime FROM file WHERE directory = ?', (dir,)) - db_mtimes = dict(c) - for fname in db_mtimes: - if fname not in files: - logger.debug(f' remove {fname}') - c.execute('DELETE FROM file WHERE directory = ? AND name = ?', (dir, fname)) - for fname in files: - mtime = os.lstat(f'{dir}/{fname}').st_mtime - if fname in db_mtimes and mtime <= db_mtimes[fname]: - logger.debug(f' okay {fname}') - else: - logger.debug(f' update {fname}') - si_uid = _update_file(c, dir, fname, studies, series, storage_instances) - if fname not in db_mtimes: - query = """INSERT INTO file (directory, - name, - mtime, - storage_instance) - VALUES (?, ?, ?, ?)""" - c.execute(query, (dir, fname, mtime, si_uid)) - else: - query = """UPDATE file - SET mtime = ?, storage_instance = ? - WHERE directory = ? AND name = ?""" - c.execute(query, (mtime, si_uid, dir, fname)) - - -def _update_file(c, path, fname, studies, series, storage_instances): - try: - do = pydicom.dcmread(f'{path}/{fname}') - except pydicom.filereader.InvalidDicomError: - logger.debug(' not a DICOM file') - return None - try: - study_comments = do.StudyComments - except AttributeError: - study_comments = '' - try: - logger.debug(f' storage instance {do.SOPInstanceUID}') - if str(do.StudyInstanceUID) not in studies: - query = """INSERT INTO study (uid, - date, - time, - comments, - patient_name, - patient_id, - patient_birth_date, - patient_sex) - VALUES (?, ?, ?, ?, ?, ?, ?, ?)""" - params = ( - str(do.StudyInstanceUID), - do.StudyDate, - do.StudyTime, - study_comments, - str(do.PatientName), - do.PatientID, - do.PatientBirthDate, - do.PatientSex, - ) - c.execute(query, params) - studies.append(str(do.StudyInstanceUID)) - if str(do.SeriesInstanceUID) not in series: - query = """INSERT INTO series (uid, - study, - number, - description, - rows, - columns, - bits_allocated, - bits_stored) - VALUES (?, ?, ?, ?, ?, ?, ?, ?)""" - params = ( - str(do.SeriesInstanceUID), - str(do.StudyInstanceUID), - do.SeriesNumber, - do.SeriesDescription, - do.Rows, - do.Columns, - do.BitsAllocated, - do.BitsStored, - ) - c.execute(query, params) - series.append(str(do.SeriesInstanceUID)) - if str(do.SOPInstanceUID) not in storage_instances: - query = """INSERT INTO storage_instance (uid, instance_number, series) - VALUES (?, ?, ?)""" - params = (str(do.SOPInstanceUID), do.InstanceNumber, str(do.SeriesInstanceUID)) - c.execute(query, params) - storage_instances.append(str(do.SOPInstanceUID)) - except AttributeError as data: - logger.debug(f' {data}') - return None - return str(do.SOPInstanceUID) - - -def clear_cache(): - with DB.readwrite_cursor() as c: - c.execute('DELETE FROM file') - c.execute('DELETE FROM directory') - c.execute('DELETE FROM storage_instance') - c.execute('DELETE FROM series') - c.execute('DELETE FROM study') - - -CREATE_QUERIES = ( - """CREATE TABLE study (uid TEXT NOT NULL PRIMARY KEY, - date TEXT NOT NULL, - time TEXT NOT NULL, - comments TEXT NOT NULL, - patient_name TEXT NOT NULL, - patient_id TEXT NOT NULL, - patient_birth_date TEXT NOT NULL, - patient_sex TEXT NOT NULL)""", - """CREATE TABLE series (uid TEXT NOT NULL PRIMARY KEY, - study TEXT NOT NULL REFERENCES study, - number TEXT NOT NULL, - description TEXT NOT NULL, - rows INTEGER NOT NULL, - columns INTEGER NOT NULL, - bits_allocated INTEGER NOT NULL, - bits_stored INTEGER NOT NULL)""", - """CREATE TABLE storage_instance (uid TEXT NOT NULL PRIMARY KEY, - instance_number INTEGER NOT NULL, - series TEXT NOT NULL references series)""", - """CREATE TABLE directory (path TEXT NOT NULL PRIMARY KEY, - mtime INTEGER NOT NULL)""", - """CREATE TABLE file (directory TEXT NOT NULL REFERENCES directory, - name TEXT NOT NULL, - mtime INTEGER NOT NULL, - storage_instance TEXT DEFAULT NULL REFERENCES storage_instance, - PRIMARY KEY (directory, name))""", -) - - -class _DB: - def __init__(self, fname=None, verbose=True): - self.fname = fname or pjoin(tempfile.gettempdir(), f'dft.{getpass.getuser()}.sqlite') - self.verbose = verbose - - @property - def session(self): - """Get sqlite3 Connection - - The connection is created on the first call of this property - """ - try: - return self._session - except AttributeError: - self._init_db() - return self._session - - def _init_db(self): - if self.verbose: - logger.info('db filename: ' + self.fname) - - self._session = sqlite3.connect(self.fname, isolation_level='EXCLUSIVE') - with self.readwrite_cursor() as c: - c.execute("SELECT COUNT(*) FROM sqlite_master WHERE type = 'table'") - if c.fetchone()[0] == 0: - logger.debug('create') - for q in CREATE_QUERIES: - c.execute(q) - - def __repr__(self): - return f'' - - @contextlib.contextmanager - def readonly_cursor(self): - cursor = self.session.cursor() - try: - yield cursor - finally: - cursor.close() - self.session.rollback() - - @contextlib.contextmanager - def readwrite_cursor(self): - cursor = self.session.cursor() - try: - yield cursor - except Exception: - self.session.rollback() - raise - finally: - cursor.close() - self.session.commit() - - -DB = None -if os.name == 'nt': - warnings.warn('dft needs FUSE which is not available for windows') -else: - DB = _DB() diff --git a/nibabel/ecat.py b/nibabel/ecat.py deleted file mode 100644 index f634bcd8a6..0000000000 --- a/nibabel/ecat.py +++ /dev/null @@ -1,1021 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Read ECAT format images - -An ECAT format image consists of: - -* a *main header*; -* at least one *matrix list* (mlist); - -ECAT thinks of memory locations in terms of *blocks*. One block is 512 -bytes. Thus block 1 starts at 0 bytes, block 2 at 512 bytes, and so on. - -The matrix list is an array with one row per frame in the data. - -Columns in the matrix list are: - -* 0: Matrix identifier (frame number) -* 1: matrix data start block number (subheader followed by image data) -* 2: Last block number of matrix (image) data -* 3: Matrix status - - * 1: hxists - rw - * 2: exists - ro - * 3: matrix deleted - -There is one sub-header for each image frame (or matrix in the terminology -above). A sub-header can also be called an *image header*. The sub-header is -one block (512 bytes), and the frame (image) data follows. - -There is very little documentation of the ECAT format, and many of the comments -in this code come from a combination of trial and error and wild speculation. - -XMedcon can read and write ECAT 6 format, and read ECAT 7 format: see -http://xmedcon.sourceforge.net and the ECAT files in the source of XMedCon, -currently ``libs/tpc/*ecat*`` and ``source/m-ecat*``. Unfortunately XMedCon is -GPL and some of the header files are adapted from CTI files (called CTI code -below). It's not clear what the licenses are for these files. -""" - -import warnings -from numbers import Integral - -import numpy as np - -from .arraywriters import make_array_writer -from .fileslice import canonical_slicers, predict_shape, slice2outax -from .spatialimages import SpatialHeader, SpatialImage -from .volumeutils import array_from_file, make_dt_codes, native_code, swapped_code -from .wrapstruct import WrapStruct - -BLOCK_SIZE = 512 - -main_header_dtd = [ - ('magic_number', '14S'), - ('original_filename', '32S'), - ('sw_version', np.uint16), - ('system_type', np.uint16), - ('file_type', np.uint16), - ('serial_number', '10S'), - ('scan_start_time', np.uint32), - ('isotope_name', '8S'), - ('isotope_halflife', np.float32), - ('radiopharmaceutical', '32S'), - ('gantry_tilt', np.float32), - ('gantry_rotation', np.float32), - ('bed_elevation', np.float32), - ('intrinsic_tilt', np.float32), - ('wobble_speed', np.uint16), - ('transm_source_type', np.uint16), - ('distance_scanned', np.float32), - ('transaxial_fov', np.float32), - ('angular_compression', np.uint16), - ('coin_samp_mode', np.uint16), - ('axial_samp_mode', np.uint16), - ('ecat_calibration_factor', np.float32), - ('calibration_unitS', np.uint16), - ('calibration_units_type', np.uint16), - ('compression_code', np.uint16), - ('study_type', '12S'), - ('patient_id', '16S'), - ('patient_name', '32S'), - ('patient_sex', '1S'), - ('patient_dexterity', '1S'), - ('patient_age', np.float32), - ('patient_height', np.float32), - ('patient_weight', np.float32), - ('patient_birth_date', np.uint32), - ('physician_name', '32S'), - ('operator_name', '32S'), - ('study_description', '32S'), - ('acquisition_type', np.uint16), - ('patient_orientation', np.uint16), - ('facility_name', '20S'), - ('num_planes', np.uint16), - ('num_frames', np.uint16), - ('num_gates', np.uint16), - ('num_bed_pos', np.uint16), - ('init_bed_position', np.float32), - ('bed_position', '15f'), - ('plane_separation', np.float32), - ('lwr_sctr_thres', np.uint16), - ('lwr_true_thres', np.uint16), - ('upr_true_thres', np.uint16), - ('user_process_code', '10S'), - ('acquisition_mode', np.uint16), - ('bin_size', np.float32), - ('branching_fraction', np.float32), - ('dose_start_time', np.uint32), - ('dosage', np.float32), - ('well_counter_corr_factor', np.float32), - ('data_units', '32S'), - ('septa_state', np.uint16), - ('fill', '12S'), -] -hdr_dtype = np.dtype(main_header_dtd) - - -subheader_dtd = [ - ('data_type', np.uint16), - ('num_dimensions', np.uint16), - ('x_dimension', np.uint16), - ('y_dimension', np.uint16), - ('z_dimension', np.uint16), - ('x_offset', np.float32), - ('y_offset', np.float32), - ('z_offset', np.float32), - ('recon_zoom', np.float32), - ('scale_factor', np.float32), - ('image_min', np.int16), - ('image_max', np.int16), - ('x_pixel_size', np.float32), - ('y_pixel_size', np.float32), - ('z_pixel_size', np.float32), - ('frame_duration', np.uint32), - ('frame_start_time', np.uint32), - ('filter_code', np.uint16), - ('x_resolution', np.float32), - ('y_resolution', np.float32), - ('z_resolution', np.float32), - ('num_r_elements', np.float32), - ('num_angles', np.float32), - ('z_rotation_angle', np.float32), - ('decay_corr_fctr', np.float32), - ('corrections_applied', np.uint32), - ('gate_duration', np.uint32), - ('r_wave_offset', np.uint32), - ('num_accepted_beats', np.uint32), - ('filter_cutoff_frequency', np.float32), - ('filter_resolution', np.float32), - ('filter_ramp_slope', np.float32), - ('filter_order', np.uint16), - ('filter_scatter_fraction', np.float32), - ('filter_scatter_slope', np.float32), - ('annotation', '40S'), - ('mt_1_1', np.float32), - ('mt_1_2', np.float32), - ('mt_1_3', np.float32), - ('mt_2_1', np.float32), - ('mt_2_2', np.float32), - ('mt_2_3', np.float32), - ('mt_3_1', np.float32), - ('mt_3_2', np.float32), - ('mt_3_3', np.float32), - ('rfilter_cutoff', np.float32), - ('rfilter_resolution', np.float32), - ('rfilter_code', np.uint16), - ('rfilter_order', np.uint16), - ('zfilter_cutoff', np.float32), - ('zfilter_resolution', np.float32), - ('zfilter_code', np.uint16), - ('zfilter_order', np.uint16), - ('mt_4_1', np.float32), - ('mt_4_2', np.float32), - ('mt_4_3', np.float32), - ('scatter_type', np.uint16), - ('recon_type', np.uint16), - ('recon_views', np.uint16), - ('fill', '174S'), - ('fill2', '96S'), -] -subhdr_dtype = np.dtype(subheader_dtd) - -# Ecat Data Types -# See: -# http://www.turkupetcentre.net/software/libdoc/libtpcimgio/ecat7_8h_source.html#l00060 -# and: -# http://www.turkupetcentre.net/software/libdoc/libtpcimgio/ecat7r_8c_source.html#l00717 -_dtdefs = ( # code, name, equivalent dtype - (1, 'ECAT7_BYTE', np.uint8), - # Byte signed? https://github.com/nipy/nibabel/pull/302/files#r28275780 - (2, 'ECAT7_VAXI2', np.int16), - (3, 'ECAT7_VAXI4', np.int32), - (4, 'ECAT7_VAXR4', np.float32), - (5, 'ECAT7_IEEER4', np.float32), - (6, 'ECAT7_SUNI2', np.int16), - (7, 'ECAT7_SUNI4', np.int32), -) -data_type_codes = make_dt_codes(_dtdefs) - - -# Matrix File Types -ft_defs = ( # code, name - (0, 'ECAT7_UNKNOWN'), - (1, 'ECAT7_2DSCAN'), - (2, 'ECAT7_IMAGE16'), - (3, 'ECAT7_ATTEN'), - (4, 'ECAT7_2DNORM'), - (5, 'ECAT7_POLARMAP'), - (6, 'ECAT7_VOLUME8'), - (7, 'ECAT7_VOLUME16'), - (8, 'ECAT7_PROJ'), - (9, 'ECAT7_PROJ16'), - (10, 'ECAT7_IMAGE8'), - (11, 'ECAT7_3DSCAN'), - (12, 'ECAT7_3DSCAN8'), - (13, 'ECAT7_3DNORM'), - (14, 'ECAT7_3DSCANFIT'), -) -file_type_codes = dict(ft_defs) - -patient_orient_defs = ( # code, description - (0, 'ECAT7_Feet_First_Prone'), - (1, 'ECAT7_Head_First_Prone'), - (2, 'ECAT7_Feet_First_Supine'), - (3, 'ECAT7_Head_First_Supine'), - (4, 'ECAT7_Feet_First_Decubitus_Right'), - (5, 'ECAT7_Head_First_Decubitus_Right'), - (6, 'ECAT7_Feet_First_Decubitus_Left'), - (7, 'ECAT7_Head_First_Decubitus_Left'), - (8, 'ECAT7_Unknown_Orientation'), -) -patient_orient_codes = dict(patient_orient_defs) - -# Indexes from the patient_orient_defs structure defined above for the -# neurological and radiological viewing conventions -patient_orient_radiological = [0, 2, 4, 6] -patient_orient_neurological = [1, 3, 5, 7] - - -class EcatHeader(WrapStruct, SpatialHeader): - """Class for basic Ecat PET header - - Sub-parts of standard Ecat File - - * main header - * matrix list - which lists the information for each frame collected (can have 1 to many - frames) - * subheaders specific to each frame with possibly-variable sized data - blocks - - This just reads the main Ecat Header, it does not load the data or read the - mlist or any sub headers - """ - - template_dtype = hdr_dtype - _ft_codes = file_type_codes - _patient_orient_codes = patient_orient_codes - - def __init__(self, binaryblock=None, endianness=None, check=True): - """Initialize Ecat header from bytes object - - Parameters - ---------- - binaryblock : {None, bytes} optional - binary block to set into header, By default, None in which case we - insert default empty header block - endianness : {None, '<', '>', other endian code}, optional - endian code of binary block, If None, guess endianness - from the data - check : {True, False}, optional - Whether to check and fix header for errors. No checks currently - implemented, so value has no effect. - """ - super().__init__(binaryblock, endianness, check) - - @classmethod - def guessed_endian(klass, hdr): - """Guess endian from MAGIC NUMBER value of header data""" - if not hdr['sw_version'] == 74: - return swapped_code - else: - return native_code - - @classmethod - def default_structarr(klass, endianness=None): - """Return header data for empty header with given endianness""" - hdr_data = super().default_structarr(endianness) - hdr_data['magic_number'] = 'MATRIX72' - hdr_data['sw_version'] = 74 - hdr_data['num_frames'] = 0 - hdr_data['file_type'] = 0 # Unknown - hdr_data['ecat_calibration_factor'] = 1.0 # scale factor - return hdr_data - - def get_data_dtype(self): - """Get numpy dtype for data from header""" - raise NotImplementedError('dtype is only valid from subheaders') - - def get_patient_orient(self): - """gets orientation of patient based on code stored - in header, not always reliable - """ - code = self._structarr['patient_orientation'].item() - if code not in self._patient_orient_codes: - raise KeyError(f'Ecat Orientation CODE {code} not recognized') - return self._patient_orient_codes[code] - - def get_filetype(self): - """Type of ECAT Matrix File from code stored in header""" - code = self._structarr['file_type'].item() - if code not in self._ft_codes: - raise KeyError(f'Ecat Filetype CODE {code} not recognized') - return self._ft_codes[code] - - @classmethod - def _get_checks(klass): - """Return sequence of check functions for this class""" - return () - - -def read_mlist(fileobj, endianness): - """read (nframes, 4) matrix list array from `fileobj` - - Parameters - ---------- - fileobj : file-like - an open file-like object implementing ``seek`` and ``read`` - - Returns - ------- - mlist : (nframes, 4) ndarray - matrix list is an array with ``nframes`` rows and columns: - - * 0: Matrix identifier (frame number) - * 1: matrix data start block number (subheader followed by image data) - * 2: Last block number of matrix (image) data - * 3: Matrix status - - * 1: hxists - rw - * 2: exists - ro - * 3: matrix deleted - - Notes - ----- - A block is 512 bytes. - - ``block_no`` in the code below is 1-based. block 1 is the main header, - and the mlist blocks start at block number 2. - - The 512 bytes in an mlist block contain 32 rows of the int32 (nframes, - 4) mlist matrix. - - The first row of these 32 looks like a special row. The 4 values appear - to be (respectively): - - * not sure - maybe negative number of mlist rows (out of 31) that are - blank and not used in this block. Called `nfree` but unused in CTI - code; - * block_no - of next set of mlist entries or 2 if no more entries. We also - allow 1 or 0 to signal no more entries; - * . Called `prvblk` in CTI code, so maybe previous block no; - * n_rows - number of mlist rows in this block (between ?0 and 31) (called - `nused` in CTI code). - """ - dt = np.dtype(np.int32) - if endianness is not native_code: - dt = dt.newbyteorder(endianness) - mlists = [] - mlist_index = 0 - mlist_block_no = 2 # 1-based indexing, block with first mlist - while True: - # Read block containing mlist entries - fileobj.seek((mlist_block_no - 1) * BLOCK_SIZE) # fix 1-based indexing - dat = fileobj.read(BLOCK_SIZE) - rows = np.ndarray(shape=(32, 4), dtype=dt, buffer=dat) - # First row special, points to next mlist entries if present - n_unused, mlist_block_no, _, n_rows = rows[0] - if not (n_unused + n_rows) == 31: # Some error condition here? - mlist = [] - return mlist - # Use all but first housekeeping row - mlists.append(rows[1 : n_rows + 1]) - mlist_index += n_rows - if mlist_block_no <= 2: # should block_no in (1, 2) be an error? - break - return np.vstack(mlists) - - -def get_frame_order(mlist): - """Returns the order of the frames stored in the file - Sometimes Frames are not stored in the file in - chronological order, this can be used to extract frames - in correct order - - Returns - ------- - id_dict: dict mapping frame number -> [mlist_row, mlist_id] - - (where mlist id is value in the first column of the mlist matrix ) - - Examples - -------- - >>> import os - >>> import nibabel as nib - >>> nibabel_dir = os.path.dirname(nib.__file__) - >>> from nibabel import ecat - >>> ecat_file = os.path.join(nibabel_dir,'tests','data','tinypet.v') - >>> img = ecat.load(ecat_file) - >>> mlist = img.get_mlist() - >>> get_frame_order(mlist) - {0: [0, 16842758]} - """ - ids = mlist[:, 0].copy() - n_valid = np.sum(ids > 0) - ids[ids <= 0] = ids.max() + 1 # put invalid frames at end after sort - valid_order = np.argsort(ids) - if not all(valid_order == sorted(valid_order)): - # raise UserWarning if Frames stored out of order - warnings.warn_explicit( - f'Frames stored out of order; true order = {valid_order}\n' - 'frames will be accessed in order STORED, NOT true order', - UserWarning, - 'ecat', - 0, - ) - id_dict = {} - for i in range(n_valid): - id_dict[i] = [valid_order[i], ids[valid_order[i]]] - return id_dict - - -def get_series_framenumbers(mlist): - """Returns framenumber of data as it was collected, - as part of a series; not just the order of how it was - stored in this or across other files - - For example, if the data is split between multiple files - this should give you the true location of this frame as - collected in the series - (Frames are numbered starting at ONE (1) not Zero) - - Returns - ------- - frame_dict: dict mapping order_stored -> frame in series - where frame in series counts from 1; [1,2,3,4...] - - Examples - -------- - >>> import os - >>> import nibabel as nib - >>> nibabel_dir = os.path.dirname(nib.__file__) - >>> from nibabel import ecat - >>> ecat_file = os.path.join(nibabel_dir,'tests','data','tinypet.v') - >>> img = ecat.load(ecat_file) - >>> mlist = img.get_mlist() - >>> get_series_framenumbers(mlist) - {0: 1} - """ - nframes = len(mlist) - frames_order = get_frame_order(mlist) - mlist_nframes = len(frames_order) - trueframenumbers = np.arange(nframes - mlist_nframes, nframes) - frame_dict = {} - for frame_stored, (true_order, _) in frames_order.items(): - # frame as stored in file -> true number in series - try: - frame_dict[frame_stored] = trueframenumbers[true_order] + 1 - except IndexError: - raise OSError('Error in header or mlist order unknown') - return frame_dict - - -def read_subheaders(fileobj, mlist, endianness): - """Retrieve all subheaders and return list of subheader recarrays - - Parameters - ---------- - fileobj : file-like - implementing ``read`` and ``seek`` - mlist : (nframes, 4) ndarray - Columns are: - * 0 - Matrix identifier. - * 1 - subheader block number - * 2 - Last block number of matrix data block. - * 3 - Matrix status - endianness : {'<', '>'} - little / big endian code - - Returns - ------- - subheaders : list - List of subheader structured arrays - """ - subheaders = [] - dt = subhdr_dtype - if endianness is not native_code: - dt = dt.newbyteorder(endianness) - for mat_id, sh_blkno, sh_last_blkno, mat_stat in mlist: - if sh_blkno == 0: - break - offset = (sh_blkno - 1) * BLOCK_SIZE - fileobj.seek(offset) - tmpdat = fileobj.read(BLOCK_SIZE) - sh = np.ndarray(shape=(), dtype=dt, buffer=tmpdat) - subheaders.append(sh) - return subheaders - - -class EcatSubHeader: - _subhdrdtype = subhdr_dtype - _data_type_codes = data_type_codes - - def __init__(self, hdr, mlist, fileobj): - """parses the subheaders in the ecat (.v) file - there is one subheader for each frame in the ecat file - - Parameters - ---------- - hdr : EcatHeader - ECAT main header - mlist : array shape (N, 4) - Matrix list - fileobj : ECAT file .v fileholder or file object - with read, seek methods - """ - self._header = hdr - self.endianness = hdr.endianness - self._mlist = mlist - self.fileobj = fileobj - self.subheaders = read_subheaders(fileobj, mlist, hdr.endianness) - - def get_shape(self, frame=0): - """returns shape of given frame""" - subhdr = self.subheaders[frame] - x = subhdr['x_dimension'].item() - y = subhdr['y_dimension'].item() - z = subhdr['z_dimension'].item() - return x, y, z - - def get_nframes(self): - """returns number of frames""" - framed = get_frame_order(self._mlist) - return len(framed) - - def _check_affines(self): - """checks if all affines are equal across frames""" - nframes = self.get_nframes() - if nframes == 1: - return True - affs = [self.get_frame_affine(i) for i in range(nframes)] - if affs: - i = iter(affs) - first = next(i) - for item in i: - if not np.allclose(first, item): - return False - return True - - def get_frame_affine(self, frame=0): - """returns best affine for given frame of data""" - subhdr = self.subheaders[frame] - x_off = subhdr['x_offset'] - y_off = subhdr['y_offset'] - z_off = subhdr['z_offset'] - - zooms = self.get_zooms(frame=frame) - - dims = self.get_shape(frame) - # get translations from center of image - origin_offset = (np.array(dims) - 1) / 2.0 - aff = np.diag(zooms) - aff[:3, -1] = -origin_offset * zooms[:-1] + np.array([x_off, y_off, z_off]) - return aff - - def get_zooms(self, frame=0): - """returns zooms ...pixdims""" - subhdr = self.subheaders[frame] - x_zoom = subhdr['x_pixel_size'] * 10 - y_zoom = subhdr['y_pixel_size'] * 10 - z_zoom = subhdr['z_pixel_size'] * 10 - return (x_zoom, y_zoom, z_zoom, 1) - - def _get_data_dtype(self, frame): - dtcode = self.subheaders[frame]['data_type'].item() - return self._data_type_codes.dtype[dtcode] - - def _get_frame_offset(self, frame=0): - return int(self._mlist[frame][1] * BLOCK_SIZE) - - def _get_oriented_data(self, raw_data, orientation=None): - """ - Get data oriented following ``patient_orientation`` header field. If - the ``orientation`` parameter is given, return data according to this - orientation. - - :param raw_data: Numpy array containing the raw data - :param orientation: None (default), 'neurological' or 'radiological' - :rtype: Numpy array containing the oriented data - """ - if orientation is None: - orientation = self._header['patient_orientation'] - elif orientation == 'neurological': - orientation = patient_orient_neurological[0] - elif orientation == 'radiological': - orientation = patient_orient_radiological[0] - else: - raise ValueError('orientation should be None, neurological or radiological') - - if orientation in patient_orient_neurological: - raw_data = raw_data[::-1, ::-1, ::-1] - elif orientation in patient_orient_radiological: - raw_data = raw_data[::, ::-1, ::-1] - - return raw_data - - def raw_data_from_fileobj(self, frame=0, orientation=None): - """ - Get raw data from file object. - - :param frame: Time frame index from where to fetch data - :param orientation: None (default), 'neurological' or 'radiological' - :rtype: Numpy array containing (possibly oriented) raw data - - .. seealso:: data_from_fileobj - """ - dtype = self._get_data_dtype(frame) - if self._header.endianness is not native_code: - dtype = dtype.newbyteorder(self._header.endianness) - shape = self.get_shape(frame) - offset = self._get_frame_offset(frame) - fid_obj = self.fileobj - raw_data = array_from_file(shape, dtype, fid_obj, offset=offset) - raw_data = self._get_oriented_data(raw_data, orientation) - return raw_data - - def data_from_fileobj(self, frame=0, orientation=None): - """ - Read scaled data from file for a given frame - - :param frame: Time frame index from where to fetch data - :param orientation: None (default), 'neurological' or 'radiological' - :rtype: Numpy array containing (possibly oriented) raw data - - .. seealso:: raw_data_from_fileobj - """ - header = self._header - subhdr = self.subheaders[frame] - raw_data = self.raw_data_from_fileobj(frame, orientation) - # Scale factors have to be set to scalars to force scalar upcasting - data = raw_data * header['ecat_calibration_factor'].item() - data = data * subhdr['scale_factor'].item() - return data - - -class EcatImageArrayProxy: - """Ecat implementation of array proxy protocol - - The array proxy allows us to freeze the passed fileobj and - header such that it returns the expected data array. - """ - - def __init__(self, subheader): - self._subheader = subheader - self._data = None - x, y, z = subheader.get_shape() - nframes = subheader.get_nframes() - self._shape = (x, y, z, nframes) - - @property - def shape(self): - return self._shape - - @property - def ndim(self): - return len(self.shape) - - @property - def is_proxy(self): - return True - - def __array__(self, dtype=None): - """Read of data from file - - This reads ALL FRAMES into one array, can be memory expensive. - - If you want to read only some slices, use the slicing syntax - (``__getitem__``) below, or ``subheader.data_from_fileobj(frame)`` - - Parameters - ---------- - dtype : numpy dtype specifier, optional - A numpy dtype specifier specifying the type of the returned array. - - Returns - ------- - array - Scaled image data with type `dtype`. - """ - # dtype=None is interpreted as float64 - data = np.empty(self.shape) - frame_mapping = get_frame_order(self._subheader._mlist) - for i in sorted(frame_mapping): - data[:, :, :, i] = self._subheader.data_from_fileobj(frame_mapping[i][0]) - if dtype is not None: - data = data.astype(dtype, copy=False) - return data - - def __getitem__(self, sliceobj): - """Return slice `sliceobj` from ECAT data, optimizing if possible""" - sliceobj = canonical_slicers(sliceobj, self.shape) - # Indices into sliceobj referring to image axes - ax_inds = [i for i, obj in enumerate(sliceobj) if obj is not None] - assert len(ax_inds) == len(self.shape) - frame_mapping = get_frame_order(self._subheader._mlist) - # Analyze index for 4th axis - slice3 = sliceobj[ax_inds[3]] - # We will load volume by volume. Make slicer into volume by dropping - # index over the volume axis - in_slicer = sliceobj[: ax_inds[3]] + sliceobj[ax_inds[3] + 1 :] - # int index for 4th axis, load one slice - if isinstance(slice3, Integral): - data = self._subheader.data_from_fileobj(frame_mapping[slice3][0]) - return data[in_slicer] - # slice axis for 4th axis, we will iterate over slices - out_shape = predict_shape(sliceobj, self.shape) - out_data = np.empty(out_shape) - # Slice into output data with out_slicer - out_slicer = [slice(None)] * len(out_shape) - # Work out axis corresponding to volume in output - in2out_ind = slice2outax(len(self.shape), sliceobj)[3] - # Iterate over specified 4th axis indices - for i in list(range(self.shape[3]))[slice3]: - data = self._subheader.data_from_fileobj(frame_mapping[i][0]) - out_slicer[in2out_ind] = i - out_data[tuple(out_slicer)] = data[in_slicer] - return out_data - - -class EcatImage(SpatialImage): - """Class returns a list of Ecat images, with one image(hdr/data) per frame""" - - header_class = EcatHeader - subheader_class = EcatSubHeader - valid_exts = ('.v',) - files_types = (('image', '.v'), ('header', '.v')) - - header: EcatHeader - _subheader: EcatSubHeader - - ImageArrayProxy = EcatImageArrayProxy - - def __init__(self, dataobj, affine, header, subheader, mlist, extra=None, file_map=None): - """Initialize Image - - The image is a combination of - (array, affine matrix, header, subheader, mlist) - with optional meta data in `extra`, and filename / file-like objects - contained in the `file_map`. - - Parameters - ---------- - dataobj : array-like - image data - affine : None or (4,4) array-like - homogeneous affine giving relationship between voxel coords and - world coords. - header : None or header instance - meta data for this image format - subheader : None or subheader instance - meta data for each sub-image for frame in the image - mlist : None or array - Matrix list array giving offset and order of data in file - extra : None or mapping, optional - metadata associated with this image that cannot be - stored in header or subheader - file_map : mapping, optional - mapping giving file information for this image format - - Examples - -------- - >>> import os - >>> import nibabel as nib - >>> nibabel_dir = os.path.dirname(nib.__file__) - >>> from nibabel import ecat - >>> ecat_file = os.path.join(nibabel_dir,'tests','data','tinypet.v') - >>> img = ecat.load(ecat_file) - >>> frame0 = img.get_frame(0) - >>> frame0.shape == (10, 10, 3) - True - >>> data4d = img.get_fdata() - >>> data4d.shape == (10, 10, 3, 1) - True - """ - self._subheader = subheader - self._mlist = mlist - self._dataobj = dataobj - if affine is not None: - # Check that affine is array-like 4,4. Maybe this is too strict at - # this abstract level, but so far I think all image formats we know - # do need 4,4. - affine = np.array(affine, dtype=np.float64, copy=True) - if not affine.shape == (4, 4): - raise ValueError('Affine should be shape 4,4') - self._affine = affine - if extra is None: - extra = {} - self.extra = extra - self._header = header - if file_map is None: - file_map = self.__class__.make_file_map() - self.file_map = file_map - self._data_cache = None - self._fdata_cache = None - - @property - def affine(self): - if not self._subheader._check_affines(): - warnings.warn( - 'Affines different across frames, loading affine from FIRST frame', UserWarning - ) - return self._affine - - def get_frame_affine(self, frame): - """returns 4X4 affine""" - return self._subheader.get_frame_affine(frame=frame) - - def get_frame(self, frame, orientation=None): - """ - Get full volume for a time frame - - :param frame: Time frame index from where to fetch data - :param orientation: None (default), 'neurological' or 'radiological' - :rtype: Numpy array containing (possibly oriented) raw data - """ - return self._subheader.data_from_fileobj(frame, orientation) - - def get_data_dtype(self, frame): - subhdr = self._subheader - dt = subhdr._get_data_dtype(frame) - return dt - - @property - def shape(self): - x, y, z = self._subheader.get_shape() - nframes = self._subheader.get_nframes() - return (x, y, z, nframes) - - def get_mlist(self): - """get access to the mlist""" - return self._mlist - - def get_subheaders(self): - """get access to subheaders""" - return self._subheader - - @staticmethod - def _get_fileholders(file_map): - """returns files specific to header and image of the image - for ecat .v this is the same image file - - Returns - ------- - header : file holding header data - image : file holding image data - """ - return file_map['header'], file_map['image'] - - @classmethod - def from_file_map(klass, file_map, *, mmap=True, keep_file_open=None): - """class method to create image from mapping - specified in file_map - """ - hdr_file, img_file = klass._get_fileholders(file_map) - # note header and image are in same file - hdr_fid = hdr_file.get_prepare_fileobj(mode='rb') - header = klass.header_class.from_fileobj(hdr_fid) - hdr_copy = header.copy() - # LOAD MLIST - mlist = np.zeros((header['num_frames'], 4), dtype=np.int32) - mlist_data = read_mlist(hdr_fid, hdr_copy.endianness) - mlist[: len(mlist_data)] = mlist_data - # LOAD SUBHEADERS - subheaders = klass.subheader_class(hdr_copy, mlist, hdr_fid) - # LOAD DATA - # Class level ImageArrayProxy - data = klass.ImageArrayProxy(subheaders) - # Get affine - if not subheaders._check_affines(): - warnings.warn( - 'Affines different across frames, loading affine from FIRST frame', UserWarning - ) - aff = subheaders.get_frame_affine() - img = klass(data, aff, header, subheaders, mlist, extra=None, file_map=file_map) - return img - - def _get_empty_dir(self): - """ - Get empty directory entry of the form - [numAvail, nextDir, previousDir, numUsed] - """ - return np.array([31, 2, 0, 0], dtype=np.int32) - - def _write_data(self, data, stream, pos, dtype=None, endianness=None): - """ - Write data to ``stream`` using an array_writer - - :param data: Numpy array containing the dat - :param stream: The file-like object to write the data to - :param pos: The position in the stream to write the data to - :param endianness: Endianness code of the data to write - """ - if dtype is None: - dtype = data.dtype - - if endianness is None: - endianness = native_code - - stream.seek(pos) - make_array_writer(data.view(data.dtype.newbyteorder(endianness)), dtype).to_fileobj(stream) - - def to_file_map(self, file_map=None): - """Write ECAT7 image to `file_map` or contained ``self.file_map`` - - The format consist of: - - - A main header (512L) with dictionary entries in the form - [numAvail, nextDir, previousDir, numUsed] - - For every frame (3D volume in 4D data) - - A subheader (size = frame_offset) - - Frame data (3D volume) - """ - if file_map is None: - file_map = self.file_map - - # It appears to be necessary to load the data before saving even if the - # data itself is not used. - self.get_fdata() - hdr = self.header - mlist = self._mlist - subheaders = self.get_subheaders() - dir_pos = 512 - entry_pos = dir_pos + 16 # 528 - current_dir = self._get_empty_dir() - - hdr_fh, img_fh = self._get_fileholders(file_map) - hdrf = hdr_fh.get_prepare_fileobj(mode='wb') - imgf = hdrf - - # Write main header - hdr.write_to(hdrf) - - # Write every frames - for index in range(self.header['num_frames']): - # Move to subheader offset - frame_offset = subheaders._get_frame_offset(index) - 512 - imgf.seek(frame_offset) - - # Write subheader - subhdr = subheaders.subheaders[index] - imgf.write(subhdr.tobytes()) - - # Seek to the next image block - pos = imgf.tell() - imgf.seek(pos + 2) - - # Get frame - image = self._subheader.raw_data_from_fileobj(index) - - # Write frame images - self._write_data(image, imgf, pos + 2, endianness='>') - - # Move to dictionary offset and write dictionary entry - self._write_data(mlist[index], imgf, entry_pos, endianness='>') - - entry_pos = entry_pos + 16 - - current_dir[0] = current_dir[0] - 1 - current_dir[3] = current_dir[3] + 1 - - # Create a new directory is previous one is full - if current_dir[0] == 0: - # self._write_dir(current_dir, imgf, dir_pos) - self._write_data(current_dir, imgf, dir_pos) - current_dir = self._get_empty_dir() - current_dir[3] = dir_pos / 512 - dir_pos = mlist[index][2] + 1 - entry_pos = dir_pos + 16 - - tmp_avail = current_dir[0] - tmp_used = current_dir[3] - - # Fill directory with empty data until directory is full - while current_dir[0] > 0: - entry_pos = dir_pos + 16 + (16 * current_dir[3]) - self._write_data(np.zeros(4, dtype=np.int32), imgf, entry_pos) - current_dir[0] = current_dir[0] - 1 - current_dir[3] = current_dir[3] + 1 - - current_dir[0] = tmp_avail - current_dir[3] = tmp_used - - # Write directory index - self._write_data(current_dir, imgf, dir_pos, endianness='>') - - @classmethod - def from_image(klass, img): - raise NotImplementedError('Ecat images can only be generated from file objects') - - @classmethod - def load(klass, filespec): - return klass.from_filename(filespec) - - -load = EcatImage.load diff --git a/nibabel/environment.py b/nibabel/environment.py deleted file mode 100644 index a828ccb865..0000000000 --- a/nibabel/environment.py +++ /dev/null @@ -1,92 +0,0 @@ -# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -"""Settings from the system environment relevant to NIPY""" - -import os -from os.path import join as pjoin - - -def get_home_dir(): - """Return the closest possible equivalent to a 'home' directory. - - The path may not exist; code using this routine should not - expect the directory to exist. - - Parameters - ---------- - None - - Returns - ------- - home_dir : string - best guess at location of home directory - """ - return os.path.expanduser('~') - - -def get_nipy_user_dir(): - """Get the NIPY user directory - - This uses the logic in `get_home_dir` to find the home directory - and the adds either .nipy or _nipy to the end of the path. - - We check first in environment variable ``NIPY_USER_DIR``, otherwise - returning the default of ``/.nipy`` (Unix) or - ``/_nipy`` (Windows) - - The path may well not exist; code using this routine should not - expect the directory to exist. - - Parameters - ---------- - None - - Returns - ------- - nipy_dir : string - path to user's NIPY configuration directory - - Examples - -------- - >>> pth = get_nipy_user_dir() - - """ - try: - return os.path.abspath(os.environ['NIPY_USER_DIR']) - except KeyError: - pass - home_dir = get_home_dir() - if os.name == 'posix': - sdir = '.nipy' - else: - sdir = '_nipy' - return pjoin(home_dir, sdir) - - -def get_nipy_system_dir(): - r"""Get systemwide NIPY configuration file directory - - On posix systems this will be ``/etc/nipy``. - On Windows, the directory is less useful, but by default it will be - ``C:\etc\nipy`` - - The path may well not exist; code using this routine should not - expect the directory to exist. - - Parameters - ---------- - None - - Returns - ------- - nipy_dir : string - path to systemwide NIPY configuration directory - - Examples - -------- - >>> pth = get_nipy_system_dir() - """ - if os.name == 'nt': - return r'C:\etc\nipy' - if os.name == 'posix': - return '/etc/nipy' diff --git a/nibabel/eulerangles.py b/nibabel/eulerangles.py deleted file mode 100644 index b1d187e8c1..0000000000 --- a/nibabel/eulerangles.py +++ /dev/null @@ -1,411 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Module implementing Euler angle rotations and their conversions - -See: - -* https://en.wikipedia.org/wiki/Rotation_matrix -* https://en.wikipedia.org/wiki/Euler_angles -* http://mathworld.wolfram.com/EulerAngles.html - -See also: *Representing Attitude with Euler Angles and Quaternions: A -Reference* (2006) by James Diebel. A cached PDF link last found here: - -http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.110.5134 - -Euler's rotation theorem tells us that any rotation in 3D can be -described by 3 angles. Let's call the 3 angles the *Euler angle vector* -and call the angles in the vector :math:`alpha`, :math:`beta` and -:math:`gamma`. The vector is [ :math:`alpha`, -:math:`beta`. :math:`gamma` ] and, in this description, the order of the -parameters specifies the order in which the rotations occur (so the -rotation corresponding to :math:`alpha` is applied first). - -In order to specify the meaning of an *Euler angle vector* we need to -specify the axes around which each of the rotations corresponding to -:math:`alpha`, :math:`beta` and :math:`gamma` will occur. - -There are therefore three axes for the rotations :math:`alpha`, -:math:`beta` and :math:`gamma`; let's call them :math:`i` :math:`j`, -:math:`k`. - -Let us express the rotation :math:`alpha` around axis `i` as a 3 by 3 -rotation matrix `A`. Similarly :math:`beta` around `j` becomes 3 x 3 -matrix `B` and :math:`gamma` around `k` becomes matrix `G`. Then the -whole rotation expressed by the Euler angle vector [ :math:`alpha`, -:math:`beta`. :math:`gamma` ], `R` is given by:: - - R = np.dot(G, np.dot(B, A)) - -See http://mathworld.wolfram.com/EulerAngles.html - -The order :math:`G B A` expresses the fact that the rotations are -performed in the order of the vector (:math:`alpha` around axis `i` = -`A` first). - -To convert a given Euler angle vector to a meaningful rotation, and a -rotation matrix, we need to define: - -* the axes `i`, `j`, `k` -* whether a rotation matrix should be applied on the left of a vector to - be transformed (vectors are column vectors) or on the right (vectors - are row vectors). -* whether the rotations move the axes as they are applied (intrinsic - rotations) - compared the situation where the axes stay fixed and the - vectors move within the axis frame (extrinsic) -* the handedness of the coordinate system - -See: https://en.wikipedia.org/wiki/Rotation_matrix#Ambiguities - -We are using the following conventions: - -* axes `i`, `j`, `k` are the `z`, `y`, and `x` axes respectively. Thus - an Euler angle vector [ :math:`alpha`, :math:`beta`. :math:`gamma` ] - in our convention implies a :math:`alpha` radian rotation around the - `z` axis, followed by a :math:`beta` rotation around the `y` axis, - followed by a :math:`gamma` rotation around the `x` axis. -* the rotation matrix applies on the left, to column vectors on the - right, so if `R` is the rotation matrix, and `v` is a 3 x N matrix - with N column vectors, the transformed vector set `vdash` is given by - ``vdash = np.dot(R, v)``. -* extrinsic rotations - the axes are fixed, and do not move with the - rotations. -* a right-handed coordinate system - -The convention of rotation around ``z``, followed by rotation around -``y``, followed by rotation around ``x``, is known (confusingly) as -"xyz", pitch-roll-yaw, Cardan angles, or Tait-Bryan angles. -""" - -import math -from functools import reduce - -import numpy as np - -_FLOAT_EPS_4 = np.finfo(float).eps * 4.0 - - -def euler2mat(z=0, y=0, x=0): - """Return matrix for rotations around z, y and x axes - - Uses the z, then y, then x convention above - - Parameters - ---------- - z : scalar - Rotation angle in radians around z-axis (performed first) - y : scalar - Rotation angle in radians around y-axis - x : scalar - Rotation angle in radians around x-axis (performed last) - - Returns - ------- - M : array shape (3,3) - Rotation matrix giving same rotation as for given angles - - Examples - -------- - >>> zrot = 1.3 # radians - >>> yrot = -0.1 - >>> xrot = 0.2 - >>> M = euler2mat(zrot, yrot, xrot) - >>> M.shape == (3, 3) - True - - The output rotation matrix is equal to the composition of the - individual rotations - - >>> M1 = euler2mat(zrot) - >>> M2 = euler2mat(0, yrot) - >>> M3 = euler2mat(0, 0, xrot) - >>> composed_M = np.dot(M3, np.dot(M2, M1)) - >>> np.allclose(M, composed_M) - True - - You can specify rotations by named arguments - - >>> np.all(M3 == euler2mat(x=xrot)) - True - - When applying M to a vector, the vector should column vector to the - right of M. If the right hand side is a 2D array rather than a - vector, then each column of the 2D array represents a vector. - - >>> vec = np.array([1, 0, 0]).reshape((3,1)) - >>> v2 = np.dot(M, vec) - >>> vecs = np.array([[1, 0, 0],[0, 1, 0]]).T # giving 3x2 array - >>> vecs2 = np.dot(M, vecs) - - Rotations are counter-clockwise. - - >>> zred = np.dot(euler2mat(z=np.pi/2), np.eye(3)) - >>> np.allclose(zred, [[0, -1, 0],[1, 0, 0], [0, 0, 1]]) - True - >>> yred = np.dot(euler2mat(y=np.pi/2), np.eye(3)) - >>> np.allclose(yred, [[0, 0, 1],[0, 1, 0], [-1, 0, 0]]) - True - >>> xred = np.dot(euler2mat(x=np.pi/2), np.eye(3)) - >>> np.allclose(xred, [[1, 0, 0],[0, 0, -1], [0, 1, 0]]) - True - - Notes - ----- - The direction of rotation is given by the right-hand rule (orient - the thumb of the right hand along the axis around which the rotation - occurs, with the end of the thumb at the positive end of the axis; - curl your fingers; the direction your fingers curl is the direction - of rotation). Therefore, the rotations are counterclockwise if - looking along the axis of rotation from positive to negative. - """ - Ms = [] - if z: - cosz = math.cos(z) - sinz = math.sin(z) - Ms.append(np.array([[cosz, -sinz, 0], [sinz, cosz, 0], [0, 0, 1]])) - if y: - cosy = math.cos(y) - siny = math.sin(y) - Ms.append(np.array([[cosy, 0, siny], [0, 1, 0], [-siny, 0, cosy]])) - if x: - cosx = math.cos(x) - sinx = math.sin(x) - Ms.append(np.array([[1, 0, 0], [0, cosx, -sinx], [0, sinx, cosx]])) - if Ms: - return reduce(np.dot, Ms[::-1]) - return np.eye(3) - - -def mat2euler(M, cy_thresh=None): - """Discover Euler angle vector from 3x3 matrix - - Uses the conventions above. - - Parameters - ---------- - M : array-like, shape (3,3) - cy_thresh : None or scalar, optional - threshold below which to give up on straightforward arctan for - estimating x rotation. If None (default), estimate from - precision of input. - - Returns - ------- - z : scalar - y : scalar - x : scalar - Rotations in radians around z, y, x axes, respectively - - Notes - ----- - If there was no numerical error, the routine could be derived using - Sympy expression for z then y then x rotation matrix, which is:: - - [ cos(y)*cos(z), -cos(y)*sin(z), sin(y)], - [cos(x)*sin(z) + cos(z)*sin(x)*sin(y), cos(x)*cos(z) - sin(x)*sin(y)*sin(z), -cos(y)*sin(x)], - [sin(x)*sin(z) - cos(x)*cos(z)*sin(y), cos(z)*sin(x) + cos(x)*sin(y)*sin(z), cos(x)*cos(y)] - - with the obvious derivations for z, y, and x - - z = atan2(-r12, r11) - y = asin(r13) - x = atan2(-r23, r33) - - Problems arise when cos(y) is close to zero, because both of:: - - z = atan2(cos(y)*sin(z), cos(y)*cos(z)) - x = atan2(cos(y)*sin(x), cos(x)*cos(y)) - - will be close to atan2(0, 0), and highly unstable. - - The ``cy`` fix for numerical instability below is from: *Graphics - Gems IV*, Paul Heckbert (editor), Academic Press, 1994, ISBN: - 0123361559. Specifically it comes from EulerAngles.c by Ken - Shoemake, and deals with the case where cos(y) is close to zero: - - See: http://www.graphicsgems.org/ - - The code appears to be licensed (from the website) as "can be used - without restrictions". - """ - M = np.asarray(M) - if cy_thresh is None: - try: - cy_thresh = np.finfo(M.dtype).eps * 4 - except ValueError: - cy_thresh = _FLOAT_EPS_4 - r11, r12, r13, r21, r22, r23, r31, r32, r33 = M.flat - # cy: sqrt((cos(y)*cos(z))**2 + (cos(x)*cos(y))**2) - cy = math.sqrt(r33 * r33 + r23 * r23) - if cy > cy_thresh: # cos(y) not close to zero, standard form - z = math.atan2(-r12, r11) # atan2(cos(y)*sin(z), cos(y)*cos(z)) - y = math.atan2(r13, cy) # atan2(sin(y), cy) - x = math.atan2(-r23, r33) # atan2(cos(y)*sin(x), cos(x)*cos(y)) - else: # cos(y) (close to) zero, so x -> 0.0 (see above) - # so r21 -> sin(z), r22 -> cos(z) and - z = math.atan2(r21, r22) - y = math.atan2(r13, cy) # atan2(sin(y), cy) - x = 0.0 - return z, y, x - - -def euler2quat(z=0, y=0, x=0): - """Return quaternion corresponding to these Euler angles - - Uses the z, then y, then x convention above - - Parameters - ---------- - z : scalar - Rotation angle in radians around z-axis (performed first) - y : scalar - Rotation angle in radians around y-axis - x : scalar - Rotation angle in radians around x-axis (performed last) - - Returns - ------- - quat : array shape (4,) - Quaternion in w, x, y z (real, then vector) format - - Notes - ----- - We can derive this formula in Sympy using: - - 1. Formula giving quaternion corresponding to rotation of theta radians - about arbitrary axis: - http://mathworld.wolfram.com/EulerParameters.html - 2. Generated formulae from 1.) for quaternions corresponding to - theta radians rotations about ``x, y, z`` axes - 3. Apply quaternion multiplication formula - - https://en.wikipedia.org/wiki/Quaternions#Hamilton_product - to - formulae from 2.) to give formula for combined rotations. - """ - z = z / 2.0 - y = y / 2.0 - x = x / 2.0 - cz = math.cos(z) - sz = math.sin(z) - cy = math.cos(y) - sy = math.sin(y) - cx = math.cos(x) - sx = math.sin(x) - return np.array( - [ - cx * cy * cz - sx * sy * sz, - cx * sy * sz + cy * cz * sx, - cx * cz * sy - sx * cy * sz, - cx * cy * sz + sx * cz * sy, - ] - ) - - -def quat2euler(q): - """Return Euler angles corresponding to quaternion `q` - - Parameters - ---------- - q : 4 element sequence - w, x, y, z of quaternion - - Returns - ------- - z : scalar - Rotation angle in radians around z-axis (performed first) - y : scalar - Rotation angle in radians around y-axis - x : scalar - Rotation angle in radians around x-axis (performed last) - - Notes - ----- - It's possible to reduce the amount of calculation a little, by - combining parts of the ``quat2mat`` and ``mat2euler`` functions, but - the reduction in computation is small, and the code repetition is - large. - """ - # delayed import to avoid cyclic dependencies - from . import quaternions as nq - - return mat2euler(nq.quat2mat(q)) - - -def euler2angle_axis(z=0, y=0, x=0): - """Return angle, axis corresponding to these Euler angles - - Uses the z, then y, then x convention above - - Parameters - ---------- - z : scalar - Rotation angle in radians around z-axis (performed first) - y : scalar - Rotation angle in radians around y-axis - x : scalar - Rotation angle in radians around x-axis (performed last) - - Returns - ------- - theta : scalar - angle of rotation - vector : array shape (3,) - axis around which rotation occurs - - Examples - -------- - >>> theta, vec = euler2angle_axis(0, 1.5, 0) - >>> print(theta) - 1.5 - >>> np.allclose(vec, [0, 1, 0]) - True - """ - # delayed import to avoid cyclic dependencies - from . import quaternions as nq - - return nq.quat2angle_axis(euler2quat(z, y, x)) - - -def angle_axis2euler(theta, vector, is_normalized=False): - """Convert angle, axis pair to Euler angles - - Parameters - ---------- - theta : scalar - angle of rotation - vector : 3 element sequence - vector specifying axis for rotation. - is_normalized : bool, optional - True if vector is already normalized (has norm of 1). Default - False - - Returns - ------- - z : scalar - y : scalar - x : scalar - Rotations in radians around z, y, x axes, respectively - - Examples - -------- - >>> z, y, x = angle_axis2euler(0, [1, 0, 0]) - >>> np.allclose((z, y, x), 0) - True - - Notes - ----- - It's possible to reduce the amount of calculation a little, by - combining parts of the ``angle_axis2mat`` and ``mat2euler`` - functions, but the reduction in computation is small, and the code - repetition is large. - """ - # delayed import to avoid cyclic dependencies - from . import quaternions as nq - - M = nq.angle_axis2mat(theta, vector, is_normalized) - return mat2euler(M) diff --git a/nibabel/externals/__init__.py b/nibabel/externals/__init__.py deleted file mode 100644 index 0eefb918c9..0000000000 --- a/nibabel/externals/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# init for externals package -from collections import OrderedDict diff --git a/nibabel/externals/conftest.py b/nibabel/externals/conftest.py deleted file mode 100644 index 472f2f0296..0000000000 --- a/nibabel/externals/conftest.py +++ /dev/null @@ -1,25 +0,0 @@ -import pytest - -try: - from contextlib import chdir as _chdir -except ImportError: # PY310 - import os - from contextlib import contextmanager - - @contextmanager # type: ignore[no-redef] - def _chdir(path): - cwd = os.getcwd() - os.chdir(path) - try: - yield - finally: - os.chdir(cwd) - - -@pytest.fixture(autouse=True) -def chdir_tmpdir(request, tmp_path): - if request.node.__class__.__name__ == "DoctestItem": - with _chdir(tmp_path): - yield - else: - yield diff --git a/nibabel/externals/netcdf.py b/nibabel/externals/netcdf.py deleted file mode 100644 index b8d1244c0c..0000000000 --- a/nibabel/externals/netcdf.py +++ /dev/null @@ -1,1084 +0,0 @@ -""" -NetCDF reader/writer module. - -This module is used to read and create NetCDF files. NetCDF files are -accessed through the `netcdf_file` object. Data written to and from NetCDF -files are contained in `netcdf_variable` objects. Attributes are given -as member variables of the `netcdf_file` and `netcdf_variable` objects. - -This module implements the Scientific.IO.NetCDF API to read and create -NetCDF files. The same API is also used in the PyNIO and pynetcdf -modules, allowing these modules to be used interchangeably when working -with NetCDF files. - -Only NetCDF3 is supported here; for NetCDF4 see -`netCDF4-python `__, -which has a similar API. - -""" - -# TODO: -# * properly implement ``_FillValue``. -# * fix character variables. -# * implement PAGESIZE for Python 2.6? - -# The Scientific.IO.NetCDF API allows attributes to be added directly to -# instances of ``netcdf_file`` and ``netcdf_variable``. To differentiate -# between user-set attributes and instance attributes, user-set attributes -# are automatically stored in the ``_attributes`` attribute by overloading -#``__setattr__``. This is the reason why the code sometimes uses -#``obj.__dict__['key'] = value``, instead of simply ``obj.key = value``; -# otherwise the key would be inserted into userspace attributes. - - -__all__ = ['netcdf_file', 'netcdf_variable'] - - -import warnings -import weakref -from operator import mul -from platform import python_implementation - -import mmap as mm - -import numpy as np -from numpy import frombuffer, dtype, empty, array, asarray -from numpy import little_endian as LITTLE_ENDIAN -from functools import reduce - - -IS_PYPY = python_implementation() == 'PyPy' - -ABSENT = b'\x00\x00\x00\x00\x00\x00\x00\x00' -ZERO = b'\x00\x00\x00\x00' -NC_BYTE = b'\x00\x00\x00\x01' -NC_CHAR = b'\x00\x00\x00\x02' -NC_SHORT = b'\x00\x00\x00\x03' -NC_INT = b'\x00\x00\x00\x04' -NC_FLOAT = b'\x00\x00\x00\x05' -NC_DOUBLE = b'\x00\x00\x00\x06' -NC_DIMENSION = b'\x00\x00\x00\n' -NC_VARIABLE = b'\x00\x00\x00\x0b' -NC_ATTRIBUTE = b'\x00\x00\x00\x0c' -FILL_BYTE = b'\x81' -FILL_CHAR = b'\x00' -FILL_SHORT = b'\x80\x01' -FILL_INT = b'\x80\x00\x00\x01' -FILL_FLOAT = b'\x7C\xF0\x00\x00' -FILL_DOUBLE = b'\x47\x9E\x00\x00\x00\x00\x00\x00' - -TYPEMAP = {NC_BYTE: ('b', 1), - NC_CHAR: ('c', 1), - NC_SHORT: ('h', 2), - NC_INT: ('i', 4), - NC_FLOAT: ('f', 4), - NC_DOUBLE: ('d', 8)} - -FILLMAP = {NC_BYTE: FILL_BYTE, - NC_CHAR: FILL_CHAR, - NC_SHORT: FILL_SHORT, - NC_INT: FILL_INT, - NC_FLOAT: FILL_FLOAT, - NC_DOUBLE: FILL_DOUBLE} - -REVERSE = {('b', 1): NC_BYTE, - ('B', 1): NC_CHAR, - ('c', 1): NC_CHAR, - ('h', 2): NC_SHORT, - ('i', 4): NC_INT, - ('f', 4): NC_FLOAT, - ('d', 8): NC_DOUBLE, - - # these come from asarray(1).dtype.char and asarray('foo').dtype.char, - # used when getting the types from generic attributes. - ('l', 4): NC_INT, - ('S', 1): NC_CHAR} - - -class netcdf_file: - """ - A file object for NetCDF data. - - A `netcdf_file` object has two standard attributes: `dimensions` and - `variables`. The values of both are dictionaries, mapping dimension - names to their associated lengths and variable names to variables, - respectively. Application programs should never modify these - dictionaries. - - All other attributes correspond to global attributes defined in the - NetCDF file. Global file attributes are created by assigning to an - attribute of the `netcdf_file` object. - - Parameters - ---------- - filename : string or file-like - string -> filename - mode : {'r', 'w', 'a'}, optional - read-write-append mode, default is 'r' - mmap : None or bool, optional - Whether to mmap `filename` when reading. Default is True - when `filename` is a file name, False when `filename` is a - file-like object. Note that when mmap is in use, data arrays - returned refer directly to the mmapped data on disk, and the - file cannot be closed as long as references to it exist. - version : {1, 2}, optional - version of netcdf to read / write, where 1 means *Classic - format* and 2 means *64-bit offset format*. Default is 1. See - `here `__ - for more info. - maskandscale : bool, optional - Whether to automatically scale and/or mask data based on attributes. - Default is False. - - Notes - ----- - The major advantage of this module over other modules is that it doesn't - require the code to be linked to the NetCDF libraries. This module is - derived from `pupynere `_. - - NetCDF files are a self-describing binary data format. The file contains - metadata that describes the dimensions and variables in the file. More - details about NetCDF files can be found `here - `__. There - are three main sections to a NetCDF data structure: - - 1. Dimensions - 2. Variables - 3. Attributes - - The dimensions section records the name and length of each dimension used - by the variables. The variables would then indicate which dimensions it - uses and any attributes such as data units, along with containing the data - values for the variable. It is good practice to include a - variable that is the same name as a dimension to provide the values for - that axes. Lastly, the attributes section would contain additional - information such as the name of the file creator or the instrument used to - collect the data. - - When writing data to a NetCDF file, there is often the need to indicate the - 'record dimension'. A record dimension is the unbounded dimension for a - variable. For example, a temperature variable may have dimensions of - latitude, longitude and time. If one wants to add more temperature data to - the NetCDF file as time progresses, then the temperature variable should - have the time dimension flagged as the record dimension. - - In addition, the NetCDF file header contains the position of the data in - the file, so access can be done in an efficient manner without loading - unnecessary data into memory. It uses the ``mmap`` module to create - Numpy arrays mapped to the data on disk, for the same purpose. - - Note that when `netcdf_file` is used to open a file with mmap=True - (default for read-only), arrays returned by it refer to data - directly on the disk. The file should not be closed, and cannot be cleanly - closed when asked, if such arrays are alive. You may want to copy data arrays - obtained from mmapped Netcdf file if they are to be processed after the file - is closed, see the example below. - - Examples - -------- - To create a NetCDF file: - - >>> f = netcdf_file('simple.nc', 'w') - >>> f.history = 'Created for a test' - >>> f.createDimension('time', 10) - >>> time = f.createVariable('time', 'i', ('time',)) - >>> time[:] = np.arange(10) - >>> time.units = 'days since 2008-01-01' - >>> f.close() - - Note the assignment of ``arange(10)`` to ``time[:]``. Exposing the slice - of the time variable allows for the data to be set in the object, rather - than letting ``arange(10)`` overwrite the ``time`` variable. - - To read the NetCDF file we just created: - - >>> f = netcdf_file('simple.nc', 'r') - >>> print(f.history) - b'Created for a test' - >>> time = f.variables['time'] - >>> print(time.units) - b'days since 2008-01-01' - >>> print(time.shape) - (10,) - >>> print(time[-1]) - 9 - - NetCDF files, when opened read-only, return arrays that refer - directly to memory-mapped data on disk: - - >>> data = time[:] - - If the data is to be processed after the file is closed, it needs - to be copied to main memory: - - >>> data = time[:].copy() - >>> f.close() - >>> data.mean() - 4.5 - - A NetCDF file can also be used as context manager: - - >>> with netcdf_file('simple.nc', 'r') as f: - ... print(f.history) - b'Created for a test' - - """ - def __init__(self, filename, mode='r', mmap=None, version=1, - maskandscale=False): - """Initialize netcdf_file from fileobj (str or file-like).""" - if mode not in 'rwa': - raise ValueError("Mode must be either 'r', 'w' or 'a'.") - - if hasattr(filename, 'seek'): # file-like - self.fp = filename - self.filename = 'None' - if mmap is None: - mmap = False - elif mmap and not hasattr(filename, 'fileno'): - raise ValueError('Cannot use file object for mmap') - else: # maybe it's a string - self.filename = filename - omode = 'r+' if mode == 'a' else mode - self.fp = open(self.filename, '%sb' % omode) - if mmap is None: - # Mmapped files on PyPy cannot be usually closed - # before the GC runs, so it's better to use mmap=False - # as the default. - mmap = (not IS_PYPY) - - if mode != 'r': - # Cannot read write-only files - mmap = False - - self.use_mmap = mmap - self.mode = mode - self.version_byte = version - self.maskandscale = maskandscale - - self.dimensions = {} - self.variables = {} - - self._dims = [] - self._recs = 0 - self._recsize = 0 - - self._mm = None - self._mm_buf = None - if self.use_mmap: - self._mm = mm.mmap(self.fp.fileno(), 0, access=mm.ACCESS_READ) - self._mm_buf = np.frombuffer(self._mm, dtype=np.int8) - - self._attributes = {} - - if mode in 'ra': - self._read() - - def __setattr__(self, attr, value): - # Store user defined attributes in a separate dict, - # so we can save them to file later. - try: - self._attributes[attr] = value - except AttributeError: - pass - self.__dict__[attr] = value - - def close(self): - """Closes the NetCDF file.""" - if hasattr(self, 'fp') and not self.fp.closed: - try: - self.flush() - finally: - self.variables = {} - if self._mm_buf is not None: - ref = weakref.ref(self._mm_buf) - self._mm_buf = None - if ref() is None: - # self._mm_buf is gc'd, and we can close the mmap - self._mm.close() - else: - # we cannot close self._mm, since self._mm_buf is - # alive and there may still be arrays referring to it - warnings.warn(( - "Cannot close a netcdf_file opened with mmap=True, when " - "netcdf_variables or arrays referring to its data still exist. " - "All data arrays obtained from such files refer directly to " - "data on disk, and must be copied before the file can be cleanly " - "closed. (See netcdf_file docstring for more information on mmap.)" - ), category=RuntimeWarning) - self._mm = None - self.fp.close() - __del__ = close - - def __enter__(self): - return self - - def __exit__(self, type, value, traceback): - self.close() - - def createDimension(self, name, length): - """ - Adds a dimension to the Dimension section of the NetCDF data structure. - - Note that this function merely adds a new dimension that the variables can - reference. The values for the dimension, if desired, should be added as - a variable using `createVariable`, referring to this dimension. - - Parameters - ---------- - name : str - Name of the dimension (Eg, 'lat' or 'time'). - length : int - Length of the dimension. - - See Also - -------- - createVariable - - """ - if length is None and self._dims: - raise ValueError("Only first dimension may be unlimited!") - - self.dimensions[name] = length - self._dims.append(name) - - def createVariable(self, name, type, dimensions): - """ - Create an empty variable for the `netcdf_file` object, specifying its data - type and the dimensions it uses. - - Parameters - ---------- - name : str - Name of the new variable. - type : dtype or str - Data type of the variable. - dimensions : sequence of str - List of the dimension names used by the variable, in the desired order. - - Returns - ------- - variable : netcdf_variable - The newly created ``netcdf_variable`` object. - This object has also been added to the `netcdf_file` object as well. - - See Also - -------- - createDimension - - Notes - ----- - Any dimensions to be used by the variable should already exist in the - NetCDF data structure or should be created by `createDimension` prior to - creating the NetCDF variable. - - """ - shape = tuple([self.dimensions[dim] for dim in dimensions]) - shape_ = tuple([dim or 0 for dim in shape]) # replace None with 0 for NumPy - - type = dtype(type) - typecode, size = type.char, type.itemsize - if (typecode, size) not in REVERSE: - raise ValueError("NetCDF 3 does not support type %s" % type) - - data = empty(shape_, dtype=type.newbyteorder("B")) # convert to big endian always for NetCDF 3 - self.variables[name] = netcdf_variable( - data, typecode, size, shape, dimensions, - maskandscale=self.maskandscale) - return self.variables[name] - - def flush(self): - """ - Perform a sync-to-disk flush if the `netcdf_file` object is in write mode. - - See Also - -------- - sync : Identical function - - """ - if hasattr(self, 'mode') and self.mode in 'wa': - self._write() - sync = flush - - def _write(self): - self.fp.seek(0) - self.fp.write(b'CDF') - self.fp.write(array(self.version_byte, '>b').tobytes()) - - # Write headers and data. - self._write_numrecs() - self._write_dim_array() - self._write_gatt_array() - self._write_var_array() - - def _write_numrecs(self): - # Get highest record count from all record variables. - for var in self.variables.values(): - if var.isrec and len(var.data) > self._recs: - self.__dict__['_recs'] = len(var.data) - self._pack_int(self._recs) - - def _write_dim_array(self): - if self.dimensions: - self.fp.write(NC_DIMENSION) - self._pack_int(len(self.dimensions)) - for name in self._dims: - self._pack_string(name) - length = self.dimensions[name] - self._pack_int(length or 0) # replace None with 0 for record dimension - else: - self.fp.write(ABSENT) - - def _write_gatt_array(self): - self._write_att_array(self._attributes) - - def _write_att_array(self, attributes): - if attributes: - self.fp.write(NC_ATTRIBUTE) - self._pack_int(len(attributes)) - for name, values in attributes.items(): - self._pack_string(name) - self._write_att_values(values) - else: - self.fp.write(ABSENT) - - def _write_var_array(self): - if self.variables: - self.fp.write(NC_VARIABLE) - self._pack_int(len(self.variables)) - - # Sort variable names non-recs first, then recs. - def sortkey(n): - v = self.variables[n] - if v.isrec: - return (-1,) - return v._shape - variables = sorted(self.variables, key=sortkey, reverse=True) - - # Set the metadata for all variables. - for name in variables: - self._write_var_metadata(name) - # Now that we have the metadata, we know the vsize of - # each record variable, so we can calculate recsize. - self.__dict__['_recsize'] = sum([ - var._vsize for var in self.variables.values() - if var.isrec]) - # Set the data for all variables. - for name in variables: - self._write_var_data(name) - else: - self.fp.write(ABSENT) - - def _write_var_metadata(self, name): - var = self.variables[name] - - self._pack_string(name) - self._pack_int(len(var.dimensions)) - for dimname in var.dimensions: - dimid = self._dims.index(dimname) - self._pack_int(dimid) - - self._write_att_array(var._attributes) - - nc_type = REVERSE[var.typecode(), var.itemsize()] - self.fp.write(nc_type) - - if not var.isrec: - vsize = var.data.size * var.data.itemsize - vsize += -vsize % 4 - else: # record variable - try: - vsize = var.data[0].size * var.data.itemsize - except IndexError: - vsize = 0 - rec_vars = len([v for v in self.variables.values() - if v.isrec]) - if rec_vars > 1: - vsize += -vsize % 4 - self.variables[name].__dict__['_vsize'] = vsize - self._pack_int(vsize) - - # Pack a bogus begin, and set the real value later. - self.variables[name].__dict__['_begin'] = self.fp.tell() - self._pack_begin(0) - - def _write_var_data(self, name): - var = self.variables[name] - - # Set begin in file header. - the_beguine = self.fp.tell() - self.fp.seek(var._begin) - self._pack_begin(the_beguine) - self.fp.seek(the_beguine) - - # Write data. - if not var.isrec: - self.fp.write(var.data.tobytes()) - count = var.data.size * var.data.itemsize - self._write_var_padding(var, var._vsize - count) - else: # record variable - # Handle rec vars with shape[0] < nrecs. - if self._recs > len(var.data): - shape = (self._recs,) + var.data.shape[1:] - # Resize in-place does not always work since - # the array might not be single-segment - try: - var.data.resize(shape) - except ValueError: - var.__dict__['data'] = np.resize(var.data, shape).astype(var.data.dtype) - - pos0 = pos = self.fp.tell() - for rec in var.data: - # Apparently scalars cannot be converted to big endian. If we - # try to convert a ``=i4`` scalar to, say, '>i4' the dtype - # will remain as ``=i4``. - if not rec.shape and (rec.dtype.byteorder == '<' or - (rec.dtype.byteorder == '=' and LITTLE_ENDIAN)): - rec = rec.byteswap() - self.fp.write(rec.tobytes()) - # Padding - count = rec.size * rec.itemsize - self._write_var_padding(var, var._vsize - count) - pos += self._recsize - self.fp.seek(pos) - self.fp.seek(pos0 + var._vsize) - - def _write_var_padding(self, var, size): - encoded_fill_value = var._get_encoded_fill_value() - num_fills = size // len(encoded_fill_value) - self.fp.write(encoded_fill_value * num_fills) - - def _write_att_values(self, values): - if hasattr(values, 'dtype'): - nc_type = REVERSE[values.dtype.char, values.dtype.itemsize] - else: - types = [(int, NC_INT), (float, NC_FLOAT), (str, NC_CHAR)] - - # bytes index into scalars in py3k. Check for "string" types - if isinstance(values, (str, bytes)): - sample = values - else: - try: - sample = values[0] # subscriptable? - except TypeError: - sample = values # scalar - - for class_, nc_type in types: - if isinstance(sample, class_): - break - - typecode, size = TYPEMAP[nc_type] - dtype_ = '>%s' % typecode - # asarray() dies with bytes and '>c' in py3k. Change to 'S' - dtype_ = 'S' if dtype_ == '>c' else dtype_ - - values = asarray(values, dtype=dtype_) - - self.fp.write(nc_type) - - if values.dtype.char == 'S': - nelems = values.itemsize - else: - nelems = values.size - self._pack_int(nelems) - - if not values.shape and (values.dtype.byteorder == '<' or - (values.dtype.byteorder == '=' and LITTLE_ENDIAN)): - values = values.byteswap() - self.fp.write(values.tobytes()) - count = values.size * values.itemsize - self.fp.write(b'\x00' * (-count % 4)) # pad - - def _read(self): - # Check magic bytes and version - magic = self.fp.read(3) - if not magic == b'CDF': - raise TypeError("Error: %s is not a valid NetCDF 3 file" % - self.filename) - self.__dict__['version_byte'] = frombuffer(self.fp.read(1), '>b')[0] - - # Read file headers and set data. - self._read_numrecs() - self._read_dim_array() - self._read_gatt_array() - self._read_var_array() - - def _read_numrecs(self): - self.__dict__['_recs'] = self._unpack_int() - - def _read_dim_array(self): - header = self.fp.read(4) - if header not in [ZERO, NC_DIMENSION]: - raise ValueError("Unexpected header.") - count = self._unpack_int() - - for dim in range(count): - name = self._unpack_string().decode('latin1') - length = self._unpack_int() or None # None for record dimension - self.dimensions[name] = length - self._dims.append(name) # preserve order - - def _read_gatt_array(self): - for k, v in self._read_att_array().items(): - self.__setattr__(k, v) - - def _read_att_array(self): - header = self.fp.read(4) - if header not in [ZERO, NC_ATTRIBUTE]: - raise ValueError("Unexpected header.") - count = self._unpack_int() - - attributes = {} - for attr in range(count): - name = self._unpack_string().decode('latin1') - attributes[name] = self._read_att_values() - return attributes - - def _read_var_array(self): - header = self.fp.read(4) - if header not in [ZERO, NC_VARIABLE]: - raise ValueError("Unexpected header.") - - begin = 0 - dtypes = {'names': [], 'formats': []} - rec_vars = [] - count = self._unpack_int() - for var in range(count): - (name, dimensions, shape, attributes, - typecode, size, dtype_, begin_, vsize) = self._read_var() - # https://www.unidata.ucar.edu/software/netcdf/guide_toc.html - # Note that vsize is the product of the dimension lengths - # (omitting the record dimension) and the number of bytes - # per value (determined from the type), increased to the - # next multiple of 4, for each variable. If a record - # variable, this is the amount of space per record. The - # netCDF "record size" is calculated as the sum of the - # vsize's of all the record variables. - # - # The vsize field is actually redundant, because its value - # may be computed from other information in the header. The - # 32-bit vsize field is not large enough to contain the size - # of variables that require more than 2^32 - 4 bytes, so - # 2^32 - 1 is used in the vsize field for such variables. - if shape and shape[0] is None: # record variable - rec_vars.append(name) - # The netCDF "record size" is calculated as the sum of - # the vsize's of all the record variables. - self.__dict__['_recsize'] += vsize - if begin == 0: - begin = begin_ - dtypes['names'].append(name) - dtypes['formats'].append(str(shape[1:]) + dtype_) - - # Handle padding with a virtual variable. - if typecode in 'bch': - actual_size = reduce(mul, (1,) + shape[1:]) * size - padding = -actual_size % 4 - if padding: - dtypes['names'].append('_padding_%d' % var) - dtypes['formats'].append('(%d,)>b' % padding) - - # Data will be set later. - data = None - else: # not a record variable - # Calculate size to avoid problems with vsize (above) - a_size = reduce(mul, shape, 1) * size - if self.use_mmap: - data = self._mm_buf[begin_:begin_+a_size].view(dtype=dtype_) - data.shape = shape - else: - pos = self.fp.tell() - self.fp.seek(begin_) - data = frombuffer(self.fp.read(a_size), dtype=dtype_ - ).copy() - data.shape = shape - self.fp.seek(pos) - - # Add variable. - self.variables[name] = netcdf_variable( - data, typecode, size, shape, dimensions, attributes, - maskandscale=self.maskandscale) - - if rec_vars: - # Remove padding when only one record variable. - if len(rec_vars) == 1: - dtypes['names'] = dtypes['names'][:1] - dtypes['formats'] = dtypes['formats'][:1] - - # Build rec array. - if self.use_mmap: - rec_array = self._mm_buf[begin:begin+self._recs*self._recsize].view(dtype=dtypes) - rec_array.shape = (self._recs,) - else: - pos = self.fp.tell() - self.fp.seek(begin) - rec_array = frombuffer(self.fp.read(self._recs*self._recsize), - dtype=dtypes).copy() - rec_array.shape = (self._recs,) - self.fp.seek(pos) - - for var in rec_vars: - self.variables[var].__dict__['data'] = rec_array[var] - - def _read_var(self): - name = self._unpack_string().decode('latin1') - dimensions = [] - shape = [] - dims = self._unpack_int() - - for i in range(dims): - dimid = self._unpack_int() - dimname = self._dims[dimid] - dimensions.append(dimname) - dim = self.dimensions[dimname] - shape.append(dim) - dimensions = tuple(dimensions) - shape = tuple(shape) - - attributes = self._read_att_array() - nc_type = self.fp.read(4) - vsize = self._unpack_int() - begin = [self._unpack_int, self._unpack_int64][self.version_byte-1]() - - typecode, size = TYPEMAP[nc_type] - dtype_ = '>%s' % typecode - - return name, dimensions, shape, attributes, typecode, size, dtype_, begin, vsize - - def _read_att_values(self): - nc_type = self.fp.read(4) - n = self._unpack_int() - - typecode, size = TYPEMAP[nc_type] - - count = n*size - values = self.fp.read(int(count)) - self.fp.read(-count % 4) # read padding - - if typecode != 'c': - values = frombuffer(values, dtype='>%s' % typecode).copy() - if values.shape == (1,): - values = values[0] - else: - values = values.rstrip(b'\x00') - return values - - def _pack_begin(self, begin): - if self.version_byte == 1: - self._pack_int(begin) - elif self.version_byte == 2: - self._pack_int64(begin) - - def _pack_int(self, value): - self.fp.write(array(value, '>i').tobytes()) - _pack_int32 = _pack_int - - def _unpack_int(self): - return int(frombuffer(self.fp.read(4), '>i')[0]) - _unpack_int32 = _unpack_int - - def _pack_int64(self, value): - self.fp.write(array(value, '>q').tobytes()) - - def _unpack_int64(self): - return frombuffer(self.fp.read(8), '>q')[0] - - def _pack_string(self, s): - count = len(s) - self._pack_int(count) - self.fp.write(s.encode('latin1')) - self.fp.write(b'\x00' * (-count % 4)) # pad - - def _unpack_string(self): - count = self._unpack_int() - s = self.fp.read(count).rstrip(b'\x00') - self.fp.read(-count % 4) # read padding - return s - - -class netcdf_variable: - """ - A data object for netcdf files. - - `netcdf_variable` objects are constructed by calling the method - `netcdf_file.createVariable` on the `netcdf_file` object. `netcdf_variable` - objects behave much like array objects defined in numpy, except that their - data resides in a file. Data is read by indexing and written by assigning - to an indexed subset; the entire array can be accessed by the index ``[:]`` - or (for scalars) by using the methods `getValue` and `assignValue`. - `netcdf_variable` objects also have attribute `shape` with the same meaning - as for arrays, but the shape cannot be modified. There is another read-only - attribute `dimensions`, whose value is the tuple of dimension names. - - All other attributes correspond to variable attributes defined in - the NetCDF file. Variable attributes are created by assigning to an - attribute of the `netcdf_variable` object. - - Parameters - ---------- - data : array_like - The data array that holds the values for the variable. - Typically, this is initialized as empty, but with the proper shape. - typecode : dtype character code - Desired data-type for the data array. - size : int - Desired element size for the data array. - shape : sequence of ints - The shape of the array. This should match the lengths of the - variable's dimensions. - dimensions : sequence of strings - The names of the dimensions used by the variable. Must be in the - same order of the dimension lengths given by `shape`. - attributes : dict, optional - Attribute values (any type) keyed by string names. These attributes - become attributes for the netcdf_variable object. - maskandscale : bool, optional - Whether to automatically scale and/or mask data based on attributes. - Default is False. - - - Attributes - ---------- - dimensions : list of str - List of names of dimensions used by the variable object. - isrec, shape - Properties - - See also - -------- - isrec, shape - - """ - def __init__(self, data, typecode, size, shape, dimensions, - attributes=None, - maskandscale=False): - self.data = data - self._typecode = typecode - self._size = size - self._shape = shape - self.dimensions = dimensions - self.maskandscale = maskandscale - - self._attributes = attributes or {} - for k, v in self._attributes.items(): - self.__dict__[k] = v - - def __setattr__(self, attr, value): - # Store user defined attributes in a separate dict, - # so we can save them to file later. - try: - self._attributes[attr] = value - except AttributeError: - pass - self.__dict__[attr] = value - - @property - def isrec(self): - """Returns whether the variable has a record dimension or not. - - A record dimension is a dimension along which additional data could be - easily appended in the netcdf data structure without much rewriting of - the data file. This attribute is a read-only property of the - `netcdf_variable`. - - """ - return bool(self.data.shape) and not self._shape[0] - - @property - def shape(self): - """Returns the shape tuple of the data variable. - - This is a read-only attribute and can not be modified in the - same manner of other numpy arrays. - """ - return self.data.shape - - def getValue(self): - """ - Retrieve a scalar value from a `netcdf_variable` of length one. - - Raises - ------ - ValueError - If the netcdf variable is an array of length greater than one, - this exception will be raised. - - """ - return self.data.item() - - def assignValue(self, value): - """ - Assign a scalar value to a `netcdf_variable` of length one. - - Parameters - ---------- - value : scalar - Scalar value (of compatible type) to assign to a length-one netcdf - variable. This value will be written to file. - - Raises - ------ - ValueError - If the input is not a scalar, or if the destination is not a length-one - netcdf variable. - - """ - if not self.data.flags.writeable: - # Work-around for a bug in NumPy. Calling itemset() on a read-only - # memory-mapped array causes a seg. fault. - # See NumPy ticket #1622, and SciPy ticket #1202. - # This check for `writeable` can be removed when the oldest version - # of NumPy still supported by scipy contains the fix for #1622. - raise RuntimeError("variable is not writeable") - - self.data.itemset(value) - - def typecode(self): - """ - Return the typecode of the variable. - - Returns - ------- - typecode : char - The character typecode of the variable (e.g., 'i' for int). - - """ - return self._typecode - - def itemsize(self): - """ - Return the itemsize of the variable. - - Returns - ------- - itemsize : int - The element size of the variable (e.g., 8 for float64). - - """ - return self._size - - def __getitem__(self, index): - if not self.maskandscale: - return self.data[index] - - data = self.data[index].copy() - missing_value = self._get_missing_value() - data = self._apply_missing_value(data, missing_value) - scale_factor = self._attributes.get('scale_factor') - add_offset = self._attributes.get('add_offset') - if add_offset is not None or scale_factor is not None: - data = data.astype(np.float64) - if scale_factor is not None: - data = data * scale_factor - if add_offset is not None: - data += add_offset - - return data - - def __setitem__(self, index, data): - if self.maskandscale: - missing_value = ( - self._get_missing_value() or - getattr(data, 'fill_value', 999999)) - self._attributes.setdefault('missing_value', missing_value) - self._attributes.setdefault('_FillValue', missing_value) - data = ((data - self._attributes.get('add_offset', 0.0)) / - self._attributes.get('scale_factor', 1.0)) - data = np.ma.asarray(data).filled(missing_value) - if self._typecode not in 'fd' and data.dtype.kind == 'f': - data = np.round(data) - - # Expand data for record vars? - if self.isrec: - if isinstance(index, tuple): - rec_index = index[0] - else: - rec_index = index - if isinstance(rec_index, slice): - recs = (rec_index.start or 0) + len(data) - else: - recs = rec_index + 1 - if recs > len(self.data): - shape = (recs,) + self._shape[1:] - # Resize in-place does not always work since - # the array might not be single-segment - try: - self.data.resize(shape) - except ValueError: - self.__dict__['data'] = np.resize(self.data, shape).astype(self.data.dtype) - self.data[index] = data - - def _default_encoded_fill_value(self): - """ - The default encoded fill-value for this Variable's data type. - """ - nc_type = REVERSE[self.typecode(), self.itemsize()] - return FILLMAP[nc_type] - - def _get_encoded_fill_value(self): - """ - Returns the encoded fill value for this variable as bytes. - - This is taken from either the _FillValue attribute, or the default fill - value for this variable's data type. - """ - if '_FillValue' in self._attributes: - fill_value = np.array(self._attributes['_FillValue'], - dtype=self.data.dtype).tobytes() - if len(fill_value) == self.itemsize(): - return fill_value - else: - return self._default_encoded_fill_value() - else: - return self._default_encoded_fill_value() - - def _get_missing_value(self): - """ - Returns the value denoting "no data" for this variable. - - If this variable does not have a missing/fill value, returns None. - - If both _FillValue and missing_value are given, give precedence to - _FillValue. The netCDF standard gives special meaning to _FillValue; - missing_value is just used for compatibility with old datasets. - """ - - if '_FillValue' in self._attributes: - missing_value = self._attributes['_FillValue'] - elif 'missing_value' in self._attributes: - missing_value = self._attributes['missing_value'] - else: - missing_value = None - - return missing_value - - @staticmethod - def _apply_missing_value(data, missing_value): - """ - Applies the given missing value to the data array. - - Returns a numpy.ma array, with any value equal to missing_value masked - out (unless missing_value is None, in which case the original array is - returned). - """ - - if missing_value is None: - newdata = data - else: - try: - missing_value_isnan = np.isnan(missing_value) - except (TypeError, NotImplementedError): - # some data types (e.g., characters) cannot be tested for NaN - missing_value_isnan = False - - if missing_value_isnan: - mymask = np.isnan(data) - else: - mymask = (data == missing_value) - - newdata = np.ma.masked_where(mymask, data) - - return newdata - - -NetCDFFile = netcdf_file -NetCDFVariable = netcdf_variable diff --git a/nibabel/externals/oset.py b/nibabel/externals/oset.py deleted file mode 100644 index 0a29c661c5..0000000000 --- a/nibabel/externals/oset.py +++ /dev/null @@ -1,84 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""OrderedSet implementation - -Borrowed from https://pypi.org/project/oset/ -Copyright (c) 2009, Raymond Hettinger, and others All rights reserved. -License: BSD-3 -""" - - -from collections.abc import MutableSet - -KEY, PREV, NEXT = range(3) - - -class OrderedSet(MutableSet): - - def __init__(self, iterable=None): - self.end = end = [] - end += [None, end, end] # sentinel node for doubly linked list - self.map = {} # key --> [key, prev, next] - if iterable is not None: - self |= iterable - - def __len__(self): - return len(self.map) - - def __contains__(self, key): - return key in self.map - - def __getitem__(self, key): - return list(self)[key] - - def add(self, key): - if key not in self.map: - end = self.end - curr = end[PREV] - curr[NEXT] = end[PREV] = self.map[key] = [key, curr, end] - - def discard(self, key): - if key in self.map: - key, prev, next = self.map.pop(key) - prev[NEXT] = next - next[PREV] = prev - - def __iter__(self): - end = self.end - curr = end[NEXT] - while curr is not end: - yield curr[KEY] - curr = curr[NEXT] - - def __reversed__(self): - end = self.end - curr = end[PREV] - while curr is not end: - yield curr[KEY] - curr = curr[PREV] - - def pop(self, last=True): - if not self: - raise KeyError('set is empty') - key = next(reversed(self)) if last else next(iter(self)) - self.discard(key) - return key - - def __repr__(self): - if not self: - return '%s()' % (self.__class__.__name__,) - return '%s(%r)' % (self.__class__.__name__, list(self)) - - def __eq__(self, other): - if isinstance(other, OrderedSet): - return len(self) == len(other) and list(self) == list(other) - return set(self) == set(other) - - def __del__(self): - self.clear() # remove circular references diff --git a/nibabel/externals/tests/__init__.py b/nibabel/externals/tests/__init__.py deleted file mode 100644 index 437ae5d390..0000000000 --- a/nibabel/externals/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Make externals tests a package diff --git a/nibabel/externals/tests/data/example_1.nc b/nibabel/externals/tests/data/example_1.nc deleted file mode 100644 index 5775622d0e..0000000000 Binary files a/nibabel/externals/tests/data/example_1.nc and /dev/null differ diff --git a/nibabel/externals/tests/test_netcdf.py b/nibabel/externals/tests/test_netcdf.py deleted file mode 100644 index 08a336d26f..0000000000 --- a/nibabel/externals/tests/test_netcdf.py +++ /dev/null @@ -1,180 +0,0 @@ -""" Tests for netcdf """ - -import os -from os.path import join as pjoin, dirname -from io import BytesIO -from glob import glob -from contextlib import contextmanager - -import numpy as np - -import pytest - -from ..netcdf import netcdf_file - -TEST_DATA_PATH = pjoin(dirname(__file__), 'data') - -N_EG_ELS = 11 # number of elements for example variable -VARTYPE_EG = 'b' # var type for example variable - - -@contextmanager -def make_simple(*args, **kwargs): - f = netcdf_file(*args, **kwargs) - f.history = 'Created for a test' - f.createDimension('time', N_EG_ELS) - time = f.createVariable('time', VARTYPE_EG, ('time',)) - time[:] = np.arange(N_EG_ELS) - time.units = 'days since 2008-01-01' - f.flush() - yield f - f.close() - - -def assert_simple_truths(ncfileobj): - assert ncfileobj.history == b'Created for a test' - time = ncfileobj.variables['time'] - assert time.units == b'days since 2008-01-01' - assert time.shape == (N_EG_ELS,) - assert time[-1] == N_EG_ELS - 1 - - -def test_read_write_files(tmp_path): - fname = str(tmp_path / 'simple.nc') - - with make_simple(fname, 'w') as f: - pass - # To read the NetCDF file we just created:: - with netcdf_file(fname) as f: - # Using mmap is the default - assert f.use_mmap - assert_simple_truths(f) - - # Now without mmap - with netcdf_file(fname, mmap=False) as f: - # Using mmap is the default - assert not f.use_mmap - assert_simple_truths(f) - - # To read the NetCDF file we just created, as file object, no - # mmap. When n * n_bytes(var_type) is not divisible by 4, this - # raised an error in pupynere 1.0.12 and scipy rev 5893, because - # calculated vsize was rounding up in units of 4 - see - # https://www.unidata.ucar.edu/software/netcdf/docs/netcdf.html - fobj = open(fname, 'rb') - with netcdf_file(fobj) as f: - # by default, don't use mmap for file-like - assert not f.use_mmap - assert_simple_truths(f) - - -def test_read_write_sio(): - eg_sio1 = BytesIO() - with make_simple(eg_sio1, 'w') as f1: - str_val = eg_sio1.getvalue() - - eg_sio2 = BytesIO(str_val) - with netcdf_file(eg_sio2) as f2: - assert_simple_truths(f2) - - # Test that error is raised if attempting mmap for sio - eg_sio3 = BytesIO(str_val) - with pytest.raises(ValueError): - netcdf_file(eg_sio3, 'r', True) - # Test 64-bit offset write / read - eg_sio_64 = BytesIO() - with make_simple(eg_sio_64, 'w', version=2) as f_64: - str_val = eg_sio_64.getvalue() - - eg_sio_64 = BytesIO(str_val) - with netcdf_file(eg_sio_64) as f_64: - assert_simple_truths(f_64) - assert f_64.version_byte == 2 - # also when version 2 explicitly specified - eg_sio_64 = BytesIO(str_val) - with netcdf_file(eg_sio_64, version=2) as f_64: - assert_simple_truths(f_64) - assert f_64.version_byte == 2 - - -def test_read_example_data(): - # read any example data files - for fname in glob(pjoin(TEST_DATA_PATH, '*.nc')): - with netcdf_file(fname, 'r') as f: - pass - with netcdf_file(fname, 'r', mmap=False) as f: - pass - - -def test_itemset_no_segfault_on_readonly(): - # Regression test for ticket #1202. - # Open the test file in read-only mode. - filename = pjoin(TEST_DATA_PATH, 'example_1.nc') - with netcdf_file(filename, 'r') as f: - time_var = f.variables['time'] - - # time_var.assignValue(42) should raise a RuntimeError--not seg. fault! - with pytest.raises(RuntimeError): - time_var.assignValue(42) - - -def test_write_invalid_dtype(): - dtypes = ['int64', 'uint64'] - if np.dtype('int').itemsize == 8: # 64-bit machines - dtypes.append('int') - if np.dtype('uint').itemsize == 8: # 64-bit machines - dtypes.append('uint') - - with netcdf_file(BytesIO(), 'w') as f: - f.createDimension('time', N_EG_ELS) - for dt in dtypes: - with pytest.raises(ValueError): - f.createVariable('time', dt, ('time',)) - - -def test_flush_rewind(): - stream = BytesIO() - with make_simple(stream, mode='w') as f: - x = f.createDimension('x', 4) - v = f.createVariable('v', 'i2', ['x']) - v[:] = 1 - f.flush() - len_single = len(stream.getvalue()) - f.flush() - len_double = len(stream.getvalue()) - - assert len_single == len_double - - -def test_dtype_specifiers(): - # Numpy 1.7.0-dev had a bug where 'i2' wouldn't work. - # Specifying np.int16 or similar only works from the same commit as this - # comment was made. - with make_simple(BytesIO(), mode='w') as f: - f.createDimension('x',4) - f.createVariable('v1', 'i2', ['x']) - f.createVariable('v2', np.int16, ['x']) - f.createVariable('v3', np.dtype(np.int16), ['x']) - - -def test_ticket_1720(): - io = BytesIO() - - items = [0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9] - - with netcdf_file(io, 'w') as f: - f.history = 'Created for a test' - f.createDimension('float_var', 10) - float_var = f.createVariable('float_var', 'f', ('float_var',)) - float_var[:] = items - float_var.units = 'metres' - f.flush() - contents = io.getvalue() - - io = BytesIO(contents) - with netcdf_file(io, 'r') as f: - assert f.history == b'Created for a test' - float_var = f.variables['float_var'] - assert float_var.units == b'metres' - assert float_var.shape == (10,) - assert np.allclose(float_var[:], items) diff --git a/nibabel/filebasedimages.py b/nibabel/filebasedimages.py deleted file mode 100644 index 853c394614..0000000000 --- a/nibabel/filebasedimages.py +++ /dev/null @@ -1,610 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Common interface for any image format--volume or surface, binary or xml""" - -from __future__ import annotations - -import io -import typing as ty -from copy import deepcopy -from urllib import request - -from ._compression import COMPRESSION_ERRORS -from .fileholders import FileHolder, FileMap -from .filename_parser import TypesFilenamesError, _stringify_path, splitext_addext, types_filenames -from .openers import ImageOpener - -if ty.TYPE_CHECKING: - from ._typing import Self - from .filename_parser import ExtensionSpec, FileSpec - -FileSniff = tuple[bytes, str] - - -class ImageFileError(Exception): - pass - - -class FileBasedHeader: - """Template class to implement header protocol""" - - @classmethod - def from_header(klass, header: FileBasedHeader | ty.Mapping | None = None) -> Self: - if header is None: - return klass() - # I can't do isinstance here because it is not necessarily true - # that a subclass has exactly the same interface as its parent - # - for example Nifti1Images inherit from Analyze, but have - # different field names - if type(header) == klass: - return header.copy() - raise NotImplementedError( - f'Header class requires a conversion from {klass} to {type(header)}' - ) - - @classmethod - def from_fileobj(klass, fileobj: io.IOBase) -> Self: - raise NotImplementedError - - def write_to(self, fileobj: io.IOBase) -> None: - raise NotImplementedError - - def __eq__(self, other: object) -> bool: - raise NotImplementedError - - def __ne__(self, other: object) -> bool: - return not self == other - - def copy(self) -> Self: - """Copy object to independent representation - - The copy should not be affected by any changes to the original - object. - """ - return deepcopy(self) - - -class FileBasedImage: - """ - Abstract image class with interface for loading/saving images from disk. - - The class doesn't define any image properties. - - It has: - - attributes: - - * extra - - properties: - - * header - - methods: - - * to_filename(fname) - writes data to filename(s) derived from - ``fname``, where the derivation may differ between formats. - * to_file_map() - save image to files with which the image is already - associated. - - classmethods: - - * from_filename(fname) - make instance by loading from filename - * from_file_map(fmap) - make instance from file map - * instance_to_filename(img, fname) - save ``img`` instance to - filename ``fname``. - - It also has a ``header`` - some standard set of meta-data that is specific - to the image format, and ``extra`` - a dictionary container for any other - metadata. - - You cannot slice an image, and trying to slice an image generates an - informative TypeError. - - **There are several ways of writing data** - - There is the usual way, which is the default:: - - img.to_filename(fname) - - and that is, to take the data encapsulated by the image and cast it to - the datatype the header expects, setting any available header scaling - into the header to help the data match. - - You can load the data into an image from file with:: - - img.from_filename(fname) - - The image stores its associated files in its ``file_map`` attribute. In - order to just save an image, for which you know there is an associated - filename, or other storage, you can do:: - - img.to_file_map() - - **Files interface** - - The image has an attribute ``file_map``. This is a mapping, that has keys - corresponding to the file types that an image needs for storage. For - example, the Analyze data format needs an ``image`` and a ``header`` - file type for storage: - - >>> import numpy as np - >>> import nibabel as nib - >>> data = np.arange(24, dtype='f4').reshape((2,3,4)) - >>> img = nib.AnalyzeImage(data, np.eye(4)) - >>> sorted(img.file_map) - ['header', 'image'] - - The values of ``file_map`` are not in fact files but objects with - attributes ``filename``, ``fileobj`` and ``pos``. - - The reason for this interface, is that the contents of files has to - contain enough information so that an existing image instance can save - itself back to the files pointed to in ``file_map``. When a file holder - holds active file-like objects, then these may be affected by the - initial file read; in this case, the file-like objects need to - carry the position at which a write (with ``to_file_map``) should place the - data. The ``file_map`` contents should therefore be such, that this will - work. - """ - - header_class: type[FileBasedHeader] = FileBasedHeader - _meta_sniff_len: int = 0 - files_types: tuple[ExtensionSpec, ...] = (('image', None),) - valid_exts: tuple[str, ...] = () - _compressed_suffixes: tuple[str, ...] = () - - makeable: bool = True # Used in test code - rw: bool = True # Used in test code - - def __init__( - self, - header: FileBasedHeader | ty.Mapping | None = None, - extra: ty.Mapping | None = None, - file_map: FileMap | None = None, - ): - """Initialize image - - The image is a combination of (header), with - optional metadata in `extra`, and filename / file-like objects - contained in the `file_map` mapping. - - Parameters - ---------- - header : None or mapping or header instance, optional - metadata for this image format - extra : None or mapping, optional - metadata to associate with image that cannot be stored in the - metadata of this image type - file_map : mapping, optional - mapping giving file information for this image format - """ - self._header = self.header_class.from_header(header) - if extra is None: - extra = {} - self.extra = dict(extra) - - if file_map is None: - file_map = self.__class__.make_file_map() - self.file_map = file_map - - @property - def header(self) -> FileBasedHeader: - return self._header - - def __getitem__(self, key) -> None: - """No slicing or dictionary interface for images""" - raise TypeError('Cannot slice image objects.') - - def get_filename(self) -> str | None: - """Fetch the image filename - - Parameters - ---------- - None - - Returns - ------- - fname : None or str - Returns None if there is no filename, or a filename string. - If an image may have several filenames associated with it (e.g. - Analyze ``.img, .hdr`` pair) then we return the more characteristic - filename (the ``.img`` filename in the case of Analyze') - """ - # which filename is returned depends on the ordering of the - # 'files_types' class attribute - we return the name - # corresponding to the first in that tuple - characteristic_type = self.files_types[0][0] - return self.file_map[characteristic_type].filename - - def set_filename(self, filename: str) -> None: - """Sets the files in the object from a given filename - - The different image formats may check whether the filename has - an extension characteristic of the format, and raise an error if - not. - - Parameters - ---------- - filename : str or os.PathLike - If the image format only has one file associated with it, - this will be the only filename set into the image - ``.file_map`` attribute. Otherwise, the image instance will - try and guess the other filenames from this given filename. - """ - self.file_map = self.__class__.filespec_to_file_map(filename) - - @classmethod - def from_filename(klass, filename: FileSpec) -> Self: - file_map = klass.filespec_to_file_map(filename) - return klass.from_file_map(file_map) - - @classmethod - def from_file_map(klass, file_map: FileMap) -> Self: - raise NotImplementedError - - @classmethod - def filespec_to_file_map(klass, filespec: FileSpec) -> FileMap: - """Make `file_map` for this class from filename `filespec` - - Class method - - Parameters - ---------- - filespec : str or os.PathLike - Filename that might be for this image file type. - - Returns - ------- - file_map : dict - `file_map` dict with (key, value) pairs of (``file_type``, - FileHolder instance), where ``file_type`` is a string giving the - type of the contained file. - - Raises - ------ - ImageFileError - if `filespec` is not recognizable as being a filename for this - image type. - """ - try: - filenames = types_filenames( - filespec, klass.files_types, trailing_suffixes=klass._compressed_suffixes - ) - except TypesFilenamesError: - raise ImageFileError(f'Filespec "{filespec}" does not look right for class {klass}') - file_map = {} - for key, fname in filenames.items(): - file_map[key] = FileHolder(filename=fname) - return file_map - - def to_filename(self, filename: FileSpec, **kwargs) -> None: - r"""Write image to files implied by filename string - - Parameters - ---------- - filename : str or os.PathLike - filename to which to save image. We will parse `filename` - with ``filespec_to_file_map`` to work out names for image, - header etc. - \*\*kwargs : keyword arguments - Keyword arguments to format-specific save - - Returns - ------- - None - """ - self.file_map = self.filespec_to_file_map(filename) - self.to_file_map(**kwargs) - - def to_file_map(self, file_map: FileMap | None = None, **kwargs) -> None: - raise NotImplementedError - - @classmethod - def make_file_map(klass, mapping: ty.Mapping[str, str | io.IOBase] | None = None) -> FileMap: - """Class method to make files holder for this image type - - Parameters - ---------- - mapping : None or mapping, optional - mapping with keys corresponding to image file types (such as - 'image', 'header' etc, depending on image class) and values - that are filenames or file-like. Default is None - - Returns - ------- - file_map : dict - dict with string keys given by first entry in tuples in - sequence klass.files_types, and values of type FileHolder, - where FileHolder objects have default values, other than - those given by `mapping` - """ - if mapping is None: - mapping = {} - file_map = {} - for key, ext in klass.files_types: - file_map[key] = FileHolder() - mapval = mapping.get(key, None) - if isinstance(mapval, str): - file_map[key].filename = mapval - elif hasattr(mapval, 'tell'): - file_map[key].fileobj = mapval - return file_map - - load = from_filename - - @classmethod - def instance_to_filename(klass, img: FileBasedImage, filename: FileSpec) -> None: - """Save `img` in our own format, to name implied by `filename` - - This is a class method - - Parameters - ---------- - img : ``any FileBasedImage`` instance - - filename : str - Filename, implying name to which to save image. - """ - img = klass.from_image(img) - img.to_filename(filename) - - @classmethod - def from_image(klass, img: FileBasedImage) -> Self: - """Class method to create new instance of own class from `img` - - Parameters - ---------- - img : ``FileBasedImage`` instance - In fact, an object with the API of ``FileBasedImage``. - - Returns - ------- - img : ``FileBasedImage`` instance - Image, of our own class - """ - raise NotImplementedError - - @classmethod - def _sniff_meta_for( - klass, - filename: FileSpec, - sniff_nbytes: int, - sniff: FileSniff | None = None, - ) -> FileSniff | None: - """Sniff metadata for image represented by `filename` - - Parameters - ---------- - filename : str or os.PathLike - Filename for an image, or an image header (metadata) file. - If `filename` points to an image data file, and the image type has - a separate "header" file, we work out the name of the header file, - and read from that instead of `filename`. - sniff_nbytes : int - Number of bytes to read from the image or metadata file - sniff : (bytes, fname), optional - The result of a previous call to `_sniff_meta_for`. If fname - matches the computed header file name, `sniff` is returned without - rereading the file. - - Returns - ------- - sniff : None or (bytes, fname) - None if we could not read the image or metadata file. `sniff[0]` - is either length `sniff_nbytes` or the length of the image / - metadata file, whichever is the shorter. `fname` is the name of - the sniffed file. - """ - froot, ext, trailing = splitext_addext(filename, klass._compressed_suffixes) - # Determine the metadata location - t_fnames = types_filenames( - filename, klass.files_types, trailing_suffixes=klass._compressed_suffixes - ) - meta_fname = t_fnames.get('header', _stringify_path(filename)) - - # Do not re-sniff if it would be from the same file - if sniff is not None and sniff[1] == meta_fname: - return sniff - - # Attempt to sniff from metadata location - try: - with ImageOpener(meta_fname, 'rb') as fobj: - binaryblock = fobj.read(sniff_nbytes) - except COMPRESSION_ERRORS + (OSError, EOFError): - return None - return (binaryblock, meta_fname) - - @classmethod - def path_maybe_image( - klass, - filename: FileSpec, - sniff: FileSniff | None = None, - sniff_max: int = 1024, - ) -> tuple[bool, FileSniff | None]: - """Return True if `filename` may be image matching this class - - Parameters - ---------- - filename : str or os.PathLike - Filename for an image, or an image header (metadata) file. - If `filename` points to an image data file, and the image type has - a separate "header" file, we work out the name of the header file, - and read from that instead of `filename`. - sniff : None or (bytes, filename), optional - Bytes content read from a previous call to this method, on another - class, with metadata filename. This allows us to read metadata - bytes once from the image or header, and pass this read set of - bytes to other image classes, therefore saving a repeat read of the - metadata. `filename` is used to validate that metadata would be - read from the same file, re-reading if not. None forces this - method to read the metadata. - sniff_max : int, optional - The maximum number of bytes to read from the metadata. If the - metadata file is long enough, we read this many bytes from the - file, otherwise we read to the end of the file. Longer values - sniff more of the metadata / image file, making it more likely that - the returned sniff will be useful for later calls to - ``path_maybe_image`` for other image classes. - - Returns - ------- - maybe_image : bool - True if `filename` may be valid for an image of this class. - sniff : None or (bytes, filename) - Read bytes content from found metadata. May be None if the file - does not appear to have useful metadata. - """ - froot, ext, trailing = splitext_addext(filename, klass._compressed_suffixes) - if ext.lower() not in klass.valid_exts: - return False, sniff - if not hasattr(klass.header_class, 'may_contain_header'): - return True, sniff - - # Force re-sniff on too-short sniff - if sniff is not None and len(sniff[0]) < klass._meta_sniff_len: - sniff = None - sniff = klass._sniff_meta_for(filename, max(klass._meta_sniff_len, sniff_max), sniff) - if sniff is None or len(sniff[0]) < klass._meta_sniff_len: - return False, sniff - return klass.header_class.may_contain_header(sniff[0]), sniff - - -class SerializableImage(FileBasedImage): - """ - Abstract image class for (de)serializing images to/from byte streams/strings. - - The class doesn't define any image properties. - - It has: - - methods: - - * to_bytes() - serialize image to byte string - - classmethods: - - * from_bytes(bytestring) - make instance by deserializing a byte string - * from_http://github.com/url(url) - make instance by fetching and deserializing a URL - - Loading from byte strings should provide round-trip equivalence: - - .. code:: python - - img_a = klass.from_bytes(bstr) - img_b = klass.from_bytes(img_a.to_bytes()) - - np.allclose(img_a.get_fdata(), img_b.get_fdata()) - np.allclose(img_a.affine, img_b.affine) - - Further, for images that are single files on disk, the following methods of loading - the image must be equivalent: - - .. code:: python - - img = klass.from_filename(fname) - - with open(fname, 'rb') as fobj: - img = klass.from_bytes(fobj.read()) - - And the following methods of saving a file must be equivalent: - - .. code:: python - - img.to_filename(fname) - - with open(fname, 'wb') as fobj: - fobj.write(img.to_bytes()) - - Images that consist of separate header and data files (e.g., Analyze - images) currently do not support this interface. - For multi-file images, ``to_bytes()`` and ``from_bytes()`` must be - overridden, and any encoding details should be documented. - """ - - @classmethod - def _filemap_from_iobase(klass, io_obj: io.IOBase) -> FileMap: - """For single-file image types, make a file map with the correct key""" - if len(klass.files_types) > 1: - raise NotImplementedError('(de)serialization is undefined for multi-file images') - return klass.make_file_map({klass.files_types[0][0]: io_obj}) - - @classmethod - def from_stream(klass, io_obj: io.IOBase) -> Self: - """Load image from readable IO stream - - Convert to BytesIO to enable seeking, if input stream is not seekable - - Parameters - ---------- - io_obj : IOBase object - Readable stream - """ - if not io_obj.seekable(): - io_obj = io.BytesIO(io_obj.read()) - return klass.from_file_map(klass._filemap_from_iobase(io_obj)) - - def to_stream(self, io_obj: io.IOBase, **kwargs) -> None: - r"""Save image to writable IO stream - - Parameters - ---------- - io_obj : IOBase object - Writable stream - \*\*kwargs : keyword arguments - Keyword arguments that may be passed to ``img.to_file_map()`` - """ - self.to_file_map(self._filemap_from_iobase(io_obj), **kwargs) - - @classmethod - def from_bytes(klass, bytestring: bytes) -> Self: - """Construct image from a byte string - - Class method - - Parameters - ---------- - bytestring : bytes - Byte string containing the on-disk representation of an image - """ - return klass.from_stream(io.BytesIO(bytestring)) - - def to_bytes(self, **kwargs) -> bytes: - r"""Return a ``bytes`` object with the contents of the file that would - be written if the image were saved. - - Parameters - ---------- - \*\*kwargs : keyword arguments - Keyword arguments that may be passed to ``img.to_file_map()`` - - Returns - ------- - bytes - Serialized image - """ - bio = io.BytesIO() - self.to_stream(bio, **kwargs) - return bio.getvalue() - - @classmethod - def from_url(http://github.com/klass,%20url:%20str%20|%20request.Request,%20timeout:%20float%20=%205) -> Self: - """Retrieve and load an image from a URL - - Class method - - Parameters - ---------- - url : str or urllib.request.Request object - URL of file to retrieve - timeout : float, optional - Time (in seconds) to wait for a response - """ - response = request.urlopen(url, timeout=timeout) - return klass.from_stream(response) diff --git a/nibabel/fileholders.py b/nibabel/fileholders.py deleted file mode 100644 index df7c34af63..0000000000 --- a/nibabel/fileholders.py +++ /dev/null @@ -1,123 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Fileholder class""" - -from __future__ import annotations - -import typing as ty -from copy import copy - -from .openers import ImageOpener - -if ty.TYPE_CHECKING: - import io - - -class FileHolderError(Exception): - pass - - -class FileHolder: - """class to contain filename, fileobj and file position""" - - def __init__( - self, - filename: str | None = None, - fileobj: io.IOBase | None = None, - pos: int = 0, - ): - """Initialize FileHolder instance - - Parameters - ---------- - filename : str, optional - filename. Default is None - fileobj : file-like object, optional - Should implement at least 'seek' (for the purposes for this - class). Default is None - pos : int, optional - position in filename or fileobject at which to start reading - or writing data; defaults to 0 - """ - self.filename = filename - self.fileobj = fileobj - self.pos = pos - - def get_prepare_fileobj(self, *args, **kwargs) -> ImageOpener: - """Return fileobj if present, or return fileobj from filename - - Set position to that given in self.pos - - Parameters - ---------- - *args : tuple - positional arguments to file open. Ignored if there is a - defined ``self.fileobj``. These might include the mode, such - as 'rb' - **kwargs : dict - named arguments to file open. Ignored if there is a - defined ``self.fileobj`` - - Returns - ------- - fileobj : file-like object - object has position set (via ``fileobj.seek()``) to - ``self.pos`` - """ - if self.fileobj is not None: - obj = ImageOpener(self.fileobj) # for context manager - obj.seek(self.pos) - elif self.filename is not None: - obj = ImageOpener(self.filename, *args, **kwargs) - if self.pos != 0: - obj.seek(self.pos) - else: - raise FileHolderError('No filename or fileobj present') - return obj - - def same_file_as(self, other: FileHolder) -> bool: - """Test if `self` refers to same files / fileobj as `other` - - Parameters - ---------- - other : object - object with `filename` and `fileobj` attributes - - Returns - ------- - tf : bool - True if `other` has the same filename (or both have None) and the - same fileobj (or both have None - """ - return (self.filename == other.filename) and (self.fileobj == other.fileobj) - - @property - def file_like(self) -> str | io.IOBase | None: - """Return ``self.fileobj`` if not None, otherwise ``self.filename``""" - return self.fileobj if self.fileobj is not None else self.filename - - -FileMap = ty.Mapping[str, FileHolder] - - -def copy_file_map(file_map: FileMap) -> FileMap: - r"""Copy mapping of fileholders given by `file_map` - - Parameters - ---------- - file_map : mapping - mapping of ``FileHolder`` instances - - Returns - ------- - fm_copy : dict - Copy of `file_map`, using shallow copy of ``FileHolder``\s - - """ - return {key: copy(fh) for key, fh in file_map.items()} diff --git a/nibabel/filename_parser.py b/nibabel/filename_parser.py deleted file mode 100644 index a16c13ec22..0000000000 --- a/nibabel/filename_parser.py +++ /dev/null @@ -1,309 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Create filename pairs, triplets etc, with expected extensions""" - -from __future__ import annotations - -import os -import pathlib -import typing as ty - -if ty.TYPE_CHECKING: - FileSpec = str | os.PathLike[str] - ExtensionSpec = tuple[str, str | None] - - -class TypesFilenamesError(Exception): - pass - - -def _stringify_path(filepath_or_buffer: FileSpec) -> str: - """Attempt to convert a path-like object to a string. - - Parameters - ---------- - filepath_or_buffer : str or os.PathLike - - Returns - ------- - str_filepath_or_buffer : str - - Notes - ----- - Adapted from: - https://github.com/pandas-dev/pandas/blob/325dd68/pandas/io/common.py#L131-L160 - """ - return pathlib.Path(filepath_or_buffer).expanduser().as_posix() - - -def types_filenames( - template_fname: FileSpec, - types_exts: ty.Sequence[ExtensionSpec], - trailing_suffixes: ty.Sequence[str] = ('.gz', '.bz2'), - enforce_extensions: bool = True, - match_case: bool = False, -) -> dict[str, str]: - """Return filenames with standard extensions from template name - - The typical case is returning image and header filenames for an - Analyze image, that expects an 'image' file type with extension ``.img``, - and a 'header' file type, with extension ``.hdr``. - - Parameters - ---------- - template_fname : str or os.PathLike - template filename from which to construct output dict of - filenames, with given `types_exts` type to extension mapping. If - ``self.enforce_extensions`` is True, then filename must have one - of the defined extensions from the types list. If - ``self.enforce_extensions`` is False, then the other filenames - are guessed at by adding extensions to the base filename. - Ignored suffixes (from `trailing_suffixes`) append themselves to - the end of all the filenames. - types_exts : sequence of sequences - sequence of (name, extension) str sequences defining type to - extension mapping. - trailing_suffixes : sequence of strings, optional - suffixes that should be ignored when looking for - extensions - default is ``('.gz', '.bz2')`` - enforce_extensions : {True, False}, optional - If True, raise an error when attempting to set value to - type which has the wrong extension - match_case : bool, optional - If True, match case of extensions and trailing suffixes when - searching in `template_fname`, otherwise do case-insensitive - match. - - Returns - ------- - types_fnames : dict - dict with types as keys, and generated filenames as values. The - types are given by the first elements of the tuples in - `types_exts`. - - Examples - -------- - >>> types_exts = (('t1','.ext1'),('t2', '.ext2')) - >>> tfns = types_filenames('/path/test.ext1', types_exts) - >>> tfns == {'t1': '/path/test.ext1', 't2': '/path/test.ext2'} - True - - Bare file roots without extensions get them added - - >>> tfns = types_filenames('/path/test', types_exts) - >>> tfns == {'t1': '/path/test.ext1', 't2': '/path/test.ext2'} - True - - With enforce_extensions == False, allow first type to have any - extension. - - >>> tfns = types_filenames('/path/test.funny', types_exts, - ... enforce_extensions=False) - >>> tfns == {'t1': '/path/test.funny', 't2': '/path/test.ext2'} - True - """ - template_fname = _stringify_path(template_fname) - if not isinstance(template_fname, str): - raise TypesFilenamesError('Need file name as input to set_filenames') - template_fname = template_fname.removesuffix('.') - filename, found_ext, ignored, guessed_name = parse_filename( - template_fname, types_exts, trailing_suffixes, match_case - ) - # Flag cases where we just set the input name directly - direct_set_name = None - if enforce_extensions: - if guessed_name is None: - # no match - maybe there was no extension atall or the - # wrong extension. In either case we raise an error - if found_ext: - # an extension, but the wrong one - raise TypesFilenamesError( - f'File extension "{found_ext}" was not in ' - f'expected list: {[e for t, e in types_exts]}' - ) - elif ignored: # there was no extension, but an ignored suffix - # This is a special case like 'test.gz' (where .gz - # is ignored). It's confusing to change - # this to test.img.gz, or test.gz.img, so error - raise TypesFilenamesError(f'Confusing ignored suffix {ignored} without extension') - # if we've got to here, we have a guessed name and a found - # extension. - else: # not enforcing extensions. If there's an extension, we set the - # filename directly from input, for the first types_exts type - # only. Also, if there was no extension, but an ignored suffix - # ('test.gz' type case), we set the filename directly. - # Otherwise (no extension, no ignored suffix), we stay with the - # default, which is to add the default extensions according to - # type. - if found_ext or ignored: - direct_set_name = types_exts[0][0] - tfns = {} - # now we have an extension case matching problem. For example, if - # we've found .IMG as the extension, we want .HDR as the matching - # one. Let's only do this when the extension is all upper or all - # lower case. - proc_ext: ty.Callable[[str], str] = lambda s: s - if found_ext: - if found_ext == found_ext.upper(): - proc_ext = str.upper - elif found_ext == found_ext.lower(): - proc_ext = str.lower - for name, ext in types_exts: - if name == direct_set_name: - tfns[name] = template_fname - continue - fname = filename - if ext: - fname += proc_ext(ext) - if ignored: - fname += ignored - tfns[name] = fname - return tfns - - -def parse_filename( - filename: FileSpec, - types_exts: ty.Sequence[ExtensionSpec], - trailing_suffixes: ty.Sequence[str], - match_case: bool = False, -) -> tuple[str, str, str | None, str | None]: - """Split filename into fileroot, extension, trailing suffix; guess type. - - Parameters - ---------- - filename : str or os.PathLike - filename in which to search for type extensions - types_exts : sequence of sequences - sequence of (name, extension) str sequences defining type to - extension mapping. - trailing_suffixes : sequence of strings - suffixes that should be ignored when looking for - extensions - match_case : bool, optional - If True, match case of extensions and trailing suffixes when - searching in `filename`, otherwise do case-insensitive match. - - Returns - ------- - pth : str - path with any matching extensions or trailing suffixes removed - ext : str - If there were any matching extensions, in `types_exts` return - that; otherwise return extension derived from - ``os.path.splitext``. - trailing : str - If there were any matching `trailing_suffixes` return that - matching suffix, otherwise '' - guessed_type : str - If we found a matching extension in `types_exts` return the - corresponding ``type`` - - Examples - -------- - >>> types_exts = (('t1', 'ext1'),('t2', 'ext2')) - >>> parse_filename('/path/fname.funny', types_exts, ()) - ('/path/fname', '.funny', None, None) - >>> parse_filename('/path/fnameext2', types_exts, ()) - ('/path/fname', 'ext2', None, 't2') - >>> parse_filename('/path/fnameext2', types_exts, ('.gz',)) - ('/path/fname', 'ext2', None, 't2') - >>> parse_filename('/path/fnameext2.gz', types_exts, ('.gz',)) - ('/path/fname', 'ext2', '.gz', 't2') - """ - filename = _stringify_path(filename) - - ignored = None - if match_case: - endswith = _endswith - else: - endswith = _iendswith - for ext in trailing_suffixes: - if endswith(filename, ext): - extpos = -len(ext) - ignored = filename[extpos:] - filename = filename[:extpos] - break - guessed_name = None - found_ext = None - for name, type_ext in types_exts: - if type_ext and endswith(filename, type_ext): - extpos = -len(type_ext) - found_ext = filename[extpos:] - filename = filename[:extpos] - guessed_name = name - break - else: - filename, found_ext = os.path.splitext(filename) - return (filename, found_ext, ignored, guessed_name) - - -def _endswith(whole: str, end: str) -> bool: - return whole.endswith(end) - - -def _iendswith(whole: str, end: str) -> bool: - return whole.lower().endswith(end.lower()) - - -def splitext_addext( - filename: FileSpec, - addexts: ty.Sequence[str] = ('.gz', '.bz2', '.zst'), - match_case: bool = False, -) -> tuple[str, str, str]: - """Split ``/pth/fname.ext.gz`` into ``/pth/fname, .ext, .gz`` - - where ``.gz`` may be any of passed `addext` trailing suffixes. - - Parameters - ---------- - filename : str or os.PathLike - filename that may end in any or none of `addexts` - match_case : bool, optional - If True, match case of `addexts` and `filename`, otherwise do - case-insensitive match. - - Returns - ------- - froot : str - Root of filename - e.g. ``/pth/fname`` in example above - ext : str - Extension, where extension is not in `addexts` - e.g. ``.ext`` in - example above - addext : str - Any suffixes appearing in `addext` occurring at end of filename - - Examples - -------- - >>> splitext_addext('fname.ext.gz') - ('fname', '.ext', '.gz') - >>> splitext_addext('fname.ext') - ('fname', '.ext', '') - >>> splitext_addext('fname.ext.foo', ('.foo', '.bar')) - ('fname', '.ext', '.foo') - """ - filename = _stringify_path(filename) - - if match_case: - endswith = _endswith - else: - endswith = _iendswith - for ext in addexts: - if endswith(filename, ext): - extpos = -len(ext) - filename, addext = filename[:extpos], filename[extpos:] - break - else: - addext = '' - # os.path.splitext() behaves unexpectedly when filename starts with '.' - extpos = filename.rfind('.') - if extpos < 0 or filename.strip('.') == '': - root, ext = filename, '' - else: - root, ext = filename[:extpos], filename[extpos:] - return (root, ext, addext) diff --git a/nibabel/fileslice.py b/nibabel/fileslice.py deleted file mode 100644 index 91ed1f70a1..0000000000 --- a/nibabel/fileslice.py +++ /dev/null @@ -1,811 +0,0 @@ -"""Utilities for getting array slices out of file-like objects""" - -import operator -from functools import reduce -from mmap import mmap -from numbers import Integral - -import numpy as np - -# Threshold for memory gap above which we always skip, to save memory -# This value came from trying various values and looking at the timing with -# ``bench_fileslice`` -SKIP_THRESH = 2**8 - - -class _NullLock: - """Can be used as no-function dummy object in place of ``threading.lock``. - - The ``_NullLock`` is an object which can be used in place of a - ``threading.Lock`` object, but doesn't actually do anything. - - It is used by the ``read_segments`` function in the event that a - ``Lock`` is not provided by the caller. - """ - - def __enter__(self): - pass - - def __exit__(self, exc_type, exc_val, exc_tb): - return False - - -def is_fancy(sliceobj): - """Returns True if sliceobj is attempting fancy indexing - - Parameters - ---------- - sliceobj : object - something that can be used to slice an array as in ``arr[sliceobj]`` - - Returns - ------- - tf: bool - True if sliceobj represents fancy indexing, False for basic indexing - """ - if not isinstance(sliceobj, tuple): - sliceobj = (sliceobj,) - for slicer in sliceobj: - if getattr(slicer, 'ndim', 0) > 0: # ndarray always fancy, but scalars are safe - return True - # slice or Ellipsis or None OK for basic - if isinstance(slicer, slice) or slicer in (None, Ellipsis): - continue - try: - int(slicer) - except TypeError: - return True - return False - - -def canonical_slicers(sliceobj, shape, check_inds=True): - """Return canonical version of `sliceobj` for array shape `shape` - - `sliceobj` is a slicer for an array ``A`` implied by `shape`. - - * Expand `sliceobj` with ``slice(None)`` to add any missing (implied) axes - in `sliceobj` - * Find any slicers in `sliceobj` that do a full axis slice and replace by - ``slice(None)`` - * Replace any floating point values for slicing with integers - * Replace negative integer slice values with equivalent positive integers. - - Does not handle fancy indexing (indexing with arrays or array-like indices) - - Parameters - ---------- - sliceobj : object - something that can be used to slice an array as in ``arr[sliceobj]`` - shape : sequence - shape of array that will be indexed by `sliceobj` - check_inds : {True, False}, optional - Whether to check if integer indices are out of bounds - - Returns - ------- - can_slicers : tuple - version of `sliceobj` for which Ellipses have been expanded, missing - (implied) dimensions have been appended, and slice objects equivalent - to ``slice(None)`` have been replaced by ``slice(None)``, integer axes - have been checked, and negative indices set to positive equivalent - """ - if not isinstance(sliceobj, tuple): - sliceobj = (sliceobj,) - if is_fancy(sliceobj): - raise ValueError('Cannot handle fancy indexing') - can_slicers = [] - n_dim = len(shape) - n_real = 0 - for i, slicer in enumerate(sliceobj): - if slicer is None: - can_slicers.append(None) - continue - if slicer == Ellipsis: - remaining = sliceobj[i + 1 :] - if Ellipsis in remaining: - raise ValueError('More than one Ellipsis in slicing expression') - real_remaining = [r for r in remaining if r is not None] - n_ellided = n_dim - n_real - len(real_remaining) - can_slicers.extend((slice(None),) * n_ellided) - n_real += n_ellided - continue - # int / slice indexing cases - dim_len = shape[n_real] - n_real += 1 - try: # test for integer indexing - slicer = int(slicer) - except TypeError: # should be slice object - if slicer != slice(None): - # Could this be full slice? - if ( - slicer.stop == dim_len - and slicer.start in (None, 0) - and slicer.step in (None, 1) - ): - slicer = slice(None) - else: - if slicer < 0: - slicer = dim_len + slicer - elif check_inds and slicer >= dim_len: - raise ValueError(f'Integer index {slicer} too large') - can_slicers.append(slicer) - # Fill out any missing dimensions - if n_real < n_dim: - can_slicers.extend((slice(None),) * (n_dim - n_real)) - return tuple(can_slicers) - - -def slice2outax(ndim, sliceobj): - """Matching output axes for input array ndim `ndim` and slice `sliceobj` - - Parameters - ---------- - ndim : int - number of axes in input array - sliceobj : object - something that can be used to slice an array as in ``arr[sliceobj]`` - - Returns - ------- - out_ax_inds : tuple - Say ``A` is a (pretend) input array of `ndim` dimensions. Say ``B = - A[sliceobj]``. `out_ax_inds` has one value per axis in ``A`` giving - corresponding axis in ``B``. - """ - sliceobj = canonical_slicers(sliceobj, [1] * ndim, check_inds=False) - out_ax_no = 0 - out_ax_inds = [] - for obj in sliceobj: - if isinstance(obj, Integral): - out_ax_inds.append(None) - continue - if obj is not None: - out_ax_inds.append(out_ax_no) - out_ax_no += 1 - return tuple(out_ax_inds) - - -def slice2len(slicer, in_len): - """Output length after slicing original length `in_len` with `slicer` - Parameters - ---------- - slicer : slice object - in_len : int - - Returns - ------- - out_len : int - Length after slicing - - Notes - ----- - Returns same as ``len(np.arange(in_len)[slicer])`` - """ - if slicer == slice(None): - return in_len - full_slicer = fill_slicer(slicer, in_len) - return _full_slicer_len(full_slicer) - - -def _full_slicer_len(full_slicer): - """Return length of slicer processed by ``fill_slicer``""" - start, stop, step = full_slicer.start, full_slicer.stop, full_slicer.step - if stop is None: # case of negative step - stop = -1 - gap = stop - start - if (step > 0 and gap <= 0) or (step < 0 and gap >= 0): - return 0 - return int(np.ceil(gap / step)) - - -def fill_slicer(slicer, in_len): - """Return slice object with Nones filled out to match `in_len` - - Also fixes too large stop / start values according to slice() slicing - rules. - - The returned slicer can have a None as `slicer.stop` if `slicer.step` is - negative and the input `slicer.stop` is None. This is because we can't - represent the ``stop`` as an integer, because -1 has a different meaning. - - Parameters - ---------- - slicer : slice object - in_len : int - length of axis on which `slicer` will be applied - - Returns - ------- - can_slicer : slice object - slice with start, stop, step set to explicit values, with the exception - of ``stop`` for negative step, which is None for the case of slicing - down through the first element - """ - start, stop, step = slicer.start, slicer.stop, slicer.step - if step is None: - step = 1 - if start is not None and start < 0: - start = in_len + start - if stop is not None and stop < 0: - stop = in_len + stop - if step > 0: - if start is None: - start = 0 - if stop is None: - stop = in_len - else: - stop = min(stop, in_len) - else: # step < 0 - if start is None: - start = in_len - 1 - else: - start = min(start, in_len - 1) - return slice(start, stop, step) - - -def predict_shape(sliceobj, in_shape): - """Predict shape of array from slicing array shape `shape` with `sliceobj` - - Parameters - ---------- - sliceobj : object - something that can be used to slice an array as in ``arr[sliceobj]`` - in_shape : sequence - shape of array that could be sliced by `sliceobj` - - Returns - ------- - out_shape : tuple - predicted shape arising from slicing array shape `in_shape` with - `sliceobj` - """ - if not isinstance(sliceobj, tuple): - sliceobj = (sliceobj,) - sliceobj = canonical_slicers(sliceobj, in_shape) - out_shape = [] - real_no = 0 - for slicer in sliceobj: - if slicer is None: - out_shape.append(1) - continue - real_no += 1 - try: # if int - we drop a dim (no append) - slicer = int(slicer) - except TypeError: - out_shape.append(slice2len(slicer, in_shape[real_no - 1])) - return tuple(out_shape) - - -def _positive_slice(slicer): - """Return full slice `slicer` enforcing positive step size - - `slicer` assumed full in the sense of :func:`fill_slicer` - """ - start, stop, step = slicer.start, slicer.stop, slicer.step - if step > 0: - return slicer - if stop is None: - stop = -1 - gap = stop - start - n = gap / step - n = int(n) - 1 if int(n) == n else int(n) - end = start + n * step - return slice(end, start + 1, -step) - - -def threshold_heuristic(slicer, dim_len, stride, skip_thresh=SKIP_THRESH): - """Whether to force full axis read or contiguous read of stepped slice - - Allows :func:`fileslice` to sometimes read memory that it will throw away - in order to get maximum speed. In other words, trade memory for fewer disk - reads. - - Parameters - ---------- - slicer : slice object, or int - If slice, can be assumed to be full as in ``fill_slicer`` - dim_len : int - length of axis being sliced - stride : int - memory distance between elements on this axis - skip_thresh : int, optional - Memory gap threshold in bytes above which to prefer skipping memory - rather than reading it and later discarding. - - Returns - ------- - action : {'full', 'contiguous', None} - Gives the suggested optimization for reading the data - - * 'full' - read whole axis - * 'contiguous' - read all elements between start and stop - * None - read only memory needed for output - - Notes - ----- - Let's say we are in the middle of reading a file at the start of some - memory length $B$ bytes. We don't need the memory, and we are considering - whether to read it anyway (then throw it away) (READ) or stop reading, skip - $B$ bytes and restart reading from there (SKIP). - - After trying some more fancy algorithms, a hard threshold (`skip_thresh`) - for the maximum skip distance seemed to work well, as measured by times on - ``nibabel.benchmarks.bench_fileslice`` - """ - if isinstance(slicer, Integral): - gap_size = (dim_len - 1) * stride - return 'full' if gap_size <= skip_thresh else None - step_size = abs(slicer.step) * stride - if step_size > skip_thresh: - return None # Prefer skip - # At least contiguous - also full? - slicer = _positive_slice(slicer) - start, stop = slicer.start, slicer.stop - read_len = stop - start - gap_size = (dim_len - read_len) * stride - return 'full' if gap_size <= skip_thresh else 'contiguous' - - -def optimize_slicer(slicer, dim_len, all_full, is_slowest, stride, heuristic=threshold_heuristic): - """Return maybe modified slice and post-slice slicing for `slicer` - - Parameters - ---------- - slicer : slice object or int - dim_len : int - length of axis along which to slice - all_full : bool - Whether dimensions up until now have been full (all elements) - is_slowest : bool - Whether this dimension is the slowest changing in memory / on disk - stride : int - size of one step along this axis - heuristic : callable, optional - function taking slice object, dim_len, stride length as arguments, - returning one of 'full', 'contiguous', None. See - :func:`threshold_heuristic` for an example. - - Returns - ------- - to_read : slice object or int - maybe modified slice based on `slicer` expressing what data should be - read from an underlying file or buffer. `to_read` must always have - positive ``step`` (because we don't want to go backwards in the buffer - / file) - post_slice : slice object - slice to be applied after array has been read. Applies any - transformations in `slicer` that have not been applied in `to_read`. If - axis will be dropped by `to_read` slicing, so no slicing would make - sense, return string ``dropped`` - - Notes - ----- - This is the heart of the algorithm for making segments from slice objects. - - A contiguous slice is a slice with ``slice.step in (1, -1)`` - - A full slice is a continuous slice returning all elements. - - The main question we have to ask is whether we should transform `to_read`, - `post_slice` to prefer a full read and partial slice. We only do this in - the case of all_full==True. In this case we might benefit from reading a - continuous chunk of data even if the slice is not continuous, or reading - all the data even if the slice is not full. Apply a heuristic `heuristic` - to decide whether to do this, and adapt `to_read` and `post_slice` slice - accordingly. - - Otherwise (apart from constraint to be positive) return `to_read` unaltered - and `post_slice` as ``slice(None)`` - """ - # int or slice as input? - try: # if int - we drop a dim (no append) - slicer = int(slicer) # casts float to int as well - except TypeError: # slice - # Deal with full cases first - if slicer == slice(None): - return slicer, slicer - slicer = fill_slicer(slicer, dim_len) - # actually equivalent to slice(None) - if slicer == slice(0, dim_len, 1): - return slice(None), slice(None) - # full, but reversed - if slicer == slice(dim_len - 1, None, -1): - return slice(None), slice(None, None, -1) - # Not full, maybe continuous - is_int = False - else: # int - if slicer < 0: # make negative offsets positive - slicer = dim_len + slicer - is_int = True - if all_full: - action = heuristic(slicer, dim_len, stride) - # Check return values (we may be using a custom function) - if action not in ('full', 'contiguous', None): - raise ValueError(f'Unexpected return {action} from heuristic') - if is_int and action == 'contiguous': - raise ValueError('int index cannot be contiguous') - # If this is the slowest changing dimension, never upgrade None or - # contiguous beyond contiguous (we've already covered the already-full - # case) - if is_slowest and action == 'full': - action = None if is_int else 'contiguous' - if action == 'full': - return slice(None), slicer - elif action == 'contiguous': # Cannot be int - # If this is already contiguous, default None behavior handles it - step = slicer.step - if step not in (-1, 1): - if step < 0: - slicer = _positive_slice(slicer) - return (slice(slicer.start, slicer.stop, 1), slice(None, None, step)) - # We only need to be positive - if is_int: - return slicer, 'dropped' - if slicer.step > 0: - return slicer, slice(None) - return _positive_slice(slicer), slice(None, None, -1) - - -def calc_slicedefs(sliceobj, in_shape, itemsize, offset, order, heuristic=threshold_heuristic): - """Return parameters for slicing array with `sliceobj` given memory layout - - Calculate the best combination of skips / (read + discard) to use for - reading the data from disk / memory, then generate corresponding - `segments`, the disk offsets and read lengths to read the memory. If we - have chosen some (read + discard) optimization, then we need to discard the - surplus values from the read array using `post_slicers`, a slicing tuple - that takes the array as read from a file-like object, and returns the array - we want. - - Parameters - ---------- - sliceobj : object - something that can be used to slice an array as in ``arr[sliceobj]`` - in_shape : sequence - shape of underlying array to be sliced - itemsize : int - element size in array (in bytes) - offset : int - offset of array data in underlying file or memory buffer - order : {'C', 'F'} - memory layout of underlying array - heuristic : callable, optional - function taking slice object, dim_len, stride length as arguments, - returning one of 'full', 'contiguous', None. See - :func:`optimize_slicer` and :func:`threshold_heuristic` - - Returns - ------- - segments : list - list of 2 element lists where lists are (offset, length), giving - absolute memory offset in bytes and number of bytes to read - read_shape : tuple - shape with which to interpret memory as read from `segments`. - Interpreting the memory read from `segments` with this shape, and a - dtype, gives an intermediate array - call this ``R`` - post_slicers : tuple - Any new slicing to be applied to the array ``R`` after reading via - `segments` and reshaping via `read_shape`. Slices are in terms of - `read_shape`. If empty, no new slicing to apply - """ - if order not in 'CF': - raise ValueError("order should be one of 'CF'") - sliceobj = canonical_slicers(sliceobj, in_shape) - # order fastest changing first (record reordering) - if order == 'C': - sliceobj = sliceobj[::-1] - in_shape = in_shape[::-1] - # Analyze sliceobj for new read_slicers and fixup post_slicers - # read_slicers are the virtual slices; we don't slice with these, but use - # the slice definitions to read the relevant memory from disk - read_slicers, post_slicers = optimize_read_slicers(sliceobj, in_shape, itemsize, heuristic) - # work out segments corresponding to read_slicers - segments = slicers2segments(read_slicers, in_shape, offset, itemsize) - # Make post_slicers empty if it is the slicing identity operation - if all(s == slice(None) for s in post_slicers): - post_slicers = [] - read_shape = predict_shape(read_slicers, in_shape) - # If reordered, order shape, post_slicers - if order == 'C': - read_shape = read_shape[::-1] - post_slicers = post_slicers[::-1] - return list(segments), tuple(read_shape), tuple(post_slicers) - - -def optimize_read_slicers(sliceobj, in_shape, itemsize, heuristic): - """Calculates slices to read from disk, and apply after reading - - Parameters - ---------- - sliceobj : object - something that can be used to slice an array as in ``arr[sliceobj]``. - Can be assumed to be canonical in the sense of ``canonical_slicers`` - in_shape : sequence - shape of underlying array to be sliced. Array for `in_shape` assumed - to be already in 'F' order. Reorder shape / sliceobj for slicing a 'C' - array before passing to this function. - itemsize : int - element size in array (bytes) - heuristic : callable - function taking slice object, axis length, and stride length as - arguments, returning one of 'full', 'contiguous', None. See - :func:`optimize_slicer`; see :func:`threshold_heuristic` for an - example. - - Returns - ------- - read_slicers : tuple - `sliceobj` maybe rephrased to fill out dimensions that are better read - from disk and later trimmed to their original size with `post_slicers`. - `read_slicers` implies a block of memory to be read from disk. The - actual disk positions come from `slicers2segments` run over - `read_slicers`. Includes any ``newaxis`` dimensions in `sliceobj` - post_slicers : tuple - Any new slicing to be applied to the read array after reading. The - `post_slicers` discard any memory that we read to save time, but that - we don't need for the slice. Include any ``newaxis`` dimension added - by `sliceobj` - """ - read_slicers = [] - post_slicers = [] - real_no = 0 - stride = itemsize - all_full = True - for slicer in sliceobj: - if slicer is None: - read_slicers.append(None) - post_slicers.append(slice(None)) - continue - dim_len = in_shape[real_no] - real_no += 1 - is_last = real_no == len(in_shape) - # make modified sliceobj (to_read, post_slice) - read_slicer, post_slicer = optimize_slicer( - slicer, dim_len, all_full, is_last, stride, heuristic - ) - read_slicers.append(read_slicer) - all_full = all_full and read_slicer == slice(None) - if not isinstance(read_slicer, Integral): - post_slicers.append(post_slicer) - stride *= dim_len - return tuple(read_slicers), tuple(post_slicers) - - -def slicers2segments(read_slicers, in_shape, offset, itemsize): - """Get segments from `read_slicers` given `in_shape` and memory steps - - Parameters - ---------- - read_slicers : object - something that can be used to slice an array as in ``arr[sliceobj]`` - Slice objects can by be assumed canonical as in ``canonical_slicers``, - and positive as in ``_positive_slice`` - in_shape : sequence - shape of underlying array on disk before reading - offset : int - offset of array data in underlying file or memory buffer - itemsize : int - element size in array (in bytes) - - Returns - ------- - segments : list - list of 2 element lists where lists are [offset, length], giving - absolute memory offset in bytes and number of bytes to read - """ - all_full = True - all_segments = [[offset, itemsize]] - stride = itemsize - real_no = 0 - for read_slicer in read_slicers: - if read_slicer is None: - continue - dim_len = in_shape[real_no] - real_no += 1 - is_int = isinstance(read_slicer, Integral) - if not is_int: # slicer is (now) a slice - # make slice full (it will always be positive) - read_slicer = fill_slicer(read_slicer, dim_len) - slice_len = _full_slicer_len(read_slicer) - is_full = read_slicer == slice(0, dim_len, 1) - is_contiguous = not is_int and read_slicer.step == 1 - if all_full and is_contiguous: # full or contiguous - if read_slicer.start != 0: - all_segments[0][0] += stride * read_slicer.start - all_segments[0][1] *= slice_len - else: # Previous or current stuff is not contiguous - if is_int: - for segment in all_segments: - segment[0] += stride * read_slicer - else: # slice object - segments = all_segments - all_segments = [] - for i in range(read_slicer.start, read_slicer.stop, read_slicer.step): - for s in segments: - all_segments.append([s[0] + stride * i, s[1]]) - all_full = all_full and is_full - stride *= dim_len - return all_segments - - -def read_segments(fileobj, segments, n_bytes, lock=None): - """Read `n_bytes` byte data implied by `segments` from `fileobj` - - Parameters - ---------- - fileobj : file-like object - Implements `seek` and `read` - segments : sequence - list of 2 sequences where sequences are (offset, length), giving - absolute file offset in bytes and number of bytes to read - n_bytes : int - total number of bytes that will be read - lock : {None, threading.Lock, lock-like} optional - If provided, used to ensure that paired calls to ``seek`` and ``read`` - cannot be interrupted by another thread accessing the same ``fileobj``. - Each thread which accesses the same file via ``read_segments`` must - share a lock in order to ensure that the file access is thread-safe. - A lock does not need to be provided for single-threaded access. The - default value (``None``) results in a lock-like object (a - ``_NullLock``) which does not do anything. - - Returns - ------- - buffer : buffer object - object implementing buffer protocol, such as byte string or ndarray or - mmap or ctypes ``c_char_array`` - """ - # Make a lock-like thing to make the code below a bit nicer - if lock is None: - lock = _NullLock() - - if len(segments) == 0: - if n_bytes != 0: - raise ValueError('No segments, but non-zero n_bytes') - return b'' - if len(segments) == 1: - offset, length = segments[0] - with lock: - fileobj.seek(offset) - bytes = fileobj.read(length) - if len(bytes) != n_bytes: - raise ValueError('Whoops, not enough data in file') - return bytes - # More than one segment - bytes = mmap(-1, n_bytes) - for offset, length in segments: - with lock: - fileobj.seek(offset) - bytes.write(fileobj.read(length)) - if bytes.tell() != n_bytes: - raise ValueError('Oh dear, n_bytes does not look right') - return bytes - - -def _simple_fileslice(fileobj, sliceobj, shape, dtype, offset=0, order='C', heuristic=None): - """Read all data from `fileobj` into array, then slice with `sliceobj` - - The simplest possible thing; read all the data into the full array, then - slice the full array. - - Parameters - ---------- - fileobj : file-like object - implements ``read`` and ``seek`` - sliceobj : object - something that can be used to slice an array as in ``arr[sliceobj]`` - shape : sequence - shape of full array inside `fileobj` - dtype : dtype object - dtype of array inside `fileobj` - offset : int, optional - offset of array data within `fileobj` - order : {'C', 'F'}, optional - memory layout of array in `fileobj` - heuristic : optional - The routine doesn't use `heuristic`; the parameter is for API - compatibility with :func:`fileslice` - - Returns - ------- - sliced_arr : array - Array in `fileobj` as sliced with `sliceobj` - """ - fileobj.seek(offset) - nbytes = reduce(operator.mul, shape) * dtype.itemsize - bytes = fileobj.read(nbytes) - new_arr = np.ndarray(shape, dtype, buffer=bytes, order=order) - return new_arr[sliceobj] - - -def fileslice( - fileobj, sliceobj, shape, dtype, offset=0, order='C', heuristic=threshold_heuristic, lock=None -): - """Slice array in `fileobj` using `sliceobj` slicer and array definitions - - `fileobj` contains the contiguous binary data for an array ``A`` of shape, - dtype, memory layout `shape`, `dtype`, `order`, with the binary data - starting at file offset `offset`. - - Our job is to return the sliced array ``A[sliceobj]`` in the most efficient - way in terms of memory and time. - - Sometimes it will be quicker to read memory that we will later throw away, - to save time we might lose doing short seeks on `fileobj`. Call these - alternatives: (read + discard); and skip. This routine guesses when to - (read+discard) or skip using the callable `heuristic`, with a default using - a hard threshold for the memory gap large enough to prefer a skip. - - Parameters - ---------- - fileobj : file-like object - file-like object, opened for reading in binary mode. Implements - ``read`` and ``seek``. - sliceobj : object - something that can be used to slice an array as in ``arr[sliceobj]``. - shape : sequence - shape of full array inside `fileobj`. - dtype : dtype specifier - dtype of array inside `fileobj`, or input to ``numpy.dtype`` to specify - array dtype. - offset : int, optional - offset of array data within `fileobj` - order : {'C', 'F'}, optional - memory layout of array in `fileobj`. - heuristic : callable, optional - function taking slice object, axis length, stride length as arguments, - returning one of 'full', 'contiguous', None. See - :func:`optimize_slicer` and see :func:`threshold_heuristic` for an - example. - lock : {None, threading.Lock, lock-like} optional - If provided, used to ensure that paired calls to ``seek`` and ``read`` - cannot be interrupted by another thread accessing the same ``fileobj``. - Each thread which accesses the same file via ``read_segments`` must - share a lock in order to ensure that the file access is thread-safe. - A lock does not need to be provided for single-threaded access. The - default value (``None``) results in a lock-like object (a - ``_NullLock``) which does not do anything. - - Returns - ------- - sliced_arr : array - Array in `fileobj` as sliced with `sliceobj` - """ - if is_fancy(sliceobj): - raise ValueError('Cannot handle fancy indexing') - dtype = np.dtype(dtype) - itemsize = int(dtype.itemsize) - segments, sliced_shape, post_slicers = calc_slicedefs(sliceobj, shape, itemsize, offset, order) - n_bytes = reduce(operator.mul, sliced_shape, 1) * itemsize - arr_data = read_segments(fileobj, segments, n_bytes, lock) - sliced = np.ndarray(sliced_shape, dtype, buffer=arr_data, order=order) - return sliced[post_slicers] - - -def strided_scalar(shape, scalar=0.0): - """Return array shape `shape` where all entries point to value `scalar` - - Parameters - ---------- - shape : sequence - Shape of output array. - scalar : scalar - Scalar value with which to fill array. - - Returns - ------- - strided_arr : array - Array of shape `shape` for which all values == `scalar`, built by - setting all strides of `strided_arr` to 0, so the scalar is broadcast - out to the full array `shape`. `strided_arr` is flagged as not - `writeable`. - - The array is set read-only to avoid a numpy error when broadcasting - - see https://github.com/numpy/numpy/issues/6491 - """ - shape = tuple(shape) - scalar = np.array(scalar) - strides = [0] * len(shape) - strided_scalar = np.lib.stride_tricks.as_strided(scalar, shape, strides) - strided_scalar.flags.writeable = False - return strided_scalar diff --git a/nibabel/fileutils.py b/nibabel/fileutils.py deleted file mode 100644 index 1defbc62f7..0000000000 --- a/nibabel/fileutils.py +++ /dev/null @@ -1,60 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -# ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -# ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Utilities for reading and writing to binary file formats""" - - -def read_zt_byte_strings(fobj, n_strings=1, bufsize=1024): - """Read zero-terminated byte strings from a file object `fobj` - - Returns byte strings with terminal zero stripped. - - Found strings can be of any length. - - The file position of `fobj` on exit will be at the byte after the terminal - 0 of the final read byte string. - - Parameters - ---------- - f : fileobj - File object to use. Should implement ``read``, returning byte objects, - and ``seek(n, 1)`` to seek from current file position. - n_strings : int, optional - Number of byte strings to return - bufsize: int, optional - Define chunk size to load from file while searching for zero terminals. - We load this many bytes at a time from the file, but the returned - strings can be longer than `bufsize`. - - Returns - ------- - byte_strings : list - List of byte strings, where strings do not include the terminal 0 - """ - byte_strings = [] - trailing = b'' - while True: - buf = fobj.read(bufsize) - eof = len(buf) < bufsize # end of file - zt_strings = buf.split(b'\x00') - if len(zt_strings) > 1: # At least one 0 - byte_strings += [trailing + zt_strings[0]] + zt_strings[1:-1] - trailing = zt_strings[-1] - else: # No 0 - trailing += zt_strings[0] - n_found = len(byte_strings) - if eof or n_found >= n_strings: - break - if n_found < n_strings: - raise ValueError(f'Expected {n_strings} strings, found {n_found}') - n_extra = n_found - n_strings - leftover_strings = byte_strings[n_strings:] + [trailing] - # Add number of extra strings to account for lost terminal 0s - extra_bytes = sum(len(bs) for bs in leftover_strings) + n_extra - fobj.seek(-extra_bytes, 1) # seek back from current position - return byte_strings[:n_strings] diff --git a/nibabel/freesurfer/__init__.py b/nibabel/freesurfer/__init__.py deleted file mode 100644 index 1ab3859756..0000000000 --- a/nibabel/freesurfer/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -"""Reading functions for freesurfer files""" - -from .io import ( - read_annot, - read_geometry, - read_label, - read_morph_data, - write_annot, - write_geometry, - write_morph_data, -) -from .mghformat import MGHImage, load, save diff --git a/nibabel/freesurfer/io.py b/nibabel/freesurfer/io.py deleted file mode 100644 index 5b3f6a3664..0000000000 --- a/nibabel/freesurfer/io.py +++ /dev/null @@ -1,619 +0,0 @@ -"""Read / write FreeSurfer geometry, morphometry, label, annotation formats""" - -import getpass -import time -import warnings -from collections import OrderedDict - -import numpy as np - -from ..openers import Opener - -_ANNOT_DT = '>i4' -"""Data type for Freesurfer `.annot` files. - -Used by :func:`read_annot` and :func:`write_annot`. All data (apart from -strings) in an `.annot` file is stored as big-endian int32. -""" - - -def _fread3(fobj): - """Read a 3-byte int from an open binary file object - - Parameters - ---------- - fobj : file - File descriptor - - Returns - ------- - n : int - A 3 byte int - """ - b1, b2, b3 = np.fromfile(fobj, '>u1', 3).astype(np.int64) - return (b1 << 16) + (b2 << 8) + b3 - - -def _fread3_many(fobj, n): - """Read 3-byte ints from an open binary file object. - - Parameters - ---------- - fobj : file - File descriptor - - Returns - ------- - out : 1D array - An array of 3 byte int - """ - b1, b2, b3 = np.fromfile(fobj, '>u1', 3 * n).reshape(-1, 3).astype(int).T - return (b1 << 16) + (b2 << 8) + b3 - - -def _read_volume_info(fobj): - """Helper for reading the footer from a surface file.""" - volume_info = OrderedDict() - head = np.fromfile(fobj, '>i4', 1) - if not np.array_equal(head, [20]): # Read two bytes more - head = np.concatenate([head, np.fromfile(fobj, '>i4', 2)]) - if not np.array_equal(head, [2, 0, 20]): - warnings.warn('Unknown extension code.') - return volume_info - - volume_info['head'] = head - for key in ('valid', 'filename', 'volume', 'voxelsize', 'xras', 'yras', 'zras', 'cras'): - pair = fobj.readline().decode('utf-8').split('=') - if pair[0].strip() != key or len(pair) != 2: - raise OSError('Error parsing volume info.') - if key in ('valid', 'filename'): - volume_info[key] = pair[1].strip() - elif key == 'volume': - volume_info[key] = np.array(pair[1].split(), int) - else: - volume_info[key] = np.array(pair[1].split(), float) - # Ignore the rest - return volume_info - - -def _pack_rgb(rgb): - """Pack an RGB sequence into a single integer. - - Used by :func:`read_annot` and :func:`write_annot` to generate - "annotation values" for a Freesurfer ``.annot`` file. - - Parameters - ---------- - rgb : ndarray, shape (n, 3) - RGB colors - - Returns - ------- - out : ndarray, shape (n, 1) - Annotation values for each color. - """ - bitshifts = 2 ** np.array([[0], [8], [16]], dtype=rgb.dtype) - return rgb.dot(bitshifts) - - -def read_geometry(filepath, read_metadata=False, read_stamp=False): - """Read a triangular format Freesurfer surface mesh. - - Parameters - ---------- - filepath : str - Path to surface file. - read_metadata : bool, optional - If True, read and return metadata as key-value pairs. - - Valid keys: - - * 'head' : array of int - * 'valid' : str - * 'filename' : str - * 'volume' : array of int, shape (3,) - * 'voxelsize' : array of float, shape (3,) - * 'xras' : array of float, shape (3,) - * 'yras' : array of float, shape (3,) - * 'zras' : array of float, shape (3,) - * 'cras' : array of float, shape (3,) - - read_stamp : bool, optional - Return the comment from the file - - Returns - ------- - coords : numpy array - nvtx x 3 array of vertex (x, y, z) coordinates. - faces : numpy array - nfaces x 3 array of defining mesh triangles. - volume_info : OrderedDict - Returned only if `read_metadata` is True. Key-value pairs found in the - geometry file. - create_stamp : str - Returned only if `read_stamp` is True. The comment added by the - program that saved the file. - """ - volume_info = OrderedDict() - - TRIANGLE_MAGIC = 16777214 - QUAD_MAGIC = 16777215 - NEW_QUAD_MAGIC = 16777213 - with open(filepath, 'rb') as fobj: - magic = _fread3(fobj) - if magic in (QUAD_MAGIC, NEW_QUAD_MAGIC): # Quad file - nvert = _fread3(fobj) - nquad = _fread3(fobj) - (fmt, div) = ('>i2', 100.0) if magic == QUAD_MAGIC else ('>f4', 1.0) - coords = np.fromfile(fobj, fmt, nvert * 3).astype(np.float64) / div - coords = coords.reshape(-1, 3) - quads = _fread3_many(fobj, nquad * 4) - quads = quads.reshape(nquad, 4) - # - # Face splitting follows - # - faces = np.zeros((2 * nquad, 3), dtype=int) - nface = 0 - for quad in quads: - if (quad[0] % 2) == 0: - faces[nface] = quad[0], quad[1], quad[3] - nface += 1 - faces[nface] = quad[2], quad[3], quad[1] - nface += 1 - else: - faces[nface] = quad[0], quad[1], quad[2] - nface += 1 - faces[nface] = quad[0], quad[2], quad[3] - nface += 1 - - elif magic == TRIANGLE_MAGIC: # Triangle file - create_stamp = fobj.readline().rstrip(b'\n').decode('utf-8') - fobj.readline() - vnum = np.fromfile(fobj, '>i4', 1)[0] - fnum = np.fromfile(fobj, '>i4', 1)[0] - coords = np.fromfile(fobj, '>f4', vnum * 3).reshape(vnum, 3) - faces = np.fromfile(fobj, '>i4', fnum * 3).reshape(fnum, 3) - - if read_metadata: - volume_info = _read_volume_info(fobj) - else: - raise ValueError('File does not appear to be a Freesurfer surface') - - coords = coords.astype(np.float64) # XXX: due to mayavi bug on mac 32bits - - ret = (coords, faces) - if read_metadata: - if len(volume_info) == 0: - warnings.warn('No volume information contained in the file') - ret += (volume_info,) - if read_stamp: - ret += (create_stamp,) - - return ret - - -def write_geometry(filepath, coords, faces, create_stamp=None, volume_info=None): - """Write a triangular format Freesurfer surface mesh. - - Parameters - ---------- - filepath : str - Path to surface file. - coords : numpy array - nvtx x 3 array of vertex (x, y, z) coordinates. - faces : numpy array - nfaces x 3 array of defining mesh triangles. - create_stamp : str, optional - User/time stamp (default: "created by on ") - volume_info : dict-like or None, optional - Key-value pairs to encode at the end of the file. - - Valid keys: - - * 'head' : array of int - * 'valid' : str - * 'filename' : str - * 'volume' : array of int, shape (3,) - * 'voxelsize' : array of float, shape (3,) - * 'xras' : array of float, shape (3,) - * 'yras' : array of float, shape (3,) - * 'zras' : array of float, shape (3,) - * 'cras' : array of float, shape (3,) - - """ - magic_bytes = np.array([255, 255, 254], dtype=np.uint8) - - if create_stamp is None: - create_stamp = f'created by {getpass.getuser()} on {time.ctime()}' - - with open(filepath, 'wb') as fobj: - magic_bytes.tofile(fobj) - fobj.write((f'{create_stamp}\n\n').encode()) - - np.array([coords.shape[0], faces.shape[0]], dtype='>i4').tofile(fobj) - - # Coerce types, just to be safe - coords.astype('>f4').reshape(-1).tofile(fobj) - faces.astype('>i4').reshape(-1).tofile(fobj) - - # Add volume info, if given - if volume_info is not None and len(volume_info) > 0: - fobj.write(_serialize_volume_info(volume_info)) - - -def read_morph_data(filepath): - """Read a Freesurfer morphometry data file. - - This function reads in what Freesurfer internally calls "curv" file types, - (e.g. ?h. curv, ?h.thickness), but as that has the potential to cause - confusion where "curv" also refers to the surface curvature values, - we refer to these files as "morphometry" files with PySurfer. - - Parameters - ---------- - filepath : str - Path to morphometry file - - Returns - ------- - curv : numpy array - Vector representation of surface morpometry values - """ - with open(filepath, 'rb') as fobj: - magic = _fread3(fobj) - if magic == 16777215: - vnum = np.fromfile(fobj, '>i4', 3)[0] - curv = np.fromfile(fobj, '>f4', vnum) - else: - vnum = magic - _fread3(fobj) - curv = np.fromfile(fobj, '>i2', vnum) / 100 - return curv - - -def write_morph_data(file_like, values, fnum=0): - """Write Freesurfer morphometry data `values` to file-like `file_like` - - Equivalent to FreeSurfer's `write_curv.m`_ - - See also: - http://www.grahamwideman.com/gw/brain/fs/surfacefileformats.htm#CurvNew - - .. _write_curv.m: \ - https://github.com/neurodebian/freesurfer/blob/debian-sloppy/matlab/write_curv.m - - Parameters - ---------- - file_like : file-like - String containing path of file to be written, or file-like object, open - in binary write (`'wb'` mode, implementing the `write` method) - values : array-like - Surface morphometry values. Shape must be (N,), (N, 1), (1, N) or (N, - 1, 1) - fnum : int, optional - Number of faces in the associated surface. - """ - magic_bytes = np.array([255, 255, 255], dtype=np.uint8) - - vector = np.asarray(values) - vnum = np.prod(vector.shape) - if vector.shape not in ((vnum,), (vnum, 1), (1, vnum), (vnum, 1, 1)): - raise ValueError('Invalid shape: argument values must be a vector') - - i4info = np.iinfo('i4') - if vnum > i4info.max: - raise ValueError('Too many values for morphometry file') - if not i4info.min <= fnum <= i4info.max: - raise ValueError(f'Argument fnum must be between {i4info.min} and {i4info.max}') - - with Opener(file_like, 'wb') as fobj: - fobj.write(magic_bytes) - - # vertex count, face count (unused), vals per vertex (only 1 supported) - fobj.write(np.array([vnum, fnum, 1], dtype='>i4')) - - fobj.write(vector.astype('>f4')) - - -def read_annot(filepath, orig_ids=False): - """Read in a Freesurfer annotation from a ``.annot`` file. - - An ``.annot`` file contains a sequence of vertices with a label (also known - as an "annotation value") associated with each vertex, and then a sequence - of colors corresponding to each label. - - Annotation file format versions 1 and 2 are supported, corresponding to - the "old-style" and "new-style" color table layout. - - Note that the output color table ``ctab`` is in RGBT form, where T - (transparency) is 255 - alpha. - - See: - * https://surfer.nmr.mgh.harvard.edu/fswiki/LabelsClutsAnnotationFiles#Annotation - * https://github.com/freesurfer/freesurfer/blob/dev/matlab/read_annotation.m - * https://github.com/freesurfer/freesurfer/blob/8b88b34/utils/colortab.c - - Parameters - ---------- - filepath : str - Path to annotation file. - orig_ids : bool - Whether to return the vertex ids as stored in the annotation - file or the positional colortable ids. With orig_ids=False - vertices with no id have an id set to -1. - - Returns - ------- - labels : ndarray, shape (n_vertices,) - Annotation id at each vertex. If a vertex does not belong - to any label and orig_ids=False, its id will be set to -1. - ctab : ndarray, shape (n_labels, 5) - RGBT + label id colortable array. - names : list of bytes - The names of the labels. The length of the list is n_labels. - """ - with open(filepath, 'rb') as fobj: - dt = _ANNOT_DT - - # number of vertices - vnum = np.fromfile(fobj, dt, 1)[0] - - # vertex ids + annotation values - data = np.fromfile(fobj, dt, vnum * 2).reshape(vnum, 2) - labels = data[:, 1] - - # is there a color table? - ctab_exists = np.fromfile(fobj, dt, 1)[0] - if not ctab_exists: - raise Exception('Color table not found in annotation file') - - # in old-format files, the next field will contain the number of - # entries in the color table. In new-format files, this must be - # equal to -2 - n_entries = np.fromfile(fobj, dt, 1)[0] - - # We've got an old-format .annot file. - if n_entries > 0: - ctab, names = _read_annot_ctab_old_format(fobj, n_entries) - # We've got a new-format .annot file - else: - ctab, names = _read_annot_ctab_new_format(fobj, -n_entries) - - # generate annotation values for each LUT entry - ctab[:, [4]] = _pack_rgb(ctab[:, :3]) - - if not orig_ids: - ord = np.argsort(ctab[:, -1]) - mask = labels != 0 - labels[~mask] = -1 - labels[mask] = ord[np.searchsorted(ctab[ord, -1], labels[mask])] - return labels, ctab, names - - -def _read_annot_ctab_old_format(fobj, n_entries): - """Read in an old-style Freesurfer color table from `fobj`. - - Note that the output color table ``ctab`` is in RGBT form, where T - (transparency) is 255 - alpha. - - This function is used by :func:`read_annot`. - - Parameters - ---------- - - fobj : file-like - Open file handle to a Freesurfer `.annot` file, with seek point - at the beginning of the color table data. - n_entries : int - Number of entries in the color table. - - Returns - ------- - - ctab : ndarray, shape (n_entries, 5) - RGBT colortable array - the last column contains all zeros. - names : list of str - The names of the labels. The length of the list is n_entries. - """ - assert hasattr(fobj, 'read') - - dt = _ANNOT_DT - # orig_tab string length + string - length = np.fromfile(fobj, dt, 1)[0] - orig_tab = np.fromfile(fobj, '>c', length) - orig_tab = orig_tab[:-1] - names = list() - ctab = np.zeros((n_entries, 5), dt) - for i in range(n_entries): - # structure name length + string - name_length = np.fromfile(fobj, dt, 1)[0] - name = np.fromfile(fobj, f'|S{name_length}', 1)[0] - names.append(name) - # read RGBT for this entry - ctab[i, :4] = np.fromfile(fobj, dt, 4) - - return ctab, names - - -def _read_annot_ctab_new_format(fobj, ctab_version): - """Read in a new-style Freesurfer color table from `fobj`. - - Note that the output color table ``ctab`` is in RGBT form, where T - (transparency) is 255 - alpha. - - This function is used by :func:`read_annot`. - - Parameters - ---------- - - fobj : file-like - Open file handle to a Freesurfer `.annot` file, with seek point - at the beginning of the color table data. - ctab_version : int - Color table format version - must be equal to 2 - - Returns - ------- - - ctab : ndarray, shape (n_labels, 5) - RGBT colortable array - the last column contains all zeros. - names : list of str - The names of the labels. The length of the list is n_labels. - """ - assert hasattr(fobj, 'read') - - dt = _ANNOT_DT - # This code works with a file version == 2, nothing else - if ctab_version != 2: - raise Exception(f'Unrecognised .annot file version ({ctab_version})') - # maximum LUT index present in the file - max_index = np.fromfile(fobj, dt, 1)[0] - ctab = np.zeros((max_index, 5), dt) - # orig_tab string length + string - length = np.fromfile(fobj, dt, 1)[0] - np.fromfile(fobj, f'|S{length}', 1)[0] # Orig table path - # number of LUT entries present in the file - entries_to_read = np.fromfile(fobj, dt, 1)[0] - names = list() - for _ in range(entries_to_read): - # index of this entry - idx = np.fromfile(fobj, dt, 1)[0] - # structure name length + string - name_length = np.fromfile(fobj, dt, 1)[0] - name = np.fromfile(fobj, f'|S{name_length}', 1)[0] - names.append(name) - # RGBT - ctab[idx, :4] = np.fromfile(fobj, dt, 4) - - return ctab, names - - -def write_annot(filepath, labels, ctab, names, fill_ctab=True): - """Write out a "new-style" Freesurfer annotation file. - - Note that the color table ``ctab`` is in RGBT form, where T (transparency) - is 255 - alpha. - - See: - * https://surfer.nmr.mgh.harvard.edu/fswiki/LabelsClutsAnnotationFiles#Annotation - * https://github.com/freesurfer/freesurfer/blob/dev/matlab/write_annotation.m - * https://github.com/freesurfer/freesurfer/blob/8b88b34/utils/colortab.c - - Parameters - ---------- - filepath : str - Path to annotation file to be written - labels : ndarray, shape (n_vertices,) - Annotation id at each vertex. - ctab : ndarray, shape (n_labels, 5) - RGBT + label id colortable array. - names : list of str - The names of the labels. The length of the list is n_labels. - fill_ctab : {True, False} optional - If True, the annotation values for each vertex are automatically - generated. In this case, the provided `ctab` may have shape - (n_labels, 4) or (n_labels, 5) - if the latter, the final column is - ignored. - """ - with open(filepath, 'wb') as fobj: - dt = _ANNOT_DT - vnum = len(labels) - - def write(num, dtype=dt): - np.array([num], dtype).tofile(fobj) - - def write_string(s): - s = (s if isinstance(s, bytes) else s.encode()) + b'\x00' - write(len(s)) - write(s, dtype=f'|S{len(s)}') - - # Generate annotation values for each ctab entry - if fill_ctab: - ctab = np.hstack((ctab[:, :4], _pack_rgb(ctab[:, :3]))) - elif not np.array_equal(ctab[:, [4]], _pack_rgb(ctab[:, :3])): - warnings.warn(f'Annotation values in {filepath} will be incorrect') - - # vtxct - write(vnum) - - # convert labels into coded CLUT values - clut_labels = ctab[:, -1][labels] - clut_labels[np.where(labels == -1)] = 0 - - # vno, label - data = np.vstack((np.array(range(vnum)), clut_labels)).T.astype(dt) - data.tofile(fobj) - - # tag - write(1) - - # ctabversion - write(-2) - - # maxstruc - write(max(np.max(labels) + 1, ctab.shape[0])) - - # File of LUT is unknown. - write_string('NOFILE') - - # num_entries - write(ctab.shape[0]) - - for ind, (clu, name) in enumerate(zip(ctab, names)): - write(ind) - write_string(name) - for val in clu[:-1]: - write(val) - - -def read_label(filepath, read_scalars=False): - """Load in a Freesurfer .label file. - - Parameters - ---------- - filepath : str - Path to label file. - read_scalars : bool, optional - If True, read and return scalars associated with each vertex. - - Returns - ------- - label_array : numpy array - Array with indices of vertices included in label. - scalar_array : numpy array (floats) - Only returned if `read_scalars` is True. Array of scalar data for each - vertex. - """ - label_array = np.loadtxt(filepath, dtype=int, skiprows=2, usecols=[0]) - if read_scalars: - scalar_array = np.loadtxt(filepath, skiprows=2, usecols=[-1]) - return label_array, scalar_array - return label_array - - -def _serialize_volume_info(volume_info): - """Helper for serializing the volume info.""" - keys = ['head', 'valid', 'filename', 'volume', 'voxelsize', 'xras', 'yras', 'zras', 'cras'] - diff = set(volume_info.keys()).difference(keys) - if len(diff) > 0: - raise ValueError(f'Invalid volume info: {diff.pop()}.') - - strings = list() - for key in keys: - if key == 'head': - if not ( - np.array_equal(volume_info[key], [20]) - or np.array_equal(volume_info[key], [2, 0, 20]) - ): - warnings.warn('Unknown extension code.') - strings.append(np.array(volume_info[key], dtype='>i4').tobytes()) - elif key in ('valid', 'filename'): - val = volume_info[key] - strings.append(f'{key} = {val}\n'.encode()) - elif key == 'volume': - val = volume_info[key] - strings.append(f'{key} = {val[0]} {val[1]} {val[2]}\n'.encode()) - else: - val = volume_info[key] - strings.append(f'{key:6s} = {val[0]:.10g} {val[1]:.10g} {val[2]:.10g}\n'.encode()) - return b''.join(strings) diff --git a/nibabel/freesurfer/mghformat.py b/nibabel/freesurfer/mghformat.py deleted file mode 100644 index 1c97fd566c..0000000000 --- a/nibabel/freesurfer/mghformat.py +++ /dev/null @@ -1,602 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Header and image reading / writing functions for MGH image format - -Author: Krish Subramaniam -""" - -from os.path import splitext - -import numpy as np - -from ..affines import from_matvec, voxel_sizes -from ..arrayproxy import ArrayProxy, reshape_dataobj -from ..batteryrunners import BatteryRunner, Report -from ..filebasedimages import SerializableImage -from ..fileholders import FileHolder -from ..filename_parser import _stringify_path -from ..openers import ImageOpener -from ..spatialimages import HeaderDataError, SpatialHeader, SpatialImage -from ..volumeutils import Recoder, array_from_file, array_to_file, endian_codes -from ..wrapstruct import LabeledWrapStruct - -# mgh header -# See https://surfer.nmr.mgh.harvard.edu/fswiki/FsTutorial/MghFormat -DATA_OFFSET = 284 -# Note that mgh data is strictly big endian ( hence the > sign ) -# fmt: off -header_dtd = [ - ('version', '>i4'), # 0; must be 1 - ('dims', '>i4', (4,)), # 4; width, height, depth, nframes - ('type', '>i4'), # 20; data type - ('dof', '>i4'), # 24; degrees of freedom - ('goodRASFlag', '>i2'), # 28; Mdc, Pxyz_c fields valid - ('delta', '>f4', (3,)), # 30; zooms (X, Y, Z) - ('Mdc', '>f4', (3, 3)), # 42; TRANSPOSE of direction cosine matrix - ('Pxyz_c', '>f4', (3,)), # 78; mm from (0, 0, 0) RAS to vol center -] -# Optional footer. Also has more stuff after this, optionally -footer_dtd = [ - ('tr', '>f4'), # 0; repetition time - ('flip_angle', '>f4'), # 4; flip angle - ('te', '>f4'), # 8; echo time - ('ti', '>f4'), # 12; inversion time - ('fov', '>f4'), # 16; field of view (unused) -] -# fmt: on - -header_dtype = np.dtype(header_dtd) -footer_dtype = np.dtype(footer_dtd) -hf_dtype = np.dtype(header_dtd + footer_dtd) - -# caveat: Note that it's ambiguous to get the code given the bytespervoxel -# caveat 2: Note that the bytespervox you get is in str ( not an int) -# FreeSurfer historically defines codes 0-10 [1], but only a subset is well supported. -# Here we use FreeSurfer's MATLAB loader [2] as an indication of current support. -# [1] https://github.com/freesurfer/freesurfer/blob/v8.0.0/include/mri.h#L53-L63 -# [2] https://github.com/freesurfer/freesurfer/blob/v8.0.0/matlab/load_mgh.m#L195-L207 -_dtdefs = ( # code, conversion function, dtype, bytes per voxel - (0, 'uint8', '>u1', '1', 'MRI_UCHAR', np.uint8, np.dtype('u1'), np.dtype('>u1')), - (1, 'int32', '>i4', '4', 'MRI_INT', np.int32, np.dtype('i4'), np.dtype('>i4')), - (3, 'float', '>f4', '4', 'MRI_FLOAT', np.float32, np.dtype('f4'), np.dtype('>f4')), - (4, 'int16', '>i2', '2', 'MRI_SHORT', np.int16, np.dtype('i2'), np.dtype('>i2')), - (10, 'uint16', '>u2', '2', 'MRI_USHRT', np.uint16, np.dtype('u2'), np.dtype('>u2')), -) - -# make full code alias bank, including dtype column -data_type_codes = Recoder( - _dtdefs, - fields=( - 'code', - 'label', - 'dtype', - 'bytespervox', - 'mritype', - 'np_dtype1', - 'np_dtype2', - 'numpy_dtype', - ), -) - - -class MGHError(Exception): - """Exception for MGH format related problems. - - To be raised whenever MGH is not happy, or we are not happy with - MGH. - """ - - -class MGHHeader(LabeledWrapStruct, SpatialHeader): - """Class for MGH format header - - The header also consists of the footer data which MGH places after the data - chunk. - """ - - # Copies of module-level definitions - template_dtype = hf_dtype - _hdrdtype = header_dtype - _ftrdtype = footer_dtype - _data_type_codes = data_type_codes - - def __init__(self, binaryblock=None, check=True): - """Initialize header from binary data block - - Parameters - ---------- - binaryblock : {None, string} optional - binary block to set into header. By default, None, in - which case we insert the default empty header block - check : bool, optional - Whether to check content of header in initialization. - Default is True. - """ - min_size = self._hdrdtype.itemsize - full_size = self.template_dtype.itemsize - if binaryblock is not None and len(binaryblock) >= min_size: - # Right zero-pad or truncate binaryblock to appropriate size - # Footer is optional and may contain variable-length text fields, - # so limit to fixed fields - binaryblock = binaryblock[:full_size] + b'\x00' * (full_size - len(binaryblock)) - super().__init__(binaryblock=binaryblock, endianness='big', check=False) - if not self._structarr['goodRASFlag']: - self._set_affine_default() - if check: - self.check_fix() - - @staticmethod - def chk_version(hdr, fix=False): - rep = Report() - if hdr['version'] != 1: - rep = Report(HeaderDataError, 40) - rep.problem_msg = 'Unknown MGH format version' - if fix: - hdr['version'] = 1 - return hdr, rep - - @classmethod - def _get_checks(klass): - return (klass.chk_version,) - - @classmethod - def from_header(klass, header=None, check=True): - """Class method to create MGH header from another MGH header""" - # own type, return copy - if type(header) == klass: - obj = header.copy() - if check: - obj.check_fix() - return obj - # not own type, make fresh header instance - obj = klass(check=check) - return obj - - @classmethod - def from_fileobj(klass, fileobj, check=True): - """ - classmethod for loading a MGH fileobject - """ - # We need the following hack because MGH data stores header information - # after the data chunk too. We read the header initially, deduce the - # dimensions from the header, skip over and then read the footer - # information - hdr_str = fileobj.read(klass._hdrdtype.itemsize) - hdr_str_to_np = np.ndarray(shape=(), dtype=klass._hdrdtype, buffer=hdr_str) - if not np.all(hdr_str_to_np['dims']): - raise MGHError('Dimensions of the data should be non-zero') - tp = int(hdr_str_to_np['type']) - fileobj.seek( - DATA_OFFSET - + int(klass._data_type_codes.bytespervox[tp]) * np.prod(hdr_str_to_np['dims']) - ) - ftr_str = fileobj.read(klass._ftrdtype.itemsize) - return klass(hdr_str + ftr_str, check=check) - - def get_affine(self): - """Get the affine transform from the header information. - - MGH format doesn't store the transform directly. Instead it's gleaned - from the zooms ( delta ), direction cosines ( Mdc ), RAS centers ( - Pxyz_c ) and the dimensions. - """ - hdr = self._structarr - MdcD = hdr['Mdc'].T * hdr['delta'] - vol_center = MdcD.dot(hdr['dims'][:3]) / 2 - return from_matvec(MdcD, hdr['Pxyz_c'] - vol_center) - - # For compatibility with nifti (multiple affines) - get_best_affine = get_affine - - def get_vox2ras(self): - """return the get_affine()""" - return self.get_affine() - - def get_vox2ras_tkr(self): - """Get the vox2ras-tkr transform. See "Torig" here: - https://surfer.nmr.mgh.harvard.edu/fswiki/CoordinateSystems - """ - ds = self._structarr['delta'] - ns = self._structarr['dims'][:3] * ds / 2.0 - v2rtkr = np.array( - [ - [-ds[0], 0, 0, ns[0]], - [0, 0, ds[2], -ns[2]], - [0, -ds[1], 0, ns[1]], - [0, 0, 0, 1], - ], - dtype=np.float32, - ) - return v2rtkr - - def get_ras2vox(self): - """return the inverse get_affine()""" - return np.linalg.inv(self.get_affine()) - - def get_data_dtype(self): - """Get numpy dtype for MGH data - - For examples see ``set_data_dtype`` - """ - code = int(self._structarr['type']) - dtype = self._data_type_codes.numpy_dtype[code] - return dtype - - def set_data_dtype(self, datatype): - """Set numpy dtype for data from code or dtype or type""" - try: - code = self._data_type_codes[datatype] - except KeyError: - raise MGHError(f'datatype dtype "{datatype}" not recognized') - self._structarr['type'] = code - - def _ndims(self): - """Get dimensionality of data - - MGH does not encode dimensionality explicitly, so an image where the - fourth dimension is 1 is treated as three-dimensional. - - Returns - ------- - ndims : 3 or 4 - """ - return 3 + (self._structarr['dims'][3] > 1) - - def get_zooms(self): - """Get zooms from header - - Returns the spacing of voxels in the x, y, and z dimensions. - For four-dimensional files, a fourth zoom is included, equal to the - repetition time (TR) in ms (see `The MGH/MGZ Volume Format - `_). - - To access only the spatial zooms, use `hdr['delta']`. - - Returns - ------- - z : tuple - tuple of header zoom values - - .. _mghformat: https://surfer.nmr.mgh.harvard.edu/fswiki/FsTutorial/MghFormat#line-82 - """ - # Do not return time zoom (TR) if 3D image - tzoom = (self['tr'],) if self._ndims() > 3 else () - return tuple(self._structarr['delta']) + tzoom - - def set_zooms(self, zooms): - """Set zooms into header fields - - Sets the spacing of voxels in the x, y, and z dimensions. - For four-dimensional files, a temporal zoom (repetition time, or TR, in - ms) may be provided as a fourth sequence element. - - Parameters - ---------- - zooms : sequence - sequence of floats specifying spatial and (optionally) temporal - zooms - """ - hdr = self._structarr - zooms = np.asarray(zooms) - ndims = self._ndims() - if len(zooms) > ndims: - raise HeaderDataError(f'Expecting {ndims} zoom values') - if np.any(zooms[:3] <= 0): - raise HeaderDataError( - f'Spatial (first three) zooms must be positive; got {tuple(zooms[:3])}' - ) - hdr['delta'] = zooms[:3] - if len(zooms) == 4: - if zooms[3] < 0: - raise HeaderDataError(f'TR must be non-negative; got {zooms[3]}') - hdr['tr'] = zooms[3] - - def get_data_shape(self): - """Get shape of data""" - shape = tuple(self._structarr['dims']) - # If last dimension (nframes) is 1, remove it because - # we want to maintain 3D and it's redundant - if shape[3] == 1: - shape = shape[:3] - return shape - - def set_data_shape(self, shape): - """Set shape of data - - Parameters - ---------- - shape : sequence - sequence of integers specifying data array shape - """ - shape = tuple(shape) - if len(shape) > 4: - raise ValueError('Shape may be at most 4 dimensional') - self._structarr['dims'] = shape + (1,) * (4 - len(shape)) - self._structarr['delta'] = 1 - - def get_data_bytespervox(self): - """Get the number of bytes per voxel of the data""" - return int(self._data_type_codes.bytespervox[int(self._structarr['type'])]) - - def get_data_size(self): - """Get the number of bytes the data chunk occupies.""" - return self.get_data_bytespervox() * np.prod(self._structarr['dims']) - - def get_data_offset(self): - """Return offset into data file to read data""" - return DATA_OFFSET - - def get_footer_offset(self): - """Return offset where the footer resides. - Occurs immediately after the data chunk. - """ - return self.get_data_offset() + self.get_data_size() - - def data_from_fileobj(self, fileobj): - """Read data array from `fileobj` - - Parameters - ---------- - fileobj : file-like - Must be open, and implement ``read`` and ``seek`` methods - - Returns - ------- - arr : ndarray - data array - """ - dtype = self.get_data_dtype() - shape = self.get_data_shape() - offset = self.get_data_offset() - return array_from_file(shape, dtype, fileobj, offset) - - def get_slope_inter(self): - """MGH format does not do scaling?""" - return None, None - - @classmethod - def guessed_endian(klass, mapping): - """MGHHeader data must be big-endian""" - return '>' - - @classmethod - def default_structarr(klass, endianness=None): - """Return header data for empty header - - Ignores byte order; always big endian - """ - if endianness is not None and endian_codes[endianness] != '>': - raise ValueError('MGHHeader must always be big endian') - structarr = super().default_structarr(endianness=endianness) - structarr['version'] = 1 - structarr['dims'] = 1 - structarr['type'] = 3 - structarr['goodRASFlag'] = 1 - structarr['delta'] = 1 - structarr['Mdc'] = [[-1, 0, 0], [0, 0, 1], [0, -1, 0]] - return structarr - - def _set_affine_default(self): - """If goodRASFlag is 0, set the default affine""" - self._structarr['goodRASFlag'] = 1 - self._structarr['delta'] = 1 - self._structarr['Mdc'] = [[-1, 0, 0], [0, 0, 1], [0, -1, 0]] - self._structarr['Pxyz_c'] = 0 - - def writehdr_to(self, fileobj): - """Write header to fileobj - - Write starts at the beginning. - - Parameters - ---------- - fileobj : file-like object - Should implement ``write`` and ``seek`` method - - Returns - ------- - None - """ - hdr_nofooter = np.ndarray((), dtype=self._hdrdtype, buffer=self.binaryblock) - # goto the very beginning of the file-like obj - fileobj.seek(0) - fileobj.write(hdr_nofooter.tobytes()) - - def writeftr_to(self, fileobj): - """Write footer to fileobj - - Footer data is located after the data chunk. So move there and write. - - Parameters - ---------- - fileobj : file-like object - Should implement ``write`` and ``seek`` method - - Returns - ------- - None - """ - ftr_loc_in_hdr = len(self.binaryblock) - self._ftrdtype.itemsize - ftr_nd = np.ndarray( - (), dtype=self._ftrdtype, buffer=self.binaryblock, offset=ftr_loc_in_hdr - ) - fileobj.seek(self.get_footer_offset()) - fileobj.write(ftr_nd.tobytes()) - - def copy(self): - """Return copy of structure""" - return self.__class__(self.binaryblock, check=False) - - def as_byteswapped(self, endianness=None): - """Return new object with given ``endianness`` - - If big endian, returns a copy of the object. Otherwise raises ValueError. - - Parameters - ---------- - endianness : None or string, optional - endian code to which to swap. None means swap from current - endianness, and is the default - - Returns - ------- - wstr : ``MGHHeader`` - ``MGHHeader`` object - - """ - if endianness is None or endian_codes[endianness] != '>': - raise ValueError('Cannot byteswap MGHHeader - must always be big endian') - return self.copy() - - @classmethod - def diagnose_binaryblock(klass, binaryblock, endianness=None): - if endianness is not None and endian_codes[endianness] != '>': - raise ValueError('MGHHeader must always be big endian') - wstr = klass(binaryblock, check=False) - battrun = BatteryRunner(klass._get_checks()) - reports = battrun.check_only(wstr) - return '\n'.join([report.message for report in reports if report.message]) - - -class MGHImage(SpatialImage, SerializableImage): - """Class for MGH format image""" - - header_class = MGHHeader - header: MGHHeader - valid_exts = ('.mgh', '.mgz') - # Register that .mgz extension signals gzip compression - ImageOpener.compress_ext_map['.mgz'] = ImageOpener.gz_def - files_types = (('image', '.mgh'),) - _compressed_suffixes = () - - makeable = True - rw = True - - ImageArrayProxy = ArrayProxy - - def __init__(self, dataobj, affine, header=None, extra=None, file_map=None): - shape = dataobj.shape - if len(shape) < 3: - dataobj = reshape_dataobj(dataobj, shape + (1,) * (3 - len(shape))) - super().__init__(dataobj, affine, header=header, extra=extra, file_map=file_map) - - @classmethod - def filespec_to_file_map(klass, filespec): - filespec = _stringify_path(filespec) - """ Check for compressed .mgz format, then .mgh format """ - if splitext(filespec)[1].lower() == '.mgz': - return dict(image=FileHolder(filename=filespec)) - return super().filespec_to_file_map(filespec) - - @classmethod - def from_file_map(klass, file_map, *, mmap=True, keep_file_open=None): - """Class method to create image from mapping in ``file_map`` - - Parameters - ---------- - file_map : dict - Mapping with (key, value) pairs of (``file_type``, FileHolder - instance giving file-likes for each file needed for this image - type. - mmap : {True, False, 'c', 'r'}, optional, keyword only - `mmap` controls the use of numpy memory mapping for reading image - array data. If False, do not try numpy ``memmap`` for data array. - If one of {'c', 'r'}, try numpy memmap with ``mode=mmap``. A - `mmap` value of True gives the same behavior as ``mmap='c'``. If - image data file cannot be memory-mapped, ignore `mmap` value and - read array from file. - keep_file_open : { None, True, False }, optional, keyword only - `keep_file_open` controls whether a new file handle is created - every time the image is accessed, or a single file handle is - created and used for the lifetime of this ``ArrayProxy``. If - ``True``, a single file handle is created and used. If ``False``, - a new file handle is created every time the image is accessed. - If ``file_map`` refers to an open file handle, this setting has no - effect. The default value (``None``) will result in the value of - ``nibabel.arrayproxy.KEEP_FILE_OPEN_DEFAULT`` being used. - - Returns - ------- - img : MGHImage instance - """ - if mmap not in (True, False, 'c', 'r'): - raise ValueError("mmap should be one of {True, False, 'c', 'r'}") - img_fh = file_map['image'] - mghf = img_fh.get_prepare_fileobj('rb') - header = klass.header_class.from_fileobj(mghf) - affine = header.get_affine() - hdr_copy = header.copy() - # Pass original image fileobj / filename to array proxy - data = klass.ImageArrayProxy( - img_fh.file_like, hdr_copy, mmap=mmap, keep_file_open=keep_file_open - ) - img = klass(data, affine, header, file_map=file_map) - return img - - def to_file_map(self, file_map=None): - """Write image to `file_map` or contained ``self.file_map`` - - Parameters - ---------- - file_map : None or mapping, optional - files mapping. If None (default) use object's ``file_map`` - attribute instead - """ - if file_map is None: - file_map = self.file_map - data = np.asanyarray(self.dataobj) - self.update_header() - hdr = self.header - with file_map['image'].get_prepare_fileobj('wb') as mghf: - hdr.writehdr_to(mghf) - self._write_data(mghf, data, hdr) - hdr.writeftr_to(mghf) - self._header = hdr - self.file_map = file_map - - def _write_data(self, mghfile, data, header): - """Utility routine to write image - - Parameters - ---------- - mghfile : file-like - file-like object implementing ``seek`` or ``tell``, and - ``write`` - data : array-like - array to write - header : analyze-type header object - header - """ - shape = header.get_data_shape() - if data.shape != shape: - raise HeaderDataError( - 'Data should be shape ({})'.format(', '.join(str(s) for s in shape)) - ) - offset = header.get_data_offset() - out_dtype = header.get_data_dtype() - array_to_file(data, mghfile, out_dtype, offset) - - def _affine2header(self): - """Unconditionally set affine into the header""" - hdr = self._header - shape = np.array(self._dataobj.shape[:3]) - - # for more information, go through save_mgh.m in FreeSurfer dist - voxelsize = voxel_sizes(self._affine) - Mdc = self._affine[:3, :3] / voxelsize - c_ras = self._affine.dot(np.hstack((shape / 2.0, [1])))[:3] - - # Assign after we've had a chance to raise exceptions - hdr['delta'] = voxelsize - hdr['Mdc'] = Mdc.T - hdr['Pxyz_c'] = c_ras - - -load = MGHImage.from_filename -save = MGHImage.instance_to_filename diff --git a/nibabel/freesurfer/tests/__init__.py b/nibabel/freesurfer/tests/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/nibabel/freesurfer/tests/test_io.py b/nibabel/freesurfer/tests/test_io.py deleted file mode 100644 index d6c9649ca3..0000000000 --- a/nibabel/freesurfer/tests/test_io.py +++ /dev/null @@ -1,372 +0,0 @@ -import getpass -import hashlib -import os -import struct -import time -import unittest -from os.path import isdir -from os.path import join as pjoin -from pathlib import Path - -import numpy as np -import pytest -from numpy.testing import assert_allclose - -from ...fileslice import strided_scalar -from ...testing import clear_and_catch_warnings -from ...tests.nibabel_data import get_nibabel_data, needs_nibabel_data -from ...tmpdirs import InTemporaryDirectory -from .. import ( - read_annot, - read_geometry, - read_label, - read_morph_data, - write_annot, - write_geometry, - write_morph_data, -) -from ..io import _pack_rgb - -DATA_SDIR = 'fsaverage' - -have_freesurfer = False -if 'SUBJECTS_DIR' in os.environ: - # May have Freesurfer installed with data - data_path = pjoin(os.environ['SUBJECTS_DIR'], DATA_SDIR) - have_freesurfer = isdir(data_path) -else: - # May have nibabel test data submodule checked out - nib_data = get_nibabel_data() - if nib_data != '': - data_path = pjoin(nib_data, 'nitest-freesurfer', DATA_SDIR) - have_freesurfer = isdir(data_path) - -freesurfer_test = unittest.skipUnless( - have_freesurfer, f'cannot find freesurfer {DATA_SDIR} directory' -) - - -@freesurfer_test -def test_geometry(): - """Test IO of .surf""" - surf_path = pjoin(data_path, 'surf', 'lh.inflated') - coords, faces = read_geometry(surf_path) - assert 0 == faces.min() - assert coords.shape[0] == faces.max() + 1 - - surf_path = pjoin(data_path, 'surf', 'lh.sphere') - coords, faces, volume_info, create_stamp = read_geometry( - surf_path, read_metadata=True, read_stamp=True - ) - - assert 0 == faces.min() - assert coords.shape[0] == faces.max() + 1 - assert 9 == len(volume_info) - assert np.array_equal([2, 0, 20], volume_info['head']) - assert create_stamp == 'created by greve on Thu Jun 8 19:17:51 2006' - - # Test equivalence of freesurfer- and nibabel-generated triangular files - # with respect to read_geometry() - with InTemporaryDirectory(): - surf_path = 'test' - create_stamp = f'created by {getpass.getuser()} on {time.ctime()}' - volume_info['cras'] = [1.0, 2.0, 3.0] - write_geometry(surf_path, coords, faces, create_stamp, volume_info) - - coords2, faces2, volume_info2 = read_geometry(surf_path, read_metadata=True) - - for key in ('xras', 'yras', 'zras', 'cras'): - assert_allclose(volume_info2[key], volume_info[key], rtol=1e-7, atol=1e-30) - - assert np.array_equal(volume_info2['cras'], volume_info['cras']) - with open(surf_path, 'rb') as fobj: - np.fromfile(fobj, '>u1', 3) - read_create_stamp = fobj.readline().decode().rstrip('\n') - - # now write an incomplete file - write_geometry(surf_path, coords, faces) - with pytest.warns(UserWarning) as w: - read_geometry(surf_path, read_metadata=True) - assert any('volume information contained' in str(ww.message) for ww in w) - assert any('extension code' in str(ww.message) for ww in w) - - volume_info['head'] = [1, 2] - with pytest.warns(UserWarning, match='Unknown extension'): - write_geometry(surf_path, coords, faces, create_stamp, volume_info) - - volume_info['a'] = 0 - with pytest.raises(ValueError): - write_geometry(surf_path, coords, faces, create_stamp, volume_info) - - assert create_stamp == read_create_stamp - - assert np.array_equal(coords, coords2) - assert np.array_equal(faces, faces2) - - # Validate byte ordering - coords_swapped = coords.byteswap() - coords_swapped = coords_swapped.view(coords_swapped.dtype.newbyteorder()) - faces_swapped = faces.byteswap() - faces_swapped = faces_swapped.view(faces_swapped.dtype.newbyteorder()) - assert np.array_equal(coords_swapped, coords) - assert np.array_equal(faces_swapped, faces) - - -@freesurfer_test -@needs_nibabel_data('nitest-freesurfer') -def test_quad_geometry(): - """Test IO of freesurfer quad files.""" - new_quad = pjoin( - get_nibabel_data(), 'nitest-freesurfer', 'subjects', 'bert', 'surf', 'lh.inflated.nofix' - ) - coords, faces = read_geometry(new_quad) - assert 0 == faces.min() - assert coords.shape[0] == (faces.max() + 1) - with InTemporaryDirectory(): - new_path = 'test' - write_geometry(new_path, coords, faces) - coords2, faces2 = read_geometry(new_path) - assert np.array_equal(coords, coords2) - assert np.array_equal(faces, faces2) - - -@freesurfer_test -def test_morph_data(): - """Test IO of morphometry data file (eg. curvature).""" - curv_path = pjoin(data_path, 'surf', 'lh.curv') - curv = read_morph_data(curv_path) - assert -1.0 < curv.min() < 0 - assert 0 < curv.max() < 1.0 - with InTemporaryDirectory(): - new_path = 'test' - write_morph_data(new_path, curv) - curv2 = read_morph_data(new_path) - assert np.array_equal(curv2, curv) - - -def test_write_morph_data(): - """Test write_morph_data edge cases""" - values = np.arange(20, dtype='>f4') - okay_shapes = [(20,), (20, 1), (20, 1, 1), (1, 20)] - bad_shapes = [(10, 2), (1, 1, 20, 1, 1)] - big_num = np.iinfo('i4').max + 1 - with InTemporaryDirectory(): - for shape in okay_shapes: - write_morph_data('test.curv', values.reshape(shape)) - # Check ordering is preserved, regardless of shape - assert np.array_equal(read_morph_data('test.curv'), values) - - with pytest.raises(ValueError): - write_morph_data('test.curv', np.zeros(shape), big_num) - # Windows 32-bit overflows Python int - if np.dtype(int) != np.dtype(np.int32): - with pytest.raises(ValueError): - write_morph_data('test.curv', strided_scalar((big_num,))) - for shape in bad_shapes: - with pytest.raises(ValueError): - write_morph_data('test.curv', values.reshape(shape)) - - -@freesurfer_test -def test_annot(): - """Test IO of .annot against freesurfer example data.""" - annots = ['aparc', 'aparc.a2005s'] - for a in annots: - annot_path = pjoin(data_path, 'label', f'lh.{a}.annot') - - labels, ctab, names = read_annot(annot_path) - assert labels.shape == (163842,) - assert ctab.shape == (len(names), 5) - - labels_orig = None - if a == 'aparc': - labels_orig, _, _ = read_annot(annot_path, orig_ids=True) - np.testing.assert_array_equal(labels == -1, labels_orig == 0) - # Handle different version of fsaverage - content_hash = hashlib.md5(Path(annot_path).read_bytes()).hexdigest() - if content_hash == 'bf0b488994657435cdddac5f107d21e8': - assert np.sum(labels_orig == 0) == 13887 - elif content_hash == 'd4f5b7cbc2ed363ac6fcf89e19353504': - assert np.sum(labels_orig == 1639705) == 13327 - else: - raise RuntimeError( - 'Unknown freesurfer file. Please report ' - 'the problem to the maintainer of nibabel.' - ) - - # Test equivalence of freesurfer- and nibabel-generated annot files - # with respect to read_annot() - with InTemporaryDirectory(): - annot_path = 'test' - write_annot(annot_path, labels, ctab, names) - - labels2, ctab2, names2 = read_annot(annot_path) - if labels_orig is not None: - labels_orig_2, _, _ = read_annot(annot_path, orig_ids=True) - - assert np.array_equal(labels, labels2) - if labels_orig is not None: - assert np.array_equal(labels_orig, labels_orig_2) - assert np.array_equal(ctab, ctab2) - assert names == names2 - - -def test_read_write_annot(): - """Test generating .annot file and reading it back.""" - # This annot file will store a LUT for a mesh made of 10 vertices, with - # 3 colours in the LUT. - nvertices = 10 - nlabels = 3 - names = [f'label {l}' for l in range(1, nlabels + 1)] - # randomly generate a label for each vertex, making sure - # that at least one of each label value is present. Label - # values are in the range (0, nlabels-1) - they are used - # as indices into the lookup table (generated below). - labels = list(range(nlabels)) + list(np.random.randint(0, nlabels, nvertices - nlabels)) - labels = np.array(labels, dtype=np.int32) - np.random.shuffle(labels) - # Generate some random colours for the LUT - rgbal = np.zeros((nlabels, 5), dtype=np.int32) - rgbal[:, :4] = np.random.randint(0, 255, (nlabels, 4)) - # But make sure we have at least one large alpha, to make sure that when - # it is packed into a signed 32 bit int, it results in a negative value - # for the annotation value. - rgbal[0, 3] = 255 - # Generate the annotation values for each LUT entry - rgbal[:, 4] = rgbal[:, 0] + rgbal[:, 1] * (2**8) + rgbal[:, 2] * (2**16) - annot_path = 'c.annot' - with InTemporaryDirectory(): - write_annot(annot_path, labels, rgbal, names, fill_ctab=False) - labels2, rgbal2, names2 = read_annot(annot_path) - names2 = [n.decode('ascii') for n in names2] - assert np.all(np.isclose(rgbal2, rgbal)) - assert np.all(np.isclose(labels2, labels)) - assert names2 == names - - -def test_write_annot_fill_ctab(): - """Test the `fill_ctab` parameter to :func:`.write_annot`.""" - nvertices = 10 - nlabels = 3 - names = [f'label {l}' for l in range(1, nlabels + 1)] - labels = list(range(nlabels)) + list(np.random.randint(0, nlabels, nvertices - nlabels)) - labels = np.array(labels, dtype=np.int32) - np.random.shuffle(labels) - rgba = np.array(np.random.randint(0, 255, (nlabels, 4)), dtype=np.int32) - annot_path = 'c.annot' - with InTemporaryDirectory(): - write_annot(annot_path, labels, rgba, names, fill_ctab=True) - labels2, rgbal2, names2 = read_annot(annot_path) - names2 = [n.decode('ascii') for n in names2] - assert np.all(np.isclose(rgbal2[:, :4], rgba)) - assert np.all(np.isclose(labels2, labels)) - assert names2 == names - # make sure a warning is emitted if fill_ctab is False, and the - # annotation values are wrong. Use orig_ids=True so we get those bad - # values back. - badannot = (10 * np.arange(nlabels, dtype=np.int32)).reshape(-1, 1) - rgbal = np.hstack((rgba, badannot)) - with pytest.warns( - UserWarning, match=f'Annotation values in {annot_path} will be incorrect' - ): - write_annot(annot_path, labels, rgbal, names, fill_ctab=False) - labels2, rgbal2, names2 = read_annot(annot_path, orig_ids=True) - names2 = [n.decode('ascii') for n in names2] - assert np.all(np.isclose(rgbal2[:, :4], rgba)) - assert np.all(np.isclose(labels2, badannot[labels].squeeze())) - assert names2 == names - # make sure a warning is *not* emitted if fill_ctab is False, but the - # annotation values are correct. - rgbal = np.hstack((rgba, np.zeros((nlabels, 1), dtype=np.int32))) - rgbal[:, 4] = rgbal[:, 0] + rgbal[:, 1] * (2**8) + rgbal[:, 2] * (2**16) - with clear_and_catch_warnings() as w: - write_annot(annot_path, labels, rgbal, names, fill_ctab=False) - assert all( - f'Annotation values in {annot_path} will be incorrect' != str(ww.message) for ww in w - ) - labels2, rgbal2, names2 = read_annot(annot_path) - names2 = [n.decode('ascii') for n in names2] - assert np.all(np.isclose(rgbal2[:, :4], rgba)) - assert np.all(np.isclose(labels2, labels)) - assert names2 == names - - -def test_read_annot_old_format(): - """Test reading an old-style .annot file.""" - - def gen_old_annot_file(fpath, nverts, labels, rgba, names): - dt = '>i' - vdata = np.zeros((nverts, 2), dtype=dt) - vdata[:, 0] = np.arange(nverts) - vdata[:, [1]] = _pack_rgb(rgba[labels, :3]) - fbytes = b'' - # number of vertices - fbytes += struct.pack(dt, nverts) - # vertices + annotation values - fbytes += vdata.astype(dt).tobytes() - # is there a colour table? - fbytes += struct.pack(dt, 1) - # number of entries in colour table - fbytes += struct.pack(dt, rgba.shape[0]) - # length of orig_tab string - fbytes += struct.pack(dt, 5) - fbytes += b'abcd\x00' - for i in range(rgba.shape[0]): - # length of entry name (+1 for terminating byte) - fbytes += struct.pack(dt, len(names[i]) + 1) - fbytes += names[i].encode('ascii') + b'\x00' - fbytes += rgba[i, :].astype(dt).tobytes() - with open(fpath, 'wb') as f: - f.write(fbytes) - - with InTemporaryDirectory(): - nverts = 10 - nlabels = 3 - names = [f'Label {l}' for l in range(nlabels)] - labels = np.concatenate( - (np.arange(nlabels), np.random.randint(0, nlabels, nverts - nlabels)) - ) - np.random.shuffle(labels) - rgba = np.random.randint(0, 255, (nlabels, 4)) - # write an old .annot file - gen_old_annot_file('blah.annot', nverts, labels, rgba, names) - # read it back - rlabels, rrgba, rnames = read_annot('blah.annot') - rnames = [n.decode('ascii') for n in rnames] - assert np.all(np.isclose(labels, rlabels)) - assert np.all(np.isclose(rgba, rrgba[:, :4])) - assert names == rnames - - -@freesurfer_test -def test_label(): - """Test IO of .label""" - label_path = pjoin(data_path, 'label', 'lh.cortex.label') - label = read_label(label_path) - # XXX : test more - assert label.min() >= 0 - assert label.max() <= 163841 - assert label.shape[0] <= 163842 - - labels, scalars = read_label(label_path, True) - assert np.all(labels == label) - assert len(labels) == len(scalars) - - -def test_write_annot_maxstruct(): - """Test writing ANNOT files with repeated labels""" - with InTemporaryDirectory(): - nlabels = 3 - names = [f'label {l}' for l in range(1, nlabels + 1)] - # max label < n_labels - labels = np.array([1, 1, 1], dtype=np.int32) - rgba = np.array(np.random.randint(0, 255, (nlabels, 4)), dtype=np.int32) - annot_path = 'c.annot' - - write_annot(annot_path, labels, rgba, names) - # Validate the file can be read - rt_labels, rt_ctab, rt_names = read_annot(annot_path) - # Check round-trip - assert np.array_equal(labels, rt_labels) - assert np.array_equal(rgba, rt_ctab[:, :4]) - assert names == [n.decode('ascii') for n in rt_names] diff --git a/nibabel/freesurfer/tests/test_mghformat.py b/nibabel/freesurfer/tests/test_mghformat.py deleted file mode 100644 index 660d3dee97..0000000000 --- a/nibabel/freesurfer/tests/test_mghformat.py +++ /dev/null @@ -1,485 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Tests for mghformat reading writing""" - -import io -import os -import pathlib - -import numpy as np -import pytest -from numpy.testing import assert_almost_equal, assert_array_almost_equal, assert_array_equal - -from ... import imageglobals -from ...fileholders import FileHolder -from ...openers import ImageOpener -from ...spatialimages import HeaderDataError -from ...testing import data_path -from ...tests import test_spatialimages as tsi -from ...tests import test_wrapstruct as tws -from ...tmpdirs import InTemporaryDirectory -from ...volumeutils import sys_is_le -from ...wrapstruct import WrapStructError -from .. import load, save -from ..mghformat import MGHError, MGHHeader, MGHImage - -MGZ_FNAME = os.path.join(data_path, 'test.mgz') - -# sample voxel to ras matrix (mri_info --vox2ras) -v2r = np.array( - [ - [1, 2, 3, -13], - [2, 3, 1, -11.5], - [3, 1, 2, -11.5], - [0, 0, 0, 1], - ], - dtype=np.float32, -) -# sample voxel to ras - tkr matrix (mri_info --vox2ras-tkr) -v2rtkr = np.array( - [ - [-1.0, 0.0, 0.0, 1.5], - [0.0, 0.0, 1.0, -2.5], - [0.0, -1.0, 0.0, 2.0], - [0.0, 0.0, 0.0, 1.0], - ], - dtype=np.float32, -) - -BIG_CODES = ('>', 'big', 'BIG', 'b', 'be', 'B', 'BE') -LITTLE_CODES = ('<', 'little', 'l', 'le', 'L', 'LE') - -if sys_is_le: - BIG_CODES += ('swapped', 's', 'S', '!') - LITTLE_CODES += ('native', 'n', 'N', '=', '|', 'i', 'I') -else: - BIG_CODES += ('native', 'n', 'N', '=', '|', 'i', 'I') - LITTLE_CODES += ('swapped', 's', 'S', '!') - - -def test_read_mgh(): - # test.mgz was generated by the following command - # mri_volsynth --dim 3 4 5 2 --vol test.mgz - # --cdircos 1 2 3 --rdircos 2 3 1 --sdircos 3 1 2 - # mri_volsynth is a FreeSurfer command - mgz = load(MGZ_FNAME) - - # header - h = mgz.header - assert h['version'] == 1 - assert h['type'] == 3 - assert h['dof'] == 0 - assert h['goodRASFlag'] == 1 - assert_array_equal(h['dims'], [3, 4, 5, 2]) - assert_almost_equal(h['tr'], 2.0) - assert_almost_equal(h['flip_angle'], 0.0) - assert_almost_equal(h['te'], 0.0) - assert_almost_equal(h['ti'], 0.0) - assert_array_almost_equal(h.get_zooms(), [1, 1, 1, 2]) - assert_array_almost_equal(h.get_vox2ras(), v2r) - assert_array_almost_equal(h.get_vox2ras_tkr(), v2rtkr) - - # data. will be different for your own mri_volsynth invocation - v = mgz.get_fdata() - assert_almost_equal(v[1, 2, 3, 0], -0.3047, 4) - assert_almost_equal(v[1, 2, 3, 1], 0.0018, 4) - - -def test_write_mgh(): - # write our data to a tmp file - v = np.arange(120) - v = v.reshape((5, 4, 3, 2)).astype(np.float32) - # form a MGHImage object using data and vox2ras matrix - img = MGHImage(v, v2r) - with InTemporaryDirectory(): - save(img, 'tmpsave.mgz') - # read from the tmp file and see if it checks out - mgz = load('tmpsave.mgz') - h = mgz.header - dat = mgz.get_fdata() - # Delete loaded image to allow file deletion by windows - del mgz - # header - assert h['version'] == 1 - assert h['type'] == 3 - assert h['dof'] == 0 - assert h['goodRASFlag'] == 1 - assert np.array_equal(h['dims'], [5, 4, 3, 2]) - assert_almost_equal(h['tr'], 0.0) - assert_almost_equal(h['flip_angle'], 0.0) - assert_almost_equal(h['te'], 0.0) - assert_almost_equal(h['ti'], 0.0) - assert_almost_equal(h['fov'], 0.0) - assert_array_almost_equal(h.get_vox2ras(), v2r) - # data - assert_almost_equal(dat, v, 7) - - -def test_write_noaffine_mgh(): - # now just save the image without the vox2ras transform - # and see if it uses the default values to save - v = np.ones((7, 13, 3, 22), np.uint8) - # form a MGHImage object using data - # and the default affine matrix (Note the "None") - img = MGHImage(v, None) - with InTemporaryDirectory(): - save(img, 'tmpsave.mgz') - # read from the tmp file and see if it checks out - mgz = load('tmpsave.mgz') - h = mgz.header - # Delete loaded image to allow file deletion by windows - del mgz - # header - assert h['version'] == 1 - assert h['type'] == 0 # uint8 for mgh - assert h['dof'] == 0 - assert h['goodRASFlag'] == 1 - assert np.array_equal(h['dims'], [7, 13, 3, 22]) - assert_almost_equal(h['tr'], 0.0) - assert_almost_equal(h['flip_angle'], 0.0) - assert_almost_equal(h['te'], 0.0) - assert_almost_equal(h['ti'], 0.0) - assert_almost_equal(h['fov'], 0.0) - # important part -- whether default affine info is stored - assert_array_almost_equal(h['Mdc'], [[-1, 0, 0], [0, 0, 1], [0, -1, 0]]) - assert_array_almost_equal(h['Pxyz_c'], [0, 0, 0]) - - -def test_set_zooms(): - mgz = load(MGZ_FNAME) - h = mgz.header - assert_array_almost_equal(h.get_zooms(), [1, 1, 1, 2]) - h.set_zooms([1, 1, 1, 3]) - assert_array_almost_equal(h.get_zooms(), [1, 1, 1, 3]) - for zooms in ( - (-1, 1, 1, 1), - (1, -1, 1, 1), - (1, 1, -1, 1), - (1, 1, 1, -1), - (1, 1, 1, 1, 5), - ): - with pytest.raises(HeaderDataError): - h.set_zooms(zooms) - # smoke test for tr=0 - h.set_zooms((1, 1, 1, 0)) - - -def bad_dtype_mgh(): - """This function raises an MGHError exception because - float64 is not a valid MGH datatype. - """ - # try to write an unsigned short and make sure it - # raises MGHError - v = np.ones((7, 13, 3, 22), np.float64) - # form a MGHImage object using data - # and the default affine matrix (Note the "None") - MGHImage(v, None) - - -def test_bad_dtype_mgh(): - # Now test the above function - with pytest.raises(MGHError): - bad_dtype_mgh() - - -def test_filename_exts(): - # Test acceptable filename extensions - v = np.ones((7, 13, 3, 22), np.uint8) - # form a MGHImage object using data - # and the default affine matrix (Note the "None") - img = MGHImage(v, None) - # Check if these extensions allow round trip - for ext in ('.mgh', '.mgz'): - with InTemporaryDirectory(): - fname = 'tmpname' + ext - save(img, fname) - # read from the tmp file and see if it checks out - img_back = load(fname) - assert_array_equal(img_back.get_fdata(), v) - del img_back - - -def _mgh_rt(img, fobj): - file_map = {'image': FileHolder(fileobj=fobj)} - img.to_file_map(file_map) - return MGHImage.from_file_map(file_map) - - -def test_header_updating(): - # Don't update the header information if the affine doesn't change. - # Luckily the test.mgz dataset had a bad set of cosine vectors, so these - # will be changed if the affine gets updated - mgz = load(MGZ_FNAME) - hdr = mgz.header - # Test against mri_info output - exp_aff = np.loadtxt( - io.BytesIO( - b""" - 1.0000 2.0000 3.0000 -13.0000 - 2.0000 3.0000 1.0000 -11.5000 - 3.0000 1.0000 2.0000 -11.5000 - 0.0000 0.0000 0.0000 1.0000""" - ) - ) - assert_almost_equal(mgz.affine, exp_aff, 6) - assert_almost_equal(hdr.get_affine(), exp_aff, 6) - # Test that initial wonky header elements have not changed - assert np.all(hdr['delta'] == 1) - assert_almost_equal(hdr['Mdc'].T, exp_aff[:3, :3]) - # Save, reload, same thing - img_fobj = io.BytesIO() - mgz2 = _mgh_rt(mgz, img_fobj) - hdr2 = mgz2.header - assert_almost_equal(hdr2.get_affine(), exp_aff, 6) - assert_array_equal(hdr2['delta'], 1) - # Change affine, change underlying header info - exp_aff_d = exp_aff.copy() - exp_aff_d[0, -1] = -14 - # This will (probably) become part of the official API - mgz2._affine[:] = exp_aff_d - mgz2.update_header() - assert_almost_equal(hdr2.get_affine(), exp_aff_d, 6) - RZS = exp_aff_d[:3, :3] - assert_almost_equal(hdr2['delta'], np.sqrt(np.sum(RZS**2, axis=0))) - assert_almost_equal(hdr2['Mdc'].T, RZS / hdr2['delta']) - - -def test_cosine_order(): - # Test we are interpreting the cosine order right - data = np.arange(60, dtype=np.int32).reshape((3, 4, 5)) - aff = np.diag([2.0, 3, 4, 1]) - aff[0] = [2, 1, 0, 10] - img = MGHImage(data, aff) - assert_almost_equal(img.affine, aff, 6) - img_fobj = io.BytesIO() - img2 = _mgh_rt(img, img_fobj) - hdr2 = img2.header - RZS = aff[:3, :3] - zooms = np.sqrt(np.sum(RZS**2, axis=0)) - assert_almost_equal(hdr2['Mdc'].T, RZS / zooms) - assert_almost_equal(hdr2['delta'], zooms) - - -def test_eq(): - # Test headers compare properly - hdr = MGHHeader() - hdr2 = MGHHeader() - assert hdr == hdr2 - hdr.set_data_shape((2, 3, 4)) - assert hdr != hdr2 - hdr2.set_data_shape((2, 3, 4)) - assert hdr == hdr2 - - -def test_header_slope_inter(): - # Test placeholder slope / inter method - hdr = MGHHeader() - assert hdr.get_slope_inter() == (None, None) - - -def test_mgh_load_fileobj(): - # Checks the filename gets passed to array proxy - # - # This is a bit of an implementation detail, but the test is to make sure - # that we aren't passing ImageOpener objects to the array proxy, as these - # were confusing mmap on Python 3. If there's some sensible reason not to - # pass the filename to the array proxy, please feel free to change this - # test. - img = MGHImage.load(MGZ_FNAME) - assert pathlib.Path(img.dataobj.file_like) == pathlib.Path(MGZ_FNAME) - # Check fileobj also passed into dataobj - with ImageOpener(MGZ_FNAME) as fobj: - contents = fobj.read() - bio = io.BytesIO(contents) - fm = MGHImage.make_file_map(mapping=dict(image=bio)) - img2 = MGHImage.from_file_map(fm) - assert img2.dataobj.file_like is bio - assert_array_equal(img.get_fdata(), img2.get_fdata()) - - -def test_mgh_affine_default(): - hdr = MGHHeader() - hdr['goodRASFlag'] = 0 - hdr2 = MGHHeader(hdr.binaryblock) - assert hdr2['goodRASFlag'] == 1 - assert_array_equal(hdr['Mdc'], hdr2['Mdc']) - assert_array_equal(hdr['Pxyz_c'], hdr2['Pxyz_c']) - - -def test_mgh_set_data_shape(): - hdr = MGHHeader() - hdr.set_data_shape((5,)) - assert_array_equal(hdr.get_data_shape(), (5, 1, 1)) - hdr.set_data_shape((5, 4)) - assert_array_equal(hdr.get_data_shape(), (5, 4, 1)) - hdr.set_data_shape((5, 4, 3)) - assert_array_equal(hdr.get_data_shape(), (5, 4, 3)) - hdr.set_data_shape((5, 4, 3, 2)) - assert_array_equal(hdr.get_data_shape(), (5, 4, 3, 2)) - with pytest.raises(ValueError): - hdr.set_data_shape((5, 4, 3, 2, 1)) - - -def test_mghheader_default_structarr(): - hdr = MGHHeader.default_structarr() - assert hdr['version'] == 1 - assert_array_equal(hdr['dims'], 1) - assert hdr['type'] == 3 - assert hdr['dof'] == 0 - assert hdr['goodRASFlag'] == 1 - assert_array_equal(hdr['delta'], 1) - assert_array_equal(hdr['Mdc'], [[-1, 0, 0], [0, 0, 1], [0, -1, 0]]) - assert_array_equal(hdr['Pxyz_c'], 0) - assert hdr['tr'] == 0 - assert hdr['flip_angle'] == 0 - assert hdr['te'] == 0 - assert hdr['ti'] == 0 - assert hdr['fov'] == 0 - - for endianness in (None,) + BIG_CODES: - hdr2 = MGHHeader.default_structarr(endianness=endianness) - assert hdr2 == hdr - assert hdr2.view(hdr2.dtype.newbyteorder('>')) == hdr - - for endianness in LITTLE_CODES: - with pytest.raises(ValueError): - MGHHeader.default_structarr(endianness=endianness) - - -class TestMGHImage(tsi.TestSpatialImage, tsi.MmapImageMixin): - """Apply general image tests to MGHImage""" - - image_class = MGHImage - can_save = True - - def check_dtypes(self, expected, actual): - # Some images will want dtypes to be equal including endianness, - # others may only require the same type - # MGH requires the actual to be a big endian version of expected - assert expected.newbyteorder('>') == actual - - -class TestMGHHeader(tws._TestLabeledWrapStruct): - header_class = MGHHeader - - def _set_something_into_hdr(self, hdr): - hdr['dims'] = [4, 3, 2, 1] - - def get_bad_bb(self): - return b'\xff' + b'\x00' * self.header_class._hdrdtype.itemsize - - # Update tests to account for big-endian requirement - def test_general_init(self): - hdr = self.header_class() - # binaryblock has length given by header data dtype - binblock = hdr.binaryblock - assert len(binblock) == hdr.structarr.dtype.itemsize - # Endianness will always be big, and cannot be set - assert hdr.endianness == '>' - # You can also pass in a check flag, without data this has no - # effect - hdr = self.header_class(check=False) - - def test__eq__(self): - # Test equal and not equal - hdr1 = self.header_class() - hdr2 = self.header_class() - assert hdr1 == hdr2 - self._set_something_into_hdr(hdr1) - assert hdr1 != hdr2 - self._set_something_into_hdr(hdr2) - assert hdr1 == hdr2 - # REMOVED as_byteswapped() test - # Check comparing to funny thing says no - assert hdr1 != None - assert hdr1 != 1 - - def test_to_from_fileobj(self): - # Successful write using write_to - hdr = self.header_class() - str_io = io.BytesIO() - hdr.write_to(str_io) - str_io.seek(0) - hdr2 = self.header_class.from_fileobj(str_io) - assert hdr2.endianness == '>' - assert hdr2.binaryblock == hdr.binaryblock - - def test_endian_guess(self): - # Check guesses of endian - eh = self.header_class() - assert eh.endianness == '>' - assert self.header_class.guessed_endian(eh) == '>' - - def test_bytes(self): - # Test get of bytes - hdr1 = self.header_class() - bb = hdr1.binaryblock - hdr2 = self.header_class(hdr1.binaryblock) - assert hdr1 == hdr2 - assert hdr1.binaryblock == hdr2.binaryblock - # Do a set into the header, and try again. The specifics of 'setting - # something' will depend on the nature of the bytes object - self._set_something_into_hdr(hdr1) - hdr2 = self.header_class(hdr1.binaryblock) - assert hdr1 == hdr2 - assert hdr1.binaryblock == hdr2.binaryblock - # Short binaryblocks give errors (here set through init) - # Long binaryblocks are truncated - with pytest.raises(WrapStructError): - self.header_class(bb[: self.header_class._hdrdtype.itemsize - 1]) - - # Checking set to true by default, and prevents nonsense being - # set into the header. - bb_bad = self.get_bad_bb() - if bb_bad is None: - return - with imageglobals.LoggingOutputSuppressor(): - with pytest.raises(HeaderDataError): - self.header_class(bb_bad) - - # now slips past without check - _ = self.header_class(bb_bad, check=False) - - def test_as_byteswapped(self): - # Check byte swapping - hdr = self.header_class() - assert hdr.endianness == '>' - # same code just returns a copy - for endianness in BIG_CODES: - hdr2 = hdr.as_byteswapped(endianness) - assert hdr2 is not hdr - assert hdr2 == hdr - - # Different code raises error - for endianness in (None,) + LITTLE_CODES: - with pytest.raises(ValueError): - hdr.as_byteswapped(endianness) - - # Note that contents is not rechecked on swap / copy - class DC(self.header_class): - def check_fix(self, *args, **kwargs): - raise Exception - - # Assumes check=True default - with pytest.raises(Exception): - DC(hdr.binaryblock) - - hdr = DC(hdr.binaryblock, check=False) - hdr2 = hdr.as_byteswapped('>') - - def test_checks(self): - # Test header checks - hdr_t = self.header_class() - # _dxer just returns the diagnostics as a string - # Default hdr is OK - assert self._dxer(hdr_t) == '' - # Version should be 1 - hdr = hdr_t.copy() - hdr['version'] = 2 - assert self._dxer(hdr) == 'Unknown MGH format version' diff --git a/nibabel/funcs.py b/nibabel/funcs.py deleted file mode 100644 index cda4a5d2ed..0000000000 --- a/nibabel/funcs.py +++ /dev/null @@ -1,215 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Processor functions for images""" - -import numpy as np - -from .loadsave import load -from .orientations import OrientationError, io_orientation - - -def squeeze_image(img): - """Return image, remove axes length 1 at end of image shape - - For example, an image may have shape (10,20,30,1,1). In this case - squeeze will result in an image with shape (10,20,30). See doctests - for further description of behavior. - - Parameters - ---------- - img : ``SpatialImage`` - - Returns - ------- - squeezed_img : ``SpatialImage`` - Copy of img, such that data, and data shape have been squeezed, - for dimensions > 3rd, and at the end of the shape list - - Examples - -------- - >>> import nibabel as nf - >>> shape = (10,20,30,1,1) - >>> data = np.arange(np.prod(shape), dtype='int32').reshape(shape) - >>> affine = np.eye(4) - >>> img = nf.Nifti1Image(data, affine) - >>> img.shape == (10, 20, 30, 1, 1) - True - >>> img2 = squeeze_image(img) - >>> img2.shape == (10, 20, 30) - True - - If the data are 3D then last dimensions of 1 are ignored - - >>> shape = (10,1,1) - >>> data = np.arange(np.prod(shape), dtype='int32').reshape(shape) - >>> img = nf.ni1.Nifti1Image(data, affine) - >>> img.shape == (10, 1, 1) - True - >>> img2 = squeeze_image(img) - >>> img2.shape == (10, 1, 1) - True - - Only *final* dimensions of 1 are squeezed - - >>> shape = (1, 1, 5, 1, 2, 1, 1) - >>> data = data.reshape(shape) - >>> img = nf.ni1.Nifti1Image(data, affine) - >>> img.shape == (1, 1, 5, 1, 2, 1, 1) - True - >>> img2 = squeeze_image(img) - >>> img2.shape == (1, 1, 5, 1, 2) - True - """ - klass = img.__class__ - shape = img.shape - slen = len(shape) - if slen < 4: - return klass.from_image(img) - for bdim in shape[3::][::-1]: - if bdim == 1: - slen -= 1 - else: - break - if slen == len(shape): - return klass.from_image(img) - shape = shape[:slen] - data = np.asanyarray(img.dataobj).reshape(shape) - return klass(data, img.affine, img.header, img.extra) - - -def concat_images(images, check_affines=True, axis=None): - r"""Concatenate images in list to single image, along specified dimension - - Parameters - ---------- - images : sequence - sequence of ``SpatialImage`` or filenames of the same dimensionality\s - check_affines : {True, False}, optional - If True, then check that all the affines for `images` are nearly - the same, raising a ``ValueError`` otherwise. Default is True - axis : None or int, optional - If None, concatenates on a new dimension. This requires all images to - be the same shape. If not None, concatenates on the specified - dimension. This requires all images to be the same shape, except on - the specified dimension. - - Returns - ------- - concat_img : ``SpatialImage`` - New image resulting from concatenating `images` across last - dimension - """ - images = [load(img) if not hasattr(img, 'get_data') else img for img in images] - n_imgs = len(images) - if n_imgs == 0: - raise ValueError('Cannot concatenate an empty list of images.') - img0 = images[0] - affine = img0.affine - header = img0.header - klass = img0.__class__ - shape0 = img0.shape - n_dim = len(shape0) - if axis is None: - # collect images in output array for efficiency - out_shape = (n_imgs,) + shape0 - out_data = np.empty(out_shape) - else: - # collect images in list for use with np.concatenate - out_data = [None] * n_imgs - # Get part of shape we need to check inside loop - idx_mask = np.ones((n_dim,), dtype=bool) - if axis is not None: - idx_mask[axis] = False - masked_shape = np.array(shape0)[idx_mask] - for i, img in enumerate(images): - if len(img.shape) != n_dim: - raise ValueError(f'Image {i} has {len(img.shape)} dimensions, image 0 has {n_dim}') - if not np.all(np.array(img.shape)[idx_mask] == masked_shape): - raise ValueError( - f'shape {img.shape} for image {i} not compatible with ' - f'first image shape {shape0} with axis == {axis}' - ) - if check_affines and not np.all(img.affine == affine): - raise ValueError(f'Affine for image {i} does not match affine for first image') - # Do not fill cache in image if it is empty - out_data[i] = np.asanyarray(img.dataobj) - - if axis is None: - out_data = np.rollaxis(out_data, 0, out_data.ndim) - else: - out_data = np.concatenate(out_data, axis=axis) - - return klass(out_data, affine, header) - - -def four_to_three(img): - """Create 3D images from 4D image by slicing over last axis - - Parameters - ---------- - img : image - 4D image instance of some class with methods ``get_data``, - ``header`` and ``affine``, and a class constructor - allowing klass(data, affine, header) - - Returns - ------- - imgs : list - list of 3D images - """ - arr = np.asanyarray(img.dataobj) - header = img.header - affine = img.affine - image_maker = img.__class__ - if arr.ndim != 4: - raise ValueError('Expecting four dimensions') - imgs = [] - for i in range(arr.shape[3]): - arr3d = arr[..., i] - img3d = image_maker(arr3d, affine, header) - imgs.append(img3d) - return imgs - - -def as_closest_canonical(img, enforce_diag=False): - """Return `img` with data reordered to be closest to canonical - - Canonical order is the ordering of the output axes. - - Parameters - ---------- - img : ``spatialimage`` - enforce_diag : {False, True}, optional - If True, before transforming image, check if the resulting image - affine will be close to diagonal, and if not, raise an error - - Returns - ------- - canonical_img : ``spatialimage`` - Version of `img` where the underlying array may have been - reordered and / or flipped so that axes 0,1,2 are those axes in - the input data that are, respectively, closest to the output axis - orientation. We modify the affine accordingly. If `img` is - already has the correct data ordering, we just return `img` - unmodified. - """ - # Get the image class to transform the data for us - img = img.as_reoriented(io_orientation(img.affine)) - - # however, the affine may not be diagonal - if enforce_diag and not _aff_is_diag(img.affine): - raise OrientationError('Transformed affine is not diagonal') - - return img - - -def _aff_is_diag(aff): - """Utility function returning True if affine is nearly diagonal""" - rzs_aff = aff[:3, :3] - return np.allclose(rzs_aff, np.diag(np.diag(rzs_aff))) diff --git a/nibabel/gifti/__init__.py b/nibabel/gifti/__init__.py deleted file mode 100644 index f54a1d2e54..0000000000 --- a/nibabel/gifti/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""GIfTI format IO - -.. currentmodule:: nibabel.gifti - -.. autosummary:: - :toctree: ../generated - - gifti -""" - -from .gifti import ( - GiftiCoordSystem, - GiftiDataArray, - GiftiImage, - GiftiLabel, - GiftiLabelTable, - GiftiMetaData, - GiftiNVPairs, -) diff --git a/nibabel/gifti/gifti.py b/nibabel/gifti/gifti.py deleted file mode 100644 index ff7a9bdde1..0000000000 --- a/nibabel/gifti/gifti.py +++ /dev/null @@ -1,955 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Classes defining Gifti objects - -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 copy import copy -from typing import cast - -import numpy as np - -from .. import xmlutils as xml -from ..caret import CaretMetaData -from ..deprecated import deprecate_with_version -from ..filebasedimages import SerializableImage -from ..nifti1 import data_type_codes, intent_codes, xform_codes -from .util import KIND2FMT, array_index_order_codes, gifti_encoding_codes, gifti_endian_codes - -GIFTI_DTYPES = ( - data_type_codes['NIFTI_TYPE_UINT8'], - data_type_codes['NIFTI_TYPE_INT32'], - data_type_codes['NIFTI_TYPE_FLOAT32'], -) - - -class _GiftiMDList(list): - """List view of GiftiMetaData object that will translate most operations""" - - def __init__(self, metadata): - self._md = metadata - super().__init__(GiftiNVPairs._private_init(k, v, metadata) for k, v in metadata.items()) - - def append(self, nvpair): - self._md[nvpair.name] = nvpair.value - super().append(nvpair) - - def clear(self): - super().clear() - self._md.clear() - - def extend(self, iterable): - for nvpair in iterable: - self.append(nvpair) - - def insert(self, index, nvpair): - self._md[nvpair.name] = nvpair.value - super().insert(index, nvpair) - - def pop(self, index=-1): - nvpair = super().pop(index) - nvpair._container = None - del self._md[nvpair.name] - return nvpair - - def remove(self, nvpair): - super().remove(nvpair) - del self._md[nvpair.name] - - -class GiftiMetaData(CaretMetaData): - """A sequence of GiftiNVPairs containing metadata for a gifti data array""" - - @staticmethod - def _sanitize(args, kwargs): - """Sanitize and warn on deprecated arguments - - Accept nvpair positional/keyword argument that is a single - ``GiftiNVPairs`` object. - - >>> import pytest - >>> GiftiMetaData() - - >>> GiftiMetaData([("key", "val")]) - - >>> GiftiMetaData(key="val") - - >>> GiftiMetaData({"key": "val"}) - - >>> with pytest.deprecated_call(): - ... nvpairs = GiftiNVPairs(name='key', value='val') - >>> with pytest.warns(FutureWarning): - ... GiftiMetaData(nvpairs) - - >>> with pytest.warns(FutureWarning): - ... GiftiMetaData(nvpair=nvpairs) - - """ - dep_init = False - # Positional arg - dep_init |= not kwargs and len(args) == 1 and isinstance(args[0], GiftiNVPairs) - # Keyword arg - dep_init |= not args and list(kwargs) == ['nvpair'] - if not dep_init: - return args, kwargs - - warnings.warn( - 'GiftiMetaData now has a dict-like interface. ' - 'See ``pydoc dict`` for initialization options. ' - 'Passing ``GiftiNVPairs()`` or using the ``nvpair`` ' - 'keyword will fail or behave unexpectedly in NiBabel 6.0.', - FutureWarning, - stacklevel=3, - ) - pair = args[0] if args else kwargs.get('nvpair') - return (), {pair.name: pair.value} - - @property - @deprecate_with_version( - 'The data attribute is deprecated. Use GiftiMetaData object directly as a dict.', - '4.0', - '6.0', - ) - def data(self): - return _GiftiMDList(self) - - @classmethod - @deprecate_with_version( - 'from_dict class method deprecated. Use GiftiMetaData directly.', '4.0', '6.0' - ) - def from_dict(klass, data_dict): - return klass(data_dict) - - @property - @deprecate_with_version( - 'metadata property deprecated. Use GiftiMetaData object ' - 'as dict or pass to dict() for a standard dictionary.', - '4.0', - '6.0', - ) - def metadata(self): - """Returns metadata as dictionary""" - return dict(self) - - def print_summary(self): - print(dict(self)) - - -class GiftiNVPairs: - """Gifti name / value pairs - - Attributes - ---------- - name : str - value : str - """ - - @deprecate_with_version( - 'GiftiNVPairs objects are deprecated. Use the GiftiMetaData object as a dict, instead.', - '4.0', - '6.0', - ) - def __init__(self, name='', value=''): - self._name = name - self._value = value - self._container = None - - @classmethod - def _private_init(cls, name, value, md): - """Private init method to provide warning-free experience""" - with warnings.catch_warnings(): - warnings.simplefilter('ignore', DeprecationWarning) - self = cls(name, value) - self._container = md - return self - - def __eq__(self, other): - if not isinstance(other, GiftiNVPairs): - return NotImplemented - return self.name == other.name and self.value == other.value - - @property - def name(self): - return self._name - - @name.setter - def name(self, key): - if self._container: - self._container[key] = self._container.pop(self._name) - self._name = key - - @property - def value(self): - return self._value - - @value.setter - def value(self, val): - if self._container: - self._container[self._name] = val - self._value = val - - -class GiftiLabelTable(xml.XmlSerializable): - """Gifti label table: a sequence of key, label pairs - - From the gifti spec dated 2011-01-14: - The label table is used by DataArrays whose values are an key into the - LabelTable's labels. A file should contain at most one LabelTable and - it must be located in the file prior to any DataArray elements. - """ - - def __init__(self): - self.labels = [] - - def __repr__(self): - return f'' - - def get_labels_as_dict(self): - self.labels_as_dict = {} - for ele in self.labels: - self.labels_as_dict[ele.key] = ele.label - return self.labels_as_dict - - def _to_xml_element(self): - labeltable = xml.Element('LabelTable') - for ele in self.labels: - label = xml.SubElement(labeltable, 'Label') - label.attrib['Key'] = str(ele.key) - label.text = ele.label - for attr in ('Red', 'Green', 'Blue', 'Alpha'): - if getattr(ele, attr.lower(), None) is not None: - label.attrib[attr] = str(getattr(ele, attr.lower())) - return labeltable - - def print_summary(self): - print(self.get_labels_as_dict()) - - -class GiftiLabel(xml.XmlSerializable): - """Gifti label: association of integer key with optional RGBA values - - Quotes are from the gifti spec dated 2011-01-14. - - Attributes - ---------- - key : int - (From the spec): "This required attribute contains a non-negative - integer value. If a DataArray's Intent is NIFTI_INTENT_LABEL and a - value in the DataArray is 'X', its corresponding label is the label - with the Key attribute containing the value 'X'. In early versions of - the GIFTI file format, the attribute Index was used instead of Key. If - an Index attribute is encountered, it should be processed like the Key - attribute." - red : None or float - Optional value for red. - green : None or float - Optional value for green. - blue : None or float - Optional value for blue. - alpha : None or float - Optional value for alpha. - - Notes - ----- - freesurfer examples seem not to conform to datatype "NIFTI_TYPE_RGBA32" - because they are floats, not 4 8-bit integers. - """ - - def __init__(self, key=0, red=None, green=None, blue=None, alpha=None): - self.key = key - self.red = red - self.green = green - self.blue = blue - self.alpha = alpha - - def __repr__(self): - chars = 255 * np.array([self.red or 0, self.green or 0, self.blue or 0, self.alpha or 0]) - r, g, b, a = chars.astype('u1') - return f'' - - @property - def rgba(self): - """Returns RGBA as tuple""" - return (self.red, self.green, self.blue, self.alpha) - - @rgba.setter - def rgba(self, rgba): - """Set RGBA via sequence - - Parameters - ---------- - rgba : length 4 sequence - Sequence containing values for red, green, blue, alpha. - """ - if len(rgba) != 4: - raise ValueError('rgba must be length 4.') - self.red, self.green, self.blue, self.alpha = rgba - - -def _arr2txt(arr, elem_fmt): - arr = np.asarray(arr) - assert arr.dtype.names is None - if arr.ndim == 1: - arr = arr[:, None] - fmt = ' '.join([elem_fmt] * arr.shape[1]) - return '\n'.join(fmt % tuple(row) for row in arr) - - -class GiftiCoordSystem(xml.XmlSerializable): - """Gifti coordinate system transform matrix - - Quotes are from the gifti spec dated 2011-01-14. - - "For a DataArray with an Intent NIFTI_INTENT_POINTSET, this element - describes the stereotaxic space of the data before and after the - application of a transformation matrix. The most common stereotaxic - space is the Talairach Space that places the origin at the anterior - commissure and the negative X, Y, and Z axes correspond to left, - posterior, and inferior respectively. At least one - CoordinateSystemTransformMatrix is required in a DataArray with an - intent of NIFTI_INTENT_POINTSET. Multiple - CoordinateSystemTransformMatrix elements may be used to describe the - transformation to multiple spaces." - - Attributes - ---------- - dataspace : int - From the spec: Contains the stereotaxic space of a DataArray's data - prior to application of the transformation matrix. The stereotaxic - space should be one of: - - - NIFTI_XFORM_UNKNOWN - - NIFTI_XFORM_SCANNER_ANAT - - NIFTI_XFORM_ALIGNED_ANAT - - NIFTI_XFORM_TALAIRACH - - NIFTI_XFORM_MNI_152 - - xformspace : int - Spec: "Contains the stereotaxic space of a DataArray's data after - application of the transformation matrix. See the DataSpace element for - a list of stereotaxic spaces." - - xform : array-like shape (4, 4) - Affine transformation matrix - """ - - def __init__(self, dataspace=0, xformspace=0, xform=None): - self.dataspace = dataspace - self.xformspace = xformspace - if xform is None: - # create identity matrix - self.xform = np.identity(4) - else: - self.xform = xform - - def __repr__(self): - src = xform_codes.label[self.dataspace] - dst = xform_codes.label[self.xformspace] - return f'' - - def _to_xml_element(self): - coord_xform = xml.Element('CoordinateSystemTransformMatrix') - if self.xform is not None: - dataspace = xml.SubElement(coord_xform, 'DataSpace') - dataspace.text = xform_codes.niistring[self.dataspace] - xformed_space = xml.SubElement(coord_xform, 'TransformedSpace') - xformed_space.text = xform_codes.niistring[self.xformspace] - matrix_data = xml.SubElement(coord_xform, 'MatrixData') - matrix_data.text = _arr2txt(self.xform, '%10.6f') - return coord_xform - - def print_summary(self): - print('Dataspace: ', xform_codes.niistring[self.dataspace]) - print('XFormSpace: ', xform_codes.niistring[self.xformspace]) - print('Affine Transformation Matrix:\n', self.xform) - - -def _data_tag_element(dataarray, encoding, dtype, ordering): - """Creates data tag with given `encoding`, returns as XML element""" - import zlib - - order = array_index_order_codes.npcode[ordering] - enclabel = gifti_encoding_codes.label[encoding] - if enclabel == 'ASCII': - da = _arr2txt(dataarray, KIND2FMT[dtype.kind]) - elif enclabel in ('B64BIN', 'B64GZ'): - out = np.asanyarray(dataarray, dtype).tobytes(order) - if enclabel == 'B64GZ': - out = zlib.compress(out) - da = base64.b64encode(out).decode() - elif enclabel == 'External': - raise NotImplementedError('In what format are the external files?') - else: - da = '' - - data = xml.Element('Data') - data.text = da - return data - - -class GiftiDataArray(xml.XmlSerializable): - """Container for Gifti numerical data array and associated metadata - - Quotes are from the gifti spec dated 2011-01-14. - - Description of DataArray in spec: - "This element contains the numeric data and its related metadata. The - CoordinateSystemTransformMatrix child is only used when the DataArray's - Intent is NIFTI_INTENT_POINTSET. FileName and FileOffset are required - if the data is stored in an external file." - - Attributes - ---------- - darray : None or ndarray - Data array - intent : int - NIFTI intent code, see nifti1.intent_codes - datatype : int - NIFTI data type codes, see nifti1.data_type_codes. From the spec: - "This required attribute describes the numeric type of the data - contained in a Data Array and are limited to the types displayed in the - table: - - NIFTI_TYPE_UINT8 : Unsigned, 8-bit bytes. - NIFTI_TYPE_INT32 : Signed, 32-bit integers. - NIFTI_TYPE_FLOAT32 : 32-bit single precision floating point." - - At the moment, we do not enforce that the datatype is one of these - three. - encoding : string - Encoding of the data, see util.gifti_encoding_codes; default is - GIFTI_ENCODING_B64GZ. - endian : string - The Endianness to store the data array. Should correspond to the - machine endianness. Default is system byteorder. - coordsys : :class:`GiftiCoordSystem` instance - Input and output coordinate system with transformation matrix between - the two. - ind_ord : int - The ordering of the array. see util.array_index_order_codes. Default - is RowMajorOrder - C ordering - meta : :class:`GiftiMetaData` instance - An instance equivalent to a dictionary for metadata information. - ext_fname : str - Filename in which data is stored, or empty string if no corresponding - filename. - ext_offset : int - Position in bytes within `ext_fname` at which to start reading data. - """ - - def __init__( - self, - data=None, - intent='NIFTI_INTENT_NONE', - datatype=None, - encoding='GIFTI_ENCODING_B64GZ', - endian=sys.byteorder, - coordsys=None, - ordering='C', - meta=None, - ext_fname='', - ext_offset=0, - ): - """ - Returns a shell object that cannot be saved. - """ - self.data = None if data is None else np.asarray(data) - self.intent = intent_codes.code[intent] - if datatype is None: - if self.data is None: - datatype = 'none' - elif data_type_codes[self.data.dtype] in GIFTI_DTYPES: - datatype = self.data.dtype - else: - raise ValueError( - f'Data array has type {self.data.dtype}. ' - 'The GIFTI standard only supports uint8, int32 and float32 arrays.\n' - 'Explicitly cast the data array to a supported dtype or pass an ' - 'explicit "datatype" parameter to GiftiDataArray().' - ) - self.datatype = data_type_codes.code[datatype] - self.encoding = gifti_encoding_codes.code[encoding] - self.endian = gifti_endian_codes.code[endian] - self.coordsys = coordsys or GiftiCoordSystem() - self.ind_ord = array_index_order_codes.code[ordering] - self.meta = ( - GiftiMetaData() - if meta is None - else meta - if isinstance(meta, GiftiMetaData) - else GiftiMetaData(meta) - ) - self.ext_fname = ext_fname - self.ext_offset = ext_offset - self.dims = [] if self.data is None else list(self.data.shape) - - def __repr__(self): - return f'' - - @property - def num_dim(self): - return len(self.dims) - - def _to_xml_element(self): - # fix endianness to machine endianness - self.endian = gifti_endian_codes.code[sys.byteorder] - - # All attribute values must be strings - data_array = xml.Element( - 'DataArray', - attrib={ - 'Intent': intent_codes.niistring[self.intent], - 'DataType': data_type_codes.niistring[self.datatype], - 'ArrayIndexingOrder': array_index_order_codes.label[self.ind_ord], - 'Dimensionality': str(self.num_dim), - 'Encoding': gifti_encoding_codes.specs[self.encoding], - 'Endian': gifti_endian_codes.specs[self.endian], - 'ExternalFileName': self.ext_fname, - 'ExternalFileOffset': str(self.ext_offset), - }, - ) - for di, dn in enumerate(self.dims): - data_array.attrib[f'Dim{di}'] = str(dn) - - if self.meta is not None: - data_array.append(self.meta._to_xml_element()) - if self.coordsys is not None: - data_array.append(self.coordsys._to_xml_element()) - # write data array depending on the encoding - data_array.append( - _data_tag_element( - self.data, - gifti_encoding_codes.specs[self.encoding], - data_type_codes.dtype[self.datatype], - self.ind_ord, - ) - ) - - return data_array - - def print_summary(self): - print('Intent: ', intent_codes.niistring[self.intent]) - print('DataType: ', data_type_codes.niistring[self.datatype]) - print('ArrayIndexingOrder: ', array_index_order_codes.label[self.ind_ord]) - print('Dimensionality: ', self.num_dim) - print('Dimensions: ', self.dims) - print('Encoding: ', gifti_encoding_codes.specs[self.encoding]) - print('Endian: ', gifti_endian_codes.specs[self.endian]) - print('ExternalFileName: ', self.ext_fname) - print('ExternalFileOffset: ', self.ext_offset) - if self.coordsys is not None: - print('----') - print('Coordinate System:') - print(self.coordsys.print_summary()) - - @property - def metadata(self): - """Returns metadata as dictionary""" - return dict(self.meta) - - -class GiftiImage(xml.XmlSerializable, SerializableImage): - """GIFTI image object - - The Gifti spec suggests using the following suffixes to your - filename when saving each specific type of data: - - .gii - Generic GIFTI File - .coord.gii - Coordinates - .func.gii - Functional - .label.gii - Labels - .rgba.gii - RGB or RGBA - .shape.gii - Shape - .surf.gii - Surface - .tensor.gii - Tensors - .time.gii - Time Series - .topo.gii - Topology - - The Gifti file is stored in endian convention of the current machine. - """ - - valid_exts = ('.gii',) - files_types = (('image', '.gii'),) - _compressed_suffixes = ('.gz', '.bz2') - - # 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: type[xml.XmlParser] - - def __init__( - self, - header=None, - extra=None, - file_map=None, - meta=None, - labeltable=None, - darrays=None, - version='1.0', - ): - super().__init__(header=header, extra=extra, file_map=file_map) - if darrays is None: - darrays = [] - if meta is None: - meta = GiftiMetaData() - if labeltable is None: - labeltable = GiftiLabelTable() - - self._labeltable = labeltable - self._meta = meta - - self.darrays = darrays - self.version = version - - @property - def numDA(self): - return len(self.darrays) - - @property - def labeltable(self): - return self._labeltable - - @labeltable.setter - def labeltable(self, labeltable): - """Set the labeltable for this GiftiImage - - Parameters - ---------- - labeltable : :class:`GiftiLabelTable` instance - """ - if not isinstance(labeltable, GiftiLabelTable): - raise TypeError('Not a valid GiftiLabelTable instance') - self._labeltable = labeltable - - @property - def meta(self): - return self._meta - - @meta.setter - def meta(self, meta): - """Set the metadata for this GiftiImage - - Parameters - ---------- - meta : :class:`GiftiMetaData` instance - """ - if not isinstance(meta, GiftiMetaData): - raise TypeError('Not a valid GiftiMetaData instance') - self._meta = meta - - def add_gifti_data_array(self, dataarr): - """Adds a data array to the GiftiImage - - Parameters - ---------- - dataarr : :class:`GiftiDataArray` instance - """ - if not isinstance(dataarr, GiftiDataArray): - raise TypeError('Not a valid GiftiDataArray instance') - self.darrays.append(dataarr) - - def remove_gifti_data_array(self, ith): - """Removes the ith data array element from the GiftiImage""" - self.darrays.pop(ith) - - def remove_gifti_data_array_by_intent(self, intent): - """Removes all the data arrays with the given intent type""" - intent2remove = intent_codes.code[intent] - for dele in self.darrays: - if dele.intent == intent2remove: - self.darrays.remove(dele) - - def get_arrays_from_intent(self, intent): - """Return list of GiftiDataArray elements matching given intent""" - it = intent_codes.code[intent] - return [x for x in self.darrays if x.intent == it] - - def agg_data(self, intent_code=None): - """ - Aggregate GIFTI data arrays into an ndarray or tuple of ndarray - - In the general case, the numpy data array is extracted from each ``GiftiDataArray`` - object and returned in a ``tuple``, in the order they are found in the GIFTI image. - - If all ``GiftiDataArray`` s have ``intent`` of 2001 (``NIFTI_INTENT_TIME_SERIES``), - then the data arrays are concatenated as columns, producing a vertex-by-time array. - If an ``intent_code`` is passed, data arrays are filtered by the selected intents, - before being aggregated. - This may be useful for images containing several intents, or ensuring an expected - data type in an image of uncertain provenance. - If ``intent_code`` is a ``tuple``, then a ``tuple`` will be returned with the result of - ``agg_data`` for each element, in order. - This may be useful for ensuring that expected data arrives in a consistent order. - - Parameters - ---------- - intent_code : None, string, integer or tuple of strings or integers, optional - code(s) specifying nifti intent - - Returns - ------- - tuple of ndarrays or ndarray - If the input is a tuple, the returned tuple will match the order. - - Examples - -------- - - Consider a surface GIFTI file: - - >>> import nibabel as nib - >>> from nibabel.testing import get_test_data - >>> surf_img = nib.load(get_test_data('gifti', 'ascii.gii')) - - The coordinate data, which is indicated by the ``NIFTI_INTENT_POINTSET`` - intent code, may be retrieved using any of the following equivalent - calls: - - >>> coords = surf_img.agg_data('NIFTI_INTENT_POINTSET') - >>> coords_2 = surf_img.agg_data('pointset') - >>> coords_3 = surf_img.agg_data(1008) # Numeric code for pointset - >>> print(np.array2string(coords, precision=3)) - [[-16.072 -66.188 21.267] - [-16.706 -66.054 21.233] - [-17.614 -65.402 21.071]] - >>> np.array_equal(coords, coords_2) - True - >>> np.array_equal(coords, coords_3) - True - - Similarly, the triangle mesh can be retrieved using various intent - specifiers: - - >>> triangles = surf_img.agg_data('NIFTI_INTENT_TRIANGLE') - >>> triangles_2 = surf_img.agg_data('triangle') - >>> triangles_3 = surf_img.agg_data(1009) # Numeric code for pointset - >>> print(np.array2string(triangles)) - [[0 1 2]] - >>> np.array_equal(triangles, triangles_2) - True - >>> np.array_equal(triangles, triangles_3) - True - - All arrays can be retrieved as a ``tuple`` by omitting the intent - code: - - >>> coords_4, triangles_4 = surf_img.agg_data() - >>> np.array_equal(coords, coords_4) - True - >>> np.array_equal(triangles, triangles_4) - True - - Finally, a tuple of intent codes may be passed in order to select - the arrays in a specific order: - - >>> triangles_5, coords_5 = surf_img.agg_data(('triangle', 'pointset')) - >>> np.array_equal(triangles, triangles_5) - True - >>> np.array_equal(coords, coords_5) - True - - The following image is a GIFTI file with ten (10) data arrays of the same - size, and with intent code 2001 (``NIFTI_INTENT_TIME_SERIES``): - - >>> func_img = nib.load(get_test_data('gifti', 'task.func.gii')) - - When aggregating time series data, these arrays are concatenated into - a single, vertex-by-timestep array: - - >>> series = func_img.agg_data() - >>> series.shape - (642, 10) - - In the case of a GIFTI file with unknown data arrays, it may be preferable - to specify the intent code, so that a time series array is always returned: - - >>> series_2 = func_img.agg_data('NIFTI_INTENT_TIME_SERIES') - >>> series_3 = func_img.agg_data('time series') - >>> series_4 = func_img.agg_data(2001) - >>> np.array_equal(series, series_2) - True - >>> np.array_equal(series, series_3) - True - >>> np.array_equal(series, series_4) - True - - Requesting a data array from a GIFTI file with no matching intent codes - will result in an empty tuple: - - >>> surf_img.agg_data('time series') - () - >>> func_img.agg_data('triangle') - () - """ - - # Allow multiple intents to specify the order - # e.g., agg_data(('pointset', 'triangle')) ensures consistent order - - if isinstance(intent_code, tuple): - return tuple(self.agg_data(intent_code=code) for code in intent_code) - - darrays = self.darrays if intent_code is None else self.get_arrays_from_intent(intent_code) - all_data = tuple(da.data for da in darrays) - all_intent = {intent_codes.niistring[da.intent] for da in darrays} - - if all_intent == {'NIFTI_INTENT_TIME_SERIES'}: # stack when the gifti is a timeseries - return np.column_stack(all_data) - - if len(all_data) == 1: - all_data = all_data[0] - - return all_data - - def print_summary(self): - print('----start----') - print('Source filename: ', self.get_filename()) - print('Number of data arrays: ', self.numDA) - print('Version: ', self.version) - if self.meta is not None: - print('----') - print('Metadata:') - print(self.meta.print_summary()) - if self.labeltable is not None: - print('----') - print('Labeltable:') - print(self.labeltable.print_summary()) - for i, da in enumerate(self.darrays): - print('----') - print(f'DataArray {i}:') - print(da.print_summary()) - print('----end----') - - def _to_xml_element(self): - GIFTI = xml.Element( - 'GIFTI', attrib={'Version': self.version, 'NumberOfDataArrays': str(self.numDA)} - ) - if self.meta is not None: - GIFTI.append(self.meta._to_xml_element()) - if self.labeltable is not None: - GIFTI.append(self.labeltable._to_xml_element()) - for dar in self.darrays: - GIFTI.append(dar._to_xml_element()) - return GIFTI - - def to_xml(self, enc='utf-8', *, mode='strict', **kwargs) -> bytes: - """Return XML corresponding to image content""" - if mode == 'strict': - if any(arr.datatype not in GIFTI_DTYPES for arr in self.darrays): - raise ValueError( - 'GiftiImage contains data arrays with invalid data types; ' - 'use mode="compat" to automatically cast to conforming types' - ) - elif mode == 'compat': - darrays = [] - for arr in self.darrays: - if arr.datatype not in GIFTI_DTYPES: - arr = copy(arr) - # TODO: Better typing for recoders - dtype = cast('np.dtype', data_type_codes.dtype[arr.datatype]) - if np.issubdtype(dtype, np.floating): - arr.datatype = data_type_codes['float32'] - elif np.issubdtype(dtype, np.integer): - arr.datatype = data_type_codes['int32'] - else: - raise ValueError(f'Cannot convert {dtype} to float32/int32') - darrays.append(arr) - gii = copy(self) - gii.darrays = darrays - return gii.to_xml(enc=enc, mode='strict') - elif mode != 'force': - raise TypeError(f'Unknown mode {mode}') - header = b""" - -""" - return header + super().to_xml(enc, **kwargs) - - # Avoid the indirection of going through to_file_map - def to_bytes(self, enc='utf-8', *, mode='strict'): - return self.to_xml(enc=enc, mode=mode) - - to_bytes.__doc__ = SerializableImage.to_bytes.__doc__ - - def to_file_map(self, file_map=None, enc='utf-8', *, mode='strict'): - """Save the current image to the specified file_map - - Parameters - ---------- - file_map : dict - Dictionary with single key ``image`` with associated value which is - a :class:`FileHolder` instance pointing to the image file. - - Returns - ------- - 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(enc=enc, mode=mode)) - - @classmethod - def from_file_map(klass, file_map, buffer_size=35000000, mmap=True): - """Load a Gifti image from a file_map - - Parameters - ---------- - file_map : dict - Dictionary with single key ``image`` with associated value which is - a :class:`FileHolder` instance pointing to the image file. - - buffer_size: None or int, optional - size of read buffer. None uses default buffer_size - from xml.parsers.expat. - - mmap : {True, False, 'c', 'r', 'r+'} - Controls the use of numpy memory mapping for reading data. Only - has an effect when loading GIFTI images with data stored in - external files (``DataArray`` elements with an ``Encoding`` equal - to ``ExternalFileBinary``). If ``False``, do not try numpy - ``memmap`` for data array. If one of ``{'c', 'r', 'r+'}``, try - numpy ``memmap`` with ``mode=mmap``. A `mmap` value of ``True`` - gives the same behavior as ``mmap='c'``. If the file cannot be - memory-mapped, ignore `mmap` value and read array from file. - - Returns - ------- - img : GiftiImage - """ - parser = klass.parser(buffer_size=buffer_size, mmap=mmap) - with file_map['image'].get_prepare_fileobj('rb') as fptr: - parser.parse(fptr=fptr) - return parser.img - - @classmethod - def from_filename(klass, filename, buffer_size=35000000, mmap=True): - file_map = klass.filespec_to_file_map(filename) - img = klass.from_file_map(file_map, buffer_size=buffer_size, mmap=mmap) - return img - - -# Now GiftiImage is defined, we can import the parser module and set the parser -from .parse_gifti_fast import GiftiImageParser - -GiftiImage.parser = GiftiImageParser diff --git a/nibabel/gifti/parse_gifti_fast.py b/nibabel/gifti/parse_gifti_fast.py deleted file mode 100644 index 5bcd8c8c32..0000000000 --- a/nibabel/gifti/parse_gifti_fast.py +++ /dev/null @@ -1,400 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## - -import base64 -import os.path as op -import sys -import warnings -import zlib -from io import StringIO -from xml.parsers.expat import ExpatError - -import numpy as np - -from ..nifti1 import data_type_codes, intent_codes, xform_codes -from ..xmlutils import XmlParser -from .gifti import ( - GiftiCoordSystem, - GiftiDataArray, - GiftiImage, - GiftiLabel, - GiftiLabelTable, - GiftiMetaData, -) -from .util import array_index_order_codes, gifti_encoding_codes, gifti_endian_codes - - -class GiftiParseError(ExpatError): - """Gifti-specific parsing error""" - - -def read_data_block(darray, fname, data, mmap): - """Parses data from a element, or loads from an external file. - - Parameters - ---------- - darray : GiftiDataArray - GiftiDataArray object representing the parent of this - element - - fname : str or None - Name of GIFTI file being loaded, or None if in-memory - - data : str or None - Data to parse, or None if data is in an external file - - mmap : {True, False, 'c', 'r', 'r+'} - Controls the use of numpy memory mapping for reading data. Only has - an effect when loading GIFTI images with data stored in external files - (``DataArray`` elements with an ``Encoding`` equal to - ``ExternalFileBinary``). If ``False``, do not try numpy ``memmap`` - for data array. If one of ``{'c', 'r', 'r+'}``, try numpy ``memmap`` - with ``mode=mmap``. A `mmap` value of ``True`` gives the same - behavior as ``mmap='c'``. If the file cannot be memory-mapped, ignore - `mmap` value and read array from file. - - Returns - ------- - ``numpy.ndarray`` or ``numpy.memmap`` containing the parsed data - """ - if mmap not in (True, False, 'c', 'r', 'r+'): - raise ValueError("mmap value should be one of True, False, 'c', 'r', 'r+'") - if mmap is True: - mmap = 'c' - enclabel = gifti_encoding_codes.label[darray.encoding] - - if enclabel not in ('ASCII', 'B64BIN', 'B64GZ', 'External'): - raise GiftiParseError(f'Unknown encoding {darray.encoding}') - - # Encode the endianness in the dtype - byteorder = gifti_endian_codes.byteorder[darray.endian] - dtype = data_type_codes.dtype[darray.datatype].newbyteorder(byteorder) - - shape = tuple(darray.dims) - order = array_index_order_codes.npcode[darray.ind_ord] - - # GIFTI_ENCODING_ASCII - if enclabel == 'ASCII': - return np.loadtxt(StringIO(data), dtype=dtype, ndmin=1).reshape(shape, order=order) - - # We assume that the external data file is raw uncompressed binary, with - # the data type/endianness/ordering specified by the other DataArray - # attributes - if enclabel == 'External': - if fname is None: - raise GiftiParseError( - 'ExternalFileBinary is not supported when loading from in-memory XML' - ) - ext_fname = op.join(op.dirname(fname), darray.ext_fname) - if not op.exists(ext_fname): - raise GiftiParseError('Cannot locate external file ' + ext_fname) - # We either create a memmap, or load into memory - newarr = None - if mmap: - try: - return np.memmap( - ext_fname, - dtype=dtype, - mode=mmap, - offset=darray.ext_offset, - shape=shape, - order=order, - ) - # If the memmap fails, we ignore the error and load the data into - # memory below - except (AttributeError, TypeError, ValueError): - pass - # mmap=False or np.memmap failed - if newarr is None: - return np.fromfile( - ext_fname, - dtype=dtype, - count=np.prod(darray.dims), - offset=darray.ext_offset, - ).reshape(shape, order=order) - - # Numpy arrays created from bytes objects are read-only. - # Neither b64decode nor decompress will return bytearrays, and there - # are not equivalents to fobj.readinto to allow us to pass them, so - # there is not a simple way to avoid making copies. - # If this becomes a problem, we should write a decoding interface with - # a tunable chunk size. - dec = base64.b64decode(data.encode('ascii')) - if enclabel == 'B64BIN': - buff = bytearray(dec) - else: - # GIFTI_ENCODING_B64GZ - buff = bytearray(zlib.decompress(dec)) - del dec - return np.frombuffer(buff, dtype=dtype).reshape(shape, order=order) - - -def _str2int(in_str): - # Convert string to integer, where empty string gives 0 - return int(in_str) if in_str else 0 - - -class GiftiImageParser(XmlParser): - def __init__(self, encoding=None, buffer_size=35000000, verbose=0, mmap=True): - super().__init__(encoding=encoding, buffer_size=buffer_size, verbose=verbose) - # output - self.img = None - - # Queried when loading data from elements - see read_data_block - self.mmap = mmap - - # finite state machine stack - self.fsm_state = [] - - # temporary constructs - self.nvpair = None - self.da = None - self.coordsys = None - self.lata = None - self.label = None - - self.meta_global = None - self.meta_da = None - self.count_da = True - - # where to write CDATA: - self.write_to = None - - # Collecting char buffer fragments - self._char_blocks = None - - def StartElementHandler(self, name, attrs): - self.flush_chardata() - if self.verbose > 0: - print('Start element:\n\t', repr(name), attrs) - - if name == 'GIFTI': - # create gifti image - self.img = GiftiImage() - if 'Version' in attrs: - self.img.version = attrs['Version'] - if 'NumberOfDataArrays' in attrs: - self.expected_numDA = int(attrs['NumberOfDataArrays']) - self.fsm_state.append('GIFTI') - - elif name == 'MetaData': - self.fsm_state.append('MetaData') - # if this metadata tag is first, create self.img.meta - if len(self.fsm_state) == 2: - self.meta_global = GiftiMetaData() - else: - # otherwise, create darray.meta - self.meta_da = GiftiMetaData() - - elif name == 'MD': - self.nvpair = ['', ''] - self.fsm_state.append('MD') - - elif name == 'Name': - if self.nvpair is None: - raise GiftiParseError - self.write_to = 'Name' - - elif name == 'Value': - if self.nvpair is None: - raise GiftiParseError - self.write_to = 'Value' - - elif name == 'LabelTable': - self.lata = GiftiLabelTable() - self.fsm_state.append('LabelTable') - - elif name == 'Label': - self.label = GiftiLabel() - if 'Index' in attrs: - self.label.key = int(attrs['Index']) - if 'Key' in attrs: - self.label.key = int(attrs['Key']) - if 'Red' in attrs: - self.label.red = float(attrs['Red']) - if 'Green' in attrs: - self.label.green = float(attrs['Green']) - if 'Blue' in attrs: - self.label.blue = float(attrs['Blue']) - if 'Alpha' in attrs: - self.label.alpha = float(attrs['Alpha']) - self.write_to = 'Label' - - elif name == 'DataArray': - self.da = GiftiDataArray() - if 'Intent' in attrs: - self.da.intent = intent_codes.code[attrs['Intent']] - if 'DataType' in attrs: - self.da.datatype = data_type_codes.code[attrs['DataType']] - if 'ArrayIndexingOrder' in attrs: - self.da.ind_ord = array_index_order_codes.code[attrs['ArrayIndexingOrder']] - num_dim = int(attrs.get('Dimensionality', 0)) - for i in range(num_dim): - di = f'Dim{i}' - if di in attrs: - self.da.dims.append(int(attrs[di])) - # dimensionality has to correspond to the number of DimX given - # TODO (bcipolli): don't assert; raise parse warning, and recover. - assert len(self.da.dims) == num_dim - if 'Encoding' in attrs: - self.da.encoding = gifti_encoding_codes.code[attrs['Encoding']] - if 'Endian' in attrs: - self.da.endian = gifti_endian_codes.code[attrs['Endian']] - if 'ExternalFileName' in attrs: - self.da.ext_fname = attrs['ExternalFileName'] - if 'ExternalFileOffset' in attrs: - self.da.ext_offset = _str2int(attrs['ExternalFileOffset']) - self.img.darrays.append(self.da) - self.fsm_state.append('DataArray') - - elif name == 'CoordinateSystemTransformMatrix': - self.coordsys = GiftiCoordSystem() - self.img.darrays[-1].coordsys = self.coordsys - self.fsm_state.append('CoordinateSystemTransformMatrix') - - elif name == 'DataSpace': - if self.coordsys is None: - raise GiftiParseError - self.write_to = 'DataSpace' - - elif name == 'TransformedSpace': - if self.coordsys is None: - raise GiftiParseError - self.write_to = 'TransformedSpace' - - elif name == 'MatrixData': - if self.coordsys is None: - raise GiftiParseError - self.write_to = 'MatrixData' - - elif name == 'Data': - self.write_to = 'Data' - - def EndElementHandler(self, name): - self.flush_chardata() - if self.verbose > 0: - print('End element:\n\t', repr(name)) - - if name == 'GIFTI': - if hasattr(self, 'expected_numDA') and self.expected_numDA != self.img.numDA: - warnings.warn( - 'Actual # of data arrays does not match # expected: ' - f'{self.expected_numDA} != {self.img.numDA}.' - ) - # remove last element of the list - self.fsm_state.pop() - # assert len(self.fsm_state) == 0 - - elif name == 'MetaData': - self.fsm_state.pop() - if len(self.fsm_state) == 1: - # only Gifti there, so this was a closing global - # metadata tag - self.img.meta = self.meta_global - self.meta_global = None - else: - self.img.darrays[-1].meta = self.meta_da - self.meta_da = None - - elif name == 'MD': - self.fsm_state.pop() - key, val = self.nvpair - if self.meta_global is not None and self.meta_da is None: - self.meta_global[key] = val - elif self.meta_da is not None and self.meta_global is None: - self.meta_da[key] = val - # remove reference - self.nvpair = None - - elif name == 'LabelTable': - self.fsm_state.pop() - # add labeltable - self.img.labeltable = self.lata - self.lata = None - - elif name == 'DataArray': - self.fsm_state.pop() - - elif name == 'CoordinateSystemTransformMatrix': - self.fsm_state.pop() - self.coordsys = None - - elif name in ('DataSpace', 'TransformedSpace', 'MatrixData', 'Name', 'Value', 'Data'): - self.write_to = None - - elif name == 'Label': - self.lata.labels.append(self.label) - self.label = None - self.write_to = None - - def CharacterDataHandler(self, data): - """Collect character data chunks pending collation - - The parser breaks the data up into chunks of size depending on the - buffer_size of the parser. A large bit of character data, with - standard parser buffer_size (such as 8K) can easily span many calls to - this function. We thus collect the chunks and process them when we - hit start or end tags. - """ - if self._char_blocks is None: - self._char_blocks = [] - self._char_blocks.append(data) - - def flush_chardata(self): - """Collate and process collected character data""" - # Nothing to do for empty elements, except for Data elements which - # are within a DataArray with an external file - if self.write_to != 'Data' and self._char_blocks is None: - return - # Just join the strings to get the data. Maybe there are some memory - # optimizations we could do by passing the list of strings to the - # read_data_block function. - if self._char_blocks is not None: - data = ''.join(self._char_blocks) - else: - data = None - # Reset the char collector - self._char_blocks = None - - # Process data - if self.write_to == 'Name': - data = data.strip() - self.nvpair[0] = data - - elif self.write_to == 'Value': - data = data.strip() - self.nvpair[1] = data - - elif self.write_to == 'DataSpace': - data = data.strip() - self.coordsys.dataspace = xform_codes.code[data] - - elif self.write_to == 'TransformedSpace': - data = data.strip() - self.coordsys.xformspace = xform_codes.code[data] - - elif self.write_to == 'MatrixData': - # conversion to numpy array - c = StringIO(data) - self.coordsys.xform = np.loadtxt(c) - c.close() - - elif self.write_to == 'Data': - self.da.data = read_data_block(self.da, self.fname, data, self.mmap) - # update the endianness according to the - # current machine setting - self.endian = gifti_endian_codes.code[sys.byteorder] - - elif self.write_to == 'Label': - self.label.label = data.strip() - - @property - def pending_data(self): - """True if there is character data pending for processing""" - return self._char_blocks is not None diff --git a/nibabel/gifti/tests/__init__.py b/nibabel/gifti/tests/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/nibabel/gifti/tests/data/ascii.gii b/nibabel/gifti/tests/data/ascii.gii deleted file mode 100644 index 6f4bfd6e11..0000000000 --- a/nibabel/gifti/tests/data/ascii.gii +++ /dev/null @@ -1,87 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1.000000 0.000000 0.000000 0.000000 - 0.000000 1.000000 0.000000 0.000000 - 0.000000 0.000000 1.000000 0.000000 - 0.000000 0.000000 0.000000 1.000000 - - - - -16.072010 -66.187515 21.266994 - -16.705893 -66.054337 21.232786 - -17.614349 -65.401642 21.071466 - - - - - - - - - - - - - - - 0 1 2 - - - diff --git a/nibabel/gifti/tests/data/ascii_flat_data.gii b/nibabel/gifti/tests/data/ascii_flat_data.gii deleted file mode 100644 index 26a73fba02..0000000000 --- a/nibabel/gifti/tests/data/ascii_flat_data.gii +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 - - 155.17539978 135.58103943 98.30715179 140.33973694 190.0491333 73.24776459 157.3598938 196.97969055 83.65809631 171.46174622 137.43661499 78.4709549 148.54592896 97.06752777 65.96373749 123.45701599 111.46841431 66.3571167 135.30892944 202.28720093 36.38148499 178.28155518 162.59469604 37.75128937 178.11087036 115.28820038 57.17986679 142.81582642 82.82115173 31.02205276 - - - - - - - - - - - - - 6402 17923 25602 14085 25602 17923 25602 14085 4483 17923 1602 14085 4483 25603 25602 25604 25602 25603 25602 25604 6402 25603 3525 25604 1123 17922 12168 25604 12168 17922 - - diff --git a/nibabel/gifti/tests/data/base64bin.gii b/nibabel/gifti/tests/data/base64bin.gii deleted file mode 100644 index 7b721a19dc..0000000000 --- a/nibabel/gifti/tests/data/base64bin.gii +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - - - - - - - - - - - 1.000000 0.000000 0.000000 0.000000 - 0.000000 1.000000 0.000000 0.000000 - 0.000000 0.000000 1.000000 0.000000 - 0.000000 0.000000 0.000000 1.000000 - - -5ywbQ7+UB0NDncRC+VYMQ5QMPkPbfpJCIlwdQ836REPyUKdCNXYrQ8ZvCUMh8ZxCwosUQ5MiwkJv -7YNC/un2QtTv3kLYtoRCFk8HQ4ZJSkOkhhFCFEgyQz6YIkNSARdCYhwyQ4+T5kIvuGRC2tAOQ26k -pUIqLfhB - - - - - - - - - 1.000000 0.000000 0.000000 0.000000 - 0.000000 1.000000 0.000000 0.000000 - 0.000000 0.000000 1.000000 0.000000 - 0.000000 0.000000 0.000000 1.000000 - - -AhkAAANGAAACZAAABTcAAAJkAAADRgAAAmQAAAU3AACDEQAAA0YAAEIGAAAFNwAAgxEAAANkAAAC -ZAAABGQAAAJkAAADZAAAAmQAAARkAAACGQAAA2QAAMUNAAAEZAAAYwQAAAJGAACILwAABGQAAIgv -AAACRgAA - - - \ No newline at end of file diff --git a/nibabel/gifti/tests/data/external.dat b/nibabel/gifti/tests/data/external.dat deleted file mode 100644 index 6077ad13c3..0000000000 Binary files a/nibabel/gifti/tests/data/external.dat and /dev/null differ diff --git a/nibabel/gifti/tests/data/external.gii b/nibabel/gifti/tests/data/external.gii deleted file mode 100644 index c2e5d97fbb..0000000000 --- a/nibabel/gifti/tests/data/external.gii +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - 1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 - - - - - - - - - diff --git a/nibabel/gifti/tests/data/external.gii.bz2 b/nibabel/gifti/tests/data/external.gii.bz2 deleted file mode 100644 index 371cde11df..0000000000 Binary files a/nibabel/gifti/tests/data/external.gii.bz2 and /dev/null differ diff --git a/nibabel/gifti/tests/data/external.gii.gz b/nibabel/gifti/tests/data/external.gii.gz deleted file mode 100644 index f5d288512f..0000000000 Binary files a/nibabel/gifti/tests/data/external.gii.gz and /dev/null differ diff --git a/nibabel/gifti/tests/data/gzipbase64.gii b/nibabel/gifti/tests/data/gzipbase64.gii deleted file mode 100755 index b4fb117cd2..0000000000 --- a/nibabel/gifti/tests/data/gzipbase64.gii +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 - - diff --git a/nibabel/gifti/tests/data/label.gii b/nibabel/gifti/tests/data/label.gii deleted file mode 100644 index 40c8832087..0000000000 --- a/nibabel/gifti/tests/data/label.gii +++ /dev/null @@ -1,71776 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 2 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 1 - 2 - 2 - 2 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 0 - 2 - 0 - 0 - 2 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 2 - 2 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 2 - 2 - 2 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 1 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 2 - 2 - 2 - 0 - 1 - 1 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 1 - 1 - 1 - 2 - 2 - 2 - 2 - 1 - 1 - 0 - 1 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 1 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 1 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 2 - 0 - 2 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 1 - 2 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 1 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 1 - 2 - 2 - 2 - 1 - 1 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 1 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 2 - 1 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 0 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 2 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 2 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 2 - 0 - 2 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 2 - 2 - 1 - 1 - 1 - 1 - 2 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 1 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 1 - 0 - 2 - 2 - 2 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 2 - 2 - 2 - 1 - 0 - 0 - 0 - 2 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 1 - 2 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 2 - 0 - 0 - 1 - 2 - 2 - 2 - 2 - 2 - 1 - 1 - 1 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 1 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 1 - 2 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 0 - 2 - 2 - 1 - 2 - 2 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 2 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 2 - 2 - 2 - 1 - 2 - 2 - 1 - 1 - 0 - 0 - 1 - 2 - 0 - 1 - 1 - 2 - 0 - 2 - 2 - 1 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 1 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 1 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 1 - 1 - 0 - 2 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 2 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 2 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 1 - 2 - 0 - 2 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 1 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 2 - 2 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 1 - 1 - 0 - 2 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 1 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 2 - 2 - 2 - 2 - 1 - 1 - 2 - 2 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 2 - 2 - 0 - 1 - 2 - 2 - 0 - 0 - 1 - 1 - 0 - 1 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 1 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 0 - 1 - 0 - 0 - 2 - 2 - 1 - 1 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 1 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 2 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 2 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 1 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 0 - 2 - 0 - 1 - 0 - 2 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 2 - 1 - 0 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 2 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 1 - 1 - 1 - 0 - 2 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 2 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 1 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 2 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 2 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 1 - 2 - 2 - 2 - 0 - 1 - 0 - 0 - 0 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 2 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 2 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 2 - 1 - 2 - 2 - 2 - 1 - 0 - 0 - 1 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 1 - 2 - 2 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 2 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 2 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 2 - 2 - 1 - 2 - 2 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 1 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 2 - 0 - 1 - 2 - 2 - 1 - 1 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 1 - 2 - 2 - 2 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 1 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 2 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 2 - 2 - 2 - 2 - 2 - 2 - 1 - 1 - 1 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 1 - 0 - 1 - 2 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 2 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 2 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 1 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 1 - 0 - 0 - 0 - 2 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 1 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 2 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 2 - 1 - 2 - 2 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 2 - 2 - 2 - 2 - 0 - 0 - 1 - 2 - 1 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 2 - 2 - 2 - 2 - 2 - 1 - 2 - 2 - 1 - 1 - 1 - 2 - 2 - 1 - 1 - 1 - 2 - 2 - 2 - 2 - 1 - 1 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 2 - 1 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 2 - 0 - 1 - 0 - 2 - 0 - 0 - 2 - 2 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 2 - 1 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 2 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 1 - 2 - 2 - 1 - 0 - 2 - 2 - 0 - 0 - 1 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 1 - 1 - 2 - 2 - 1 - 1 - 1 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 1 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 1 - 1 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 1 - 1 - 0 - 0 - 2 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 1 - 2 - 2 - 0 - 0 - 1 - 1 - 0 - 1 - 2 - 2 - 0 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 1 - 1 - 2 - 0 - 0 - 1 - 2 - 0 - 2 - 2 - 2 - 0 - 2 - 0 - 2 - 2 - 1 - 1 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 1 - 2 - 2 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 1 - 1 - 2 - 2 - 2 - 2 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 2 - 2 - 0 - 1 - 2 - 2 - 2 - 2 - 2 - 2 - 1 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 2 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 1 - 0 - 0 - 0 - 2 - 2 - 1 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 2 - 1 - 1 - 2 - 0 - 0 - 1 - 1 - 0 - 2 - 2 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 2 - 2 - 0 - 1 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 1 - 2 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 2 - 0 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 2 - 2 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 1 - 2 - 2 - 2 - 2 - 2 - 2 - 1 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 2 - 0 - 0 - 1 - 0 - 0 - 2 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 1 - 1 - 1 - 1 - 1 - 2 - 0 - 0 - 2 - 0 - 2 - 0 - 2 - 0 - 2 - 1 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 1 - 1 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 1 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 1 - 0 - 2 - 2 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 1 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 1 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 1 - 2 - 2 - 2 - 2 - 2 - 2 - 1 - 1 - 2 - 2 - 2 - 2 - 1 - 0 - 2 - 1 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 1 - 0 - 0 - 0 - 2 - 2 - 1 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 2 - 0 - 1 - 1 - 1 - 2 - 0 - 0 - 2 - 2 - 2 - 1 - 2 - 2 - 2 - 2 - 2 - 1 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 0 - 1 - 1 - 2 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 1 - 1 - 2 - 1 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 1 - 0 - 1 - 2 - 2 - 2 - 2 - 1 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 2 - 2 - 1 - 1 - 0 - 2 - 2 - 2 - 2 - 1 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 1 - 0 - 2 - 0 - 0 - 1 - 0 - 2 - 2 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 2 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 2 - 0 - 1 - 1 - 0 - 1 - 0 - 1 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 1 - 2 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 1 - 0 - 0 - 2 - 2 - 2 - 0 - 1 - 2 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 2 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 2 - 0 - 0 - 1 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 1 - 2 - 2 - 1 - 1 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 2 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 1 - 2 - 2 - 2 - 1 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 1 - 0 - 2 - 0 - 2 - 2 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 1 - 0 - 0 - 2 - 2 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 2 - 2 - 2 - 1 - 0 - 2 - 2 - 1 - 0 - 0 - 0 - 0 - 2 - 0 - 1 - 0 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 2 - 2 - 2 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 1 - 2 - 0 - 2 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 1 - 1 - 2 - 0 - 0 - 2 - 1 - 1 - 1 - 1 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 1 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 1 - 2 - 2 - 2 - 2 - 0 - 1 - 1 - 0 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 1 - 0 - 1 - 1 - 2 - 2 - 2 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 0 - 2 - 2 - 0 - 1 - 0 - 2 - 1 - 1 - 2 - 2 - 2 - 2 - 1 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 2 - 2 - 1 - 1 - 1 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 1 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 1 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 2 - 2 - 0 - 0 - 1 - 0 - 2 - 0 - 1 - 0 - 0 - 2 - 0 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 2 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 1 - 1 - 1 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 1 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 1 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 1 - 1 - 0 - 1 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 2 - 2 - 1 - 2 - 2 - 2 - 1 - 0 - 1 - 0 - 2 - 2 - 2 - 2 - 2 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 2 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 2 - 2 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 1 - 2 - 0 - 0 - 1 - 0 - 1 - 0 - 2 - 1 - 2 - 2 - 2 - 1 - 2 - 2 - 2 - 1 - 2 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 2 - 1 - 1 - 2 - 1 - 2 - 2 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 2 - 2 - 2 - 2 - 2 - 2 - 1 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 1 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 2 - 2 - 2 - 2 - 2 - 2 - 1 - 0 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 2 - 1 - 2 - 2 - 2 - 0 - 0 - 1 - 1 - 1 - 1 - 2 - 0 - 2 - 1 - 1 - 2 - 0 - 2 - 2 - 0 - 2 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 2 - 1 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 2 - 1 - 1 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 2 - 0 - 1 - 0 - 1 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 2 - 1 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 1 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 1 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 1 - 0 - 2 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 2 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 2 - 0 - 2 - 1 - 1 - 0 - 2 - 2 - 2 - 2 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 1 - 1 - 2 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 2 - 1 - 1 - 0 - 2 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 1 - 1 - 1 - 0 - 2 - 2 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 1 - 1 - 1 - 1 - 2 - 2 - 1 - 1 - 1 - 1 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 0 - 2 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 2 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 2 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 1 - 0 - 1 - 1 - 0 - 2 - 0 - 2 - 1 - 2 - 1 - 1 - 1 - 0 - 0 - 0 - 2 - 1 - 0 - 0 - 2 - 2 - 1 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 1 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 1 - 2 - 2 - 1 - 1 - 1 - 1 - 2 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 2 - 2 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 2 - 0 - 1 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 2 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 1 - 1 - 0 - 2 - 2 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 1 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 2 - 1 - 2 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 2 - 0 - 0 - 1 - 0 - 0 - 1 - 2 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 2 - 2 - 1 - 1 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 2 - 2 - 2 - 1 - 1 - 0 - 2 - 2 - 2 - 0 - 1 - 1 - 1 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 2 - 2 - 2 - 2 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 2 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 2 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 2 - 0 - 1 - 0 - 0 - 2 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 0 - 1 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 1 - 0 - 2 - 0 - 0 - 1 - 2 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 2 - 2 - 2 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 2 - 2 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 2 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 2 - 2 - 2 - 2 - 1 - 1 - 0 - 0 - 1 - 1 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 1 - 2 - 2 - 2 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 2 - 2 - 2 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 0 - 0 - 2 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 2 - 2 - 2 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 2 - 1 - 0 - 1 - 0 - 2 - 2 - 2 - 0 - 1 - 1 - 2 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 2 - 2 - 0 - 0 - 0 - 1 - 1 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 1 - 1 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 1 - 2 - 1 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 1 - 0 - 0 - 2 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 1 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 2 - 1 - 0 - 0 - 0 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 2 - 0 - 1 - 0 - 0 - 1 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 1 - 1 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 2 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 1 - 2 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 2 - 2 - 2 - 0 - 2 - 2 - 1 - 1 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 2 - 2 - 2 - 2 - 1 - 1 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 2 - 2 - 2 - 1 - 1 - 0 - 1 - 0 - 1 - 1 - 1 - 2 - 2 - 2 - 1 - 1 - 1 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 1 - 2 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 2 - 2 - 2 - 0 - 1 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 2 - 2 - 2 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 0 - 2 - 2 - 2 - 2 - 1 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 2 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 2 - 0 - 2 - 1 - 1 - 2 - 2 - 2 - 2 - 2 - 1 - 2 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 2 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 2 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 2 - 2 - 2 - 0 - 2 - 0 - 2 - 2 - 2 - 1 - 1 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 1 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 1 - 1 - 2 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 2 - 1 - 1 - 1 - 1 - 0 - 0 - 2 - 1 - 1 - 0 - 1 - 2 - 1 - 0 - 0 - 0 - 1 - 2 - 1 - 1 - 1 - 1 - 1 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 2 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 2 - 2 - 2 - 2 - 1 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 1 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 1 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 2 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 1 - 0 - 2 - 0 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 2 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 2 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 2 - 1 - 2 - 2 - 2 - 1 - 1 - 0 - 2 - 1 - 1 - 0 - 1 - 0 - 1 - 2 - 2 - 1 - 2 - 2 - 1 - 1 - 2 - 2 - 1 - 1 - 1 - 1 - 2 - 2 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 2 - 2 - 2 - 2 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 2 - 2 - 0 - 1 - 1 - 1 - 0 - 0 - 2 - 1 - 0 - 0 - 0 - 0 - 1 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 1 - 2 - 2 - 2 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 2 - 2 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 1 - 1 - 1 - 1 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 2 - 1 - 1 - 1 - 2 - 2 - 2 - 2 - 1 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 1 - 1 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 1 - 1 - 1 - 1 - 2 - 2 - 2 - 0 - 1 - 1 - 1 - 0 - 1 - 0 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 2 - 2 - 1 - 0 - 0 - 2 - 0 - 2 - 1 - 2 - 2 - 1 - 1 - 0 - 2 - 1 - 0 - 0 - 2 - 2 - 0 - 0 - 1 - 2 - 0 - 2 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 1 - 1 - 2 - 2 - 1 - 2 - 2 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 2 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 1 - 0 - 1 - 0 - 1 - 0 - 2 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 1 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 2 - 2 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 0 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 2 - 2 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 2 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 2 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 1 - 0 - 2 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 2 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 2 - 2 - 2 - 2 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 1 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 1 - 2 - 0 - 0 - 1 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 1 - 0 - 0 - 0 - 1 - 2 - 2 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 1 - 2 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 1 - 1 - 1 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 2 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 2 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 2 - 2 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 2 - 0 - 0 - 1 - 1 - 1 - 2 - 0 - 1 - 1 - 1 - 2 - 2 - 2 - 2 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 2 - 2 - 0 - 0 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 2 - 0 - 1 - 1 - 0 - 2 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 1 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 2 - 2 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 1 - 1 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 2 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 0 - 1 - 0 - 2 - 2 - 2 - 0 - 1 - 1 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 2 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 1 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 2 - 0 - 1 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 1 - 1 - 2 - 2 - 2 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 2 - 2 - 2 - 2 - 1 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 2 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 1 - 1 - 1 - 1 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 2 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 2 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 2 - 2 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 2 - 1 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 1 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 2 - 2 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 2 - 2 - 2 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 2 - 2 - 2 - 1 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 2 - 1 - 1 - 2 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 2 - 1 - 2 - 2 - 2 - 2 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 1 - 0 - 0 - 2 - 2 - 1 - 1 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 1 - 1 - 1 - 0 - 0 - 1 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 2 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 2 - 1 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 1 - 1 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 2 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 2 - 2 - 2 - 2 - 1 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 1 - 2 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 1 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 2 - 2 - 2 - 2 - 2 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 2 - 2 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 2 - 2 - 0 - 1 - 1 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 2 - 2 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 1 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 2 - 2 - 2 - 0 - 1 - 0 - 1 - 1 - 2 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 0 - 2 - 1 - 1 - 1 - 1 - 1 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 1 - 1 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 1 - 1 - 2 - 2 - 2 - 2 - 1 - 1 - 1 - 2 - 1 - 1 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 1 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 2 - 2 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 2 - 2 - 1 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 1 - 1 - 1 - 2 - 2 - 2 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 2 - 2 - 2 - 1 - 1 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 1 - 1 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 1 - 2 - 2 - 2 - 2 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 1 - 2 - 0 - 0 - 1 - 1 - 2 - 0 - 2 - 0 - 1 - 2 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 0 - 1 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 1 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 1 - 1 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 1 - 1 - 0 - 2 - 2 - 2 - 1 - 1 - 2 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 1 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 2 - 0 - 1 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 0 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 1 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 1 - 1 - 2 - 2 - 2 - 0 - 0 - 1 - 2 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 1 - 1 - 2 - 0 - 2 - 2 - 1 - 1 - 1 - 1 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 1 - 2 - 2 - 2 - 1 - 1 - 0 - 2 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 2 - 0 - 2 - 0 - 2 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 1 - 1 - 2 - 2 - 1 - 1 - 1 - 1 - 0 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 1 - 1 - 2 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 2 - 0 - 1 - 2 - 0 - 0 - 1 - 1 - 1 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 2 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 1 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 2 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 1 - 1 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 2 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 2 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 2 - 0 - 1 - 0 - 1 - 0 - 2 - 1 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 1 - 1 - 1 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 1 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 1 - 0 - 2 - 2 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 2 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 2 - 1 - 1 - 1 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 2 - 2 - 0 - 1 - 1 - 2 - 2 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 1 - 2 - 2 - 2 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 2 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 2 - 2 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 2 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 1 - 2 - 0 - 0 - 2 - 0 - 2 - 1 - 1 - 1 - 1 - 2 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 1 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 2 - 2 - 2 - 2 - 0 - 0 - 1 - 1 - 2 - 1 - 2 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 2 - 0 - 2 - 2 - 2 - 1 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 1 - 1 - 0 - 0 - 0 - 2 - 1 - 1 - 1 - 1 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 2 - 1 - 2 - 2 - 1 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 1 - 1 - 1 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 1 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 2 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 2 - 1 - 2 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 2 - 2 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 1 - 1 - 0 - 0 - 2 - 1 - 1 - 2 - 2 - 2 - 2 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 2 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 2 - 2 - 2 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 2 - 2 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 2 - 1 - 1 - 1 - 2 - 1 - 2 - 2 - 0 - 1 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 1 - 1 - 1 - 2 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 2 - 1 - 2 - 1 - 1 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 1 - 1 - 0 - 2 - 2 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 2 - 2 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 1 - 1 - 1 - 2 - 1 - 0 - 0 - 2 - 2 - 1 - 2 - 0 - 0 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 2 - 2 - 2 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 1 - 1 - 2 - 2 - 2 - 2 - 2 - 2 - 1 - 2 - 2 - 2 - 2 - 2 - 2 - 1 - 1 - 2 - 0 - 0 - 1 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 2 - 2 - 0 - 2 - 1 - 0 - 0 - 2 - 2 - 2 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 1 - 2 - 2 - 2 - 2 - 1 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 1 - 2 - 2 - 2 - 2 - 2 - 1 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 1 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 1 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 2 - 0 - 2 - 1 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 1 - 1 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 2 - 0 - 1 - 0 - 1 - 2 - 2 - 1 - 1 - 2 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 2 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 2 - 0 - 2 - 0 - 2 - 2 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 2 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 2 - 1 - 1 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 2 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 1 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 1 - 1 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 1 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 1 - 2 - 2 - 2 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 1 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 1 - 1 - 1 - 1 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 1 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 1 - 0 - 1 - 2 - 2 - 1 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 2 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 2 - 2 - 2 - 1 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 2 - 2 - 2 - 2 - 0 - 0 - 1 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 0 - 1 - 1 - 0 - 2 - 2 - 0 - 1 - 1 - 2 - 1 - 2 - 0 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 1 - 0 - 0 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 2 - 1 - 1 - 1 - 1 - 2 - 2 - 2 - 2 - 1 - 1 - 1 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 1 - 2 - 2 - 2 - 2 - 1 - 2 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 1 - 1 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 1 - 1 - 1 - 2 - 1 - 1 - 1 - 0 - 0 - 2 - 1 - 2 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 1 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 1 - 1 - 2 - 0 - 1 - 1 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 1 - 1 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 0 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 1 - 1 - 1 - 1 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 1 - 1 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 1 - 1 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 2 - 2 - 2 - 2 - 1 - 1 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 2 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 2 - 2 - 1 - 2 - 2 - 2 - 2 - 2 - 2 - 1 - 0 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 2 - 2 - 1 - 1 - 0 - 2 - 2 - 1 - 1 - 1 - 1 - 0 - 2 - 0 - 2 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 1 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 1 - 1 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 1 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 1 - 1 - 2 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 2 - 0 - 0 - 2 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 0 - 2 - 2 - 2 - 2 - 0 - 1 - 1 - 2 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 1 - 1 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 1 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 2 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 1 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 1 - 0 - 0 - 2 - 2 - 2 - 1 - 0 - 1 - 2 - 1 - 1 - 1 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 1 - 1 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 2 - 2 - 0 - 1 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 1 - 1 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 1 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 1 - 2 - 2 - 2 - 0 - 0 - 2 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 1 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 2 - 1 - 0 - 2 - 1 - 1 - 1 - 2 - 0 - 2 - 2 - 1 - 1 - 2 - 2 - 1 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 2 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 1 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 2 - 0 - 2 - 2 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 2 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 2 - 0 - 2 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 2 - 0 - 2 - 2 - 2 - 1 - 1 - 1 - 0 - 2 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 0 - 2 - 2 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 1 - 0 - 0 - 0 - 2 - 2 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 1 - 0 - 2 - 2 - 0 - 0 - 0 - 1 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 0 - 2 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 2 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 2 - 0 - 1 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 1 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 2 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 0 - 2 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 1 - 1 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 1 - 1 - 1 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 1 - 2 - 0 - 0 - 0 - 0 - 2 - 1 - 1 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 2 - 2 - 0 - 2 - 2 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 0 - 2 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 1 - 2 - 2 - 0 - 2 - 1 - 1 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 1 - 1 - 1 - 2 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 1 - 0 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 0 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 1 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 1 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 2 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 2 - 2 - 1 - 1 - 0 - 2 - 1 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 2 - 0 - 0 - 1 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 1 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 0 - 0 - 0 - 1 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 1 - 1 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 2 - 0 - 1 - 1 - 2 - 2 - 1 - 1 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 2 - 1 - 1 - 1 - 1 - 2 - 2 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 0 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 2 - 2 - 0 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 1 - 1 - 2 - 2 - 0 - 0 - 1 - 2 - 1 - 0 - 0 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 2 - 2 - 2 - 0 - 0 - 1 - 1 - 1 - 1 - 2 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 1 - 1 - 1 - 2 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 0 - 2 - 0 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 2 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 1 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 1 - 2 - 0 - 0 - 0 - 0 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 2 - 2 - 0 - 1 - 1 - 0 - 2 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 2 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 2 - 2 - 2 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 2 - 0 - 1 - 0 - 2 - 2 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 2 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 2 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 2 - 1 - 1 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 1 - 0 - 0 - 2 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 0 - 0 - 0 - 2 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 2 - 1 - 1 - 0 - 2 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 1 - 1 - 0 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 2 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 2 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 2 - 0 - 1 - 1 - 2 - 2 - 0 - 0 - 0 - 1 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 2 - 2 - 2 - 1 - 1 - 0 - 0 - 0 - 2 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 2 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 1 - 1 - 2 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 1 - 1 - 0 - 0 - 2 - 2 - 2 - 1 - 2 - 1 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 1 - 1 - 0 - 2 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 1 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 1 - 0 - 2 - 0 - 0 - 0 - 1 - 0 - 2 - 1 - 0 - 2 - 2 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 2 - 2 - 1 - 1 - 1 - 0 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 2 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 2 - 0 - 0 - 0 - 0 - 0 - 1 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 2 - 0 - 1 - 0 - 0 - 2 - 2 - 0 - 1 - 0 - 1 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 0 - 2 - 2 - 1 - 0 - 0 - 2 - 2 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 2 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 2 - 0 - 1 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 1 - 0 - 1 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 2 - 1 - 0 - 2 - 2 - 2 - 2 - 2 - 1 - 1 - 2 - 2 - 2 - 1 - 0 - 0 - 0 - 2 - 2 - 2 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 1 - 0 - 0 - 2 - 2 - 2 - 2 - 1 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 2 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 1 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 1 - 2 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 2 - 2 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 2 - 2 - 1 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 2 - 1 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 1 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 1 - 1 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 0 - 2 - 0 - 2 - 0 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 2 - 0 - 1 - 2 - 1 - 1 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 0 - 1 - 0 - 1 - 0 - 1 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 1 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 2 - 0 - 2 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 2 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 1 - 1 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 2 - 2 - 2 - 1 - 1 - 1 - 1 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 2 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 1 - 1 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 1 - 1 - 0 - 0 - 0 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 1 - 1 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 2 - 2 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 1 - 2 - 2 - 2 - 2 - 2 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 1 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 1 - 0 - 0 - 2 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 0 - 1 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 1 - 0 - 2 - 2 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 1 - 0 - 1 - 0 - 0 - 2 - 0 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 1 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 1 - 0 - 1 - 1 - 0 - 0 - 1 - 0 - 0 - 1 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 0 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 0 - 2 - 0 - 2 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - - - diff --git a/nibabel/gifti/tests/data/rh.aparc.annot.gii b/nibabel/gifti/tests/data/rh.aparc.annot.gii deleted file mode 100644 index b9c97687c2..0000000000 --- a/nibabel/gifti/tests/data/rh.aparc.annot.gii +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 - - diff --git a/nibabel/gifti/tests/data/rh.shape.curv.gii b/nibabel/gifti/tests/data/rh.shape.curv.gii deleted file mode 100644 index 1f17da3bdd..0000000000 --- a/nibabel/gifti/tests/data/rh.shape.curv.gii +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 - - diff --git a/nibabel/gifti/tests/data/task.func.gii b/nibabel/gifti/tests/data/task.func.gii deleted file mode 100644 index 0814a6dd33..0000000000 --- a/nibabel/gifti/tests/data/task.func.gii +++ /dev/null @@ -1,33 +0,0 @@ - - -UserNamemarkiewiczDateThu Sep 12 08:59:28 2019gifticlib-versiongifti library version 1.09, 28 June, 2010TimeStep0.000000NIFTI_XFORM_UNKNOWNNIFTI_XFORM_UNKNOWN 1.000000 0.000000 0.000000 0.000000 - 0.000000 1.000000 0.000000 0.000000 - 0.000000 0.000000 1.000000 0.000000 - 0.000000 0.000000 0.000000 1.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TimeStep0.000000NIFTI_XFORM_UNKNOWNNIFTI_XFORM_UNKNOWN 1.000000 0.000000 0.000000 0.000000 - 0.000000 1.000000 0.000000 0.000000 - 0.000000 0.000000 1.000000 0.000000 - 0.000000 0.000000 0.000000 1.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TimeStep0.000000NIFTI_XFORM_UNKNOWNNIFTI_XFORM_UNKNOWN 1.000000 0.000000 0.000000 0.000000 - 0.000000 1.000000 0.000000 0.000000 - 0.000000 0.000000 1.000000 0.000000 - 0.000000 0.000000 0.000000 1.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.000000NIFTI_XFORM_UNKNOWNNIFTI_XFORM_UNKNOWN 1.000000 0.000000 0.000000 0.000000 - 0.000000 1.000000 0.000000 0.000000 - 0.000000 0.000000 1.000000 0.000000 - 0.000000 0.000000 0.000000 1.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TimeStep0.000000NIFTI_XFORM_UNKNOWNNIFTI_XFORM_UNKNOWN 1.000000 0.000000 0.000000 0.000000 - 0.000000 1.000000 0.000000 0.000000 - 0.000000 0.000000 1.000000 0.000000 - 0.000000 0.000000 0.000000 1.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TimeStep0.000000NIFTI_XFORM_UNKNOWNNIFTI_XFORM_UNKNOWN 1.000000 0.000000 0.000000 0.000000 - 0.000000 1.000000 0.000000 0.000000 - 0.000000 0.000000 1.000000 0.000000 - 0.000000 0.000000 0.000000 1.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TimeStep0.000000NIFTI_XFORM_UNKNOWNNIFTI_XFORM_UNKNOWN 1.000000 0.000000 0.000000 0.000000 - 0.000000 1.000000 0.000000 0.000000 - 0.000000 0.000000 1.000000 0.000000 - 0.000000 0.000000 0.000000 1.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TimeStep0.000000NIFTI_XFORM_UNKNOWNNIFTI_XFORM_UNKNOWN 1.000000 0.000000 0.000000 0.000000 - 0.000000 1.000000 0.000000 0.000000 - 0.000000 0.000000 1.000000 0.000000 - 0.000000 0.000000 0.000000 1.000000eJx9lXlUlWUex7no5V64+/7elcsiEiCCORWuwfOxcMXURAwVscZKmjIV1ykTTXGrRnFtSFzTzF0csnHBLCU3MHfLDTE33JBFtrk5p87pj5nfOe95zrO9v+f9Pp/f9/3aoCDomIcvdBKO8gZxQ2Xku4BWqOfZ8fNF/AIv777eIGyj6kR1wwmRulxOwSwrfl8ZiM+Q0XmMm+QzJvJfdnMpxkLwaTtJJQqyJngYM9hOrNLJuFIlf1NaWbbMTZ9p/rz6RCLgXBgiUcONYRL+uxVPc7X+yINn4UwxNb1EZM2dIf66pVLMHqZ+Otdeo+H9OBPdrnoYNsnO4aM29hwzkyUPZJPQ8M6KKnGp0Y5Hr8GyWMcIs8TIWXo+zDdQf0viy9hABul0/FRn482KYNr3ULFnb4U4XRFOt41O1u6z433fTIdkHfeLrLzxsZXC7m4+Hmtl7SsSK3pb0HXRoO8gZ15XE9dSVfSusGOdpOBOph+RPdQY7ugZU6Ph7W+d+OX5MaWuBYvcNo4+G07ZYS/uUAf1Mjtr3nEzwGGnoLkF791zkJdrZ8clAx84A3AvsxK7ysjhPhq2TwznwEUn66+GoXa4uB4fSMZXVqozPDRUeRiYG0jMEAsrn1fRw7VRlBW72NImjFuqaDLaRbNsQzCRgz10nmN9quHvMUptT0prOiZqpoSzt9rEuPMW+ic+w6LnHKw0fi0y+jaKn3cqORtSKe7EF4qSuefEqV8viCm360XuFw/FI2OsEFf3ifCJQ8Uv2VvE6l3XReN7QTS9d1zcm9OSfadrRU6qluGTNcw55Y8UreLOrC/E7/lTyl4Tut0FT/vxHRxJv7XZwwysSbewYncg7TPuiTtnmsXEQivzozyoC81U9bLwcms3qbkuus+18VaMmaVtdJR/ZqBGZWHuRCttAxys62Vg8wItp3Zq2TtCousAM4WuILS9lPS9aKLTEH8KV/lzJuWRyMp6LDrt0fNpSx/7dVYO/SuIFvkm/jJVSUU3GTXhFlI/lTO7WEKWHoL8n6GcvKrhkFzJpVI3s3IMKHx7f77uJGW9k0ULjfT2aRixz8L5Xk6iOjohSmJ0msQPxioxtFDJya0GbmqVnBgsZ+th3/q/m5BdNXJ/o4V7ShXfHHdwrKuXH6Y7ULxgo9hP4ttaA/3yG8XkQb77OFwmQj9XsWK1huXJcrqMC0EqsjD4soe2xiimLQ5i8zYbP2a6ca+zY18SzGcLXdQg8cqzNuKHOXmtj53HbfT0+dHA7psWpGoziT7NihMsHBhoY44imOf6Ohj/wM7WZhOeUhPhpSGEJTh4rYtEbUMw+4+YiCo2kxlnpZ3TRfkMM6YENXEXjFT4m1g9y0irOjM9fLWoWq6hLjmI0TG+M7t0qHMtxBwxEJRp43qOnB17gri3V82qjgY2Nmup32EmR9iJjjOyq1LDzA0+xvVGtqscOKNb0N1fQ86KYHJvSKwfpiMrSon1gox7Ghstbhm4PFzN0NRKYXhWzzajkVE31dwdqSJtXSCutjqm+9m59mEg126amBtqJTLfSftYHf18377woovvfe/Z4KvHhZUOQmTV4uCUenFtUACtlgYRfVnJfIWBMc1eMnyan9jppnKUl8h1bmSTI3z5zKRO8PJvVWsKB4SjesFEwkIr7/5kJVlr543HNjpO9lK13sbp3hILzklcmSZxLtPKHKMB+343Sx85WZXtwXTGRt8SC/kttWCysKS7nk01jeI5UxAZHolpZW5yZrrRpkg8bLIzqtpO+H4r520u9gZaWP/QxIvDA8gyNIjqg4F8eVFFB4WCN0+oabjvICBHIvqhxMkqH38yBQnHNZQsC6LEp1f/bA3pt7WcG+9CPczBkMNOvNcdeOpcJP/gZc74MHo6fb5y1crjfDevnrUz2mojZYOD/Wm+50Yos31sqkutmHVOchOCeDjWxB6fb65J9mNNhERmopMRtWEsd7QisHM42u9CaY5xk3XLy/FPnVT193FVJBGZqGdAtI301QZGlgbgnCaj7zU9pYVa/tHDxWSvjl2xSh682SxmXpZx6toVsXbnXdGn6Irw7HbSa34Ig+56+HiEi0WVTlZHhVA03MPDuFbUWyLokB9BvyVh9C9uzflsM+/O95K+LhS/MaFQF0b6thASh9jRj2/Nk8shrHV4MOZ5mDEggo6/RCKLfCLamlrhlcL5fKD0J6/9X1H+UZ7Qfea70yn6pN/Hep47/YdHRg4J5q6vbo9tchF21sO4Qg/K3hGsbB9OuzZm4t620Hmxr3a9Br7PaxQzbpn5wMfxjyUhpGnCefWAnd5pHjZNi0BbkCdemBDMhAURpOduSfI/uESklrjok14gotqrkM/wZ1ZFvfjcZuDs1nKh9f0/vM+bubjRiib3oGhq2i6yl14Qt/tJ3MyOELcnzRZ1pjKRMENP9Yy14oK1XMi/qReDklaIAzEemuvbofDXk9VTz1Ehp3h9jvj6mxqRcsyPlSkKuivV9OwWwDTVI3Fy1CmxTXtGNBXVJhbH9EtsvndDJFVMF6mvl4lndtx58cqmNFHec5fY3XdUUo7isIid1CRe6tQoqqcGsu9RCy51kTH44EPh6X5SfPKJnMAlLcm49KuYMPaJmGOTce5CuVi+0p+F6w4J/cRVwpJcK7bHq6mZqsY7cKS4/HateGndfTHuwa8ipWtLXrYdFQVTrogpKT+L4sQKcbR7uQio0xM8PYCxb+nxby+ndq2VuUYNZy8qeFF+XzxRPBYUVYlXjsh5w1/J+0MVnF2mwVWm4ky9Cf0JGSPyHU+5MGgPinnbgsgcYv6/nJQHDBGdPM4/rRm9+b/90dbO4ss+LbB54kWPvMVJ1+blic4Xb/7BUUjlfhGXPlhs7eImUR2VVPvBtj/mCjafT/ytPXK7k/jpwaGk/gcKxWWf2/4HLgxx9A==TimeStep0.000000NIFTI_XFORM_UNKNOWNNIFTI_XFORM_UNKNOWN 1.000000 0.000000 0.000000 0.000000 - 0.000000 1.000000 0.000000 0.000000 - 0.000000 0.000000 1.000000 0.000000 - 0.000000 0.000000 0.000000 1.000000eJx9lntUFVQWxgF5c7lw3+97eVxAFEmlosl8cX5OaZqgKSJZPkA0JzWVRDFSgdQSRjQfjZNSCkoqiYbKpJgkGCokRGoaiIaSKCIFgkgwN1u1Vn/M7LXO2muffdbZe+1vn2+fxTecWTfWhy/adHwys1vYz5CTdt2K+wQDdjZJL7JgUHSLEN+HYoNDtQie4kzeN2piUbDCzoH6GiPvNioYZzGS9qyK4gA9X8e4ElJsJvmslv+EG9kc7UbZWxoaJ5k5VehA8YdaXoryxaHLk7QoHYGnXB7HOnfOzJmUdJGwolykVKWLD+a1iOyPJY9931/x5PxeBTd1PlRY9MxZpkOfo0IZ4kbFi1IKzzwQM213DU+U8HGKN+WhGkq+8CbvmIwtCj1+u1zpCfNmUK2Wd1b6kDTGgxNLG8Rz/gHc8zdQ+1DHgUNK3tvmRfhBNfmeGtZkGnE8qmaKTI81RMWE3Z7seN+JXYuUrEqQIMbpqD3vQlRyr0j6SsIOiYxBtlwWlxsY5mbHQBxZd19F4SM/jo+2MKmvniV6PReGmvBv1ZEV5UhqmoE+Ljpiv5QTP8qZM9VqPJrkTFR5Ur7Yyv5hJjxXW9l6wUhlvRu5OWoeXDFjfN5MW7g7Q6Vquos9aM/fJy6+amTsTX9Edn8alwfzs8nCgTAz62rVj2v4h5QkGCK22V0QpfOtdB1W4O6moqBfPwK36Lg24IDoeLlHzOnrRkZni0iMPiQWZl4S+wb+IM5N7RK53/4sjrX3Fy/cPCnsEqeJG5kHRVBpgzjU6UbQT5Vix8k+eMQ9FN9FenGhyZOZx/qw6VsPjm7OFn/E31MbLXLzdjy2p+80Rfym339GxqxcNSe/dMPw0T2RPaBXhDarSbhuZv3rarIeqehoN5K10sjsbVrq1ikZe9aLpWFy6kxqguPUnJcZmJckp+GyF8vDpVw2aDn7kgqHExLuTnVjd7QC508duPyeI3VPtYmhp9vFiUxvBizX0jZHg5vRg4iBCm7NdmHBaTvG9VOR/J0TX0u06HN9eeO2H805UgYVu9IhsXA8X07zeQ37HhqomWVgppuC7FN6Fm1XsVRroP5fRnpWaEj6XEPexl/EmFmuKMfLyat3ITHGifmhCppaFJi+l3OgSI1Dmjtv/2DAONeHUfMNVNriHnlSi1OejND6HrHZ9Z4Y/FG1sHeU8LLt7Xwy3pmTy3z49xUVFc1mGvT9iH7FnamlGsIeGBlsw7M11cL+owaSorSMStTQmm5An65jwXAZr4xXMD1BzRsNSsYnS7ltUDMpVcNSVwslvXoGrdBT8q6ShbFKot70Za1GT3SkDv+FFiKtSnoHqPg4QcN2q5Ep25W0/kNCUaucYQkKDpTLkd1VM2OwjLFFUia0uBG+VkJQiBcKk4raezISCzWM+8WJ4Em2/pjvSedOGfOavSjaqGTzq3oqVss5PcaLf45XE/OrnB2j9Yye60jvl7Y4wRbuH9YSf8QLfbQr0R/YEx+n4mSWnKh6Ca+aW8TkW97UdMrIsdm5Fg+kh91YkuDF4GQdF7zdOfGOgrbtGp64bmDjj1Lq0rT4upp5P19FZoScu6UG1hx9IByvdYldGS6wzIOOYa58FuRFWaIva3zMjGwyE1Lqy4JKE6+FBiLrVHLEhkn+zgDqkq0UrVfwXKGaTV5qrIt0VM3R8rd5PsRf0bBXY8vjkpa3qrVUL9cQc8WbnOlmEp8wMeiBiTcP2vCaomZ9pA2bgUoqRshYvLBHpNS5khWnIyfdxJGLJoILtDZu1bOtWsexfWpWbzdisNXwVxtGhdedaSvoFluHu+PSV8KavS707ZDQ9IoB7LT8EqmlLETB1Uxn6uylbJnnQZ8rEj7f5km6i60mjibufKXl/CU9Gyfrqcs0UZ3iy60R/pRVqJm9VUPgTybaLupIGK1hXayetHN6RJcfEyN9idqqZk+ggVNb3fEJV/D8Bg+K7vQKfbeWkhlGNj3th3KYFYd2f8486Y86x4R6vA+PZhvpb7DNjkk6Lrd6M9mk5d0OGap8Z5K67QnNkPHaWSkRN4ysy/ZiTKUrL4f2iv1lDpQsuy4yaprFc0uvi/g8I1M/8eX+p2b63jGw+G0Dby7wJSPAzJrcACQdVlJrA9lxzZ+g+L6s3qJkfo2JgFW+HI70o2aaHxkHfYjt1WJXFcCWRl/qZpi5P8lC09kgrtp49sinXeL0NSsztvozcIHmL1z7vyR/42bR2arGNVgW8cde3ImLf3LknUNmfq0zcnqRicG3zBQYzMTHBDLkswBefNaG61oViVY15dNlFE57JI67qBhTZsAlwY+EdCsfzNLzfKSZ0LhAXhy9RcwsNLPnfiAfDjkY0TJhm7CMNuG1OlvsseFyI8ueb9q7hXuwnIVht8T4of0p61Kw446ao3mlQj7/sJhcdVX0G6wD5yAxKG2tWJVdJW4rZEwct1sseaZBDG/rFjGWneJp22wqrg6jWeOJX5YUn1InVvWkihs2ri0s6xUxBhe+O+3OqlpHfnqpVVQNuSQmN14S/lHtIx/NnThyU2SjGHkgVcyqqhLZTzSNKFgVI56yHBEeQbMjNhR/LQ7e6RFWx26R8aQrc0v6cCzBHs3en4Uuv0oceuREw1QnTn3YKILaH4qHfe1RpDSImjAHfAPOCHnoLpFb81C0N3gw2bZit8eJzU2dIir6vuitbxR2LX0oKK8QSZofxZLgOpEzqlHsld8UJ6fKeD3XhfUfybhpdEY3UEPcUimVtjkwq7JF6Ke1izeSHwj3k86khLrR1OCCtE7K3wd4skqn5K7Kgf3Jv/+nTC+UinC5lJVXlP+3T9yvxopnd+v/cmbD7N/vKFCNFHtdHTkaP0BIjm+LMCVtEgEdN//sI2VeiSgiVvS06TnXGRzhkPrZn74LA2tH/qYzlw0REWllEelFhWLd7ZXivw1CkBc=TimeStep0.000000NIFTI_XFORM_UNKNOWNNIFTI_XFORM_UNKNOWN 1.000000 0.000000 0.000000 0.000000 - 0.000000 1.000000 0.000000 0.000000 - 0.000000 0.000000 1.000000 0.000000 - 0.000000 0.000000 0.000000 1.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 \ No newline at end of file diff --git a/nibabel/gifti/tests/test_1.py b/nibabel/gifti/tests/test_1.py deleted file mode 100644 index 0e19e59c43..0000000000 --- a/nibabel/gifti/tests/test_1.py +++ /dev/null @@ -1,18 +0,0 @@ -"""Testing loading of gifti file - -The file is ``test_1`` because we are testing a bug where, if we try to load a -file before instantiating some Gifti objects, loading fails with an -AttributeError (see: https://github.com/nipy/nibabel/issues/392). - -Thus, we have to run this test before the other gifti tests to catch the gifti -code unprepared. -""" - -from nibabel import load - -from .test_parse_gifti_fast import DATA_FILE3 - - -def test_load_gifti(): - # This expression should not raise an error - load(DATA_FILE3) diff --git a/nibabel/gifti/tests/test_gifti.py b/nibabel/gifti/tests/test_gifti.py deleted file mode 100644 index 416faf3c84..0000000000 --- a/nibabel/gifti/tests/test_gifti.py +++ /dev/null @@ -1,569 +0,0 @@ -"""Testing gifti objects""" - -import itertools -import sys -from io import BytesIO - -import numpy as np -import pytest -from numpy.testing import assert_array_equal - -from nibabel.tmpdirs import InTemporaryDirectory - -from ... import load -from ...fileholders import FileHolder -from ...nifti1 import data_type_codes -from ...testing import deprecated_to, expires, get_test_data -from .. import ( - GiftiCoordSystem, - GiftiDataArray, - GiftiImage, - GiftiLabel, - GiftiLabelTable, - GiftiMetaData, - GiftiNVPairs, -) -from .test_parse_gifti_fast import ( - DATA_FILE1, - DATA_FILE2, - DATA_FILE3, - DATA_FILE4, - DATA_FILE5, - DATA_FILE6, -) - -rng = np.random.default_rng() - - -def test_agg_data(): - surf_gii_img = load(get_test_data('gifti', 'ascii.gii')) - func_gii_img = load(get_test_data('gifti', 'task.func.gii')) - shape_gii_img = load(get_test_data('gifti', 'rh.shape.curv.gii')) - # add timeseries data with intent code ``none`` - - point_data = surf_gii_img.get_arrays_from_intent('pointset')[0].data - triangle_data = surf_gii_img.get_arrays_from_intent('triangle')[0].data - func_da = func_gii_img.get_arrays_from_intent('time series') - func_data = np.column_stack(tuple(da.data for da in func_da)) - shape_data = shape_gii_img.get_arrays_from_intent('shape')[0].data - - assert surf_gii_img.agg_data() == (point_data, triangle_data) - assert_array_equal(func_gii_img.agg_data(), func_data) - assert_array_equal(shape_gii_img.agg_data(), shape_data) - - assert_array_equal(surf_gii_img.agg_data('pointset'), point_data) - assert_array_equal(surf_gii_img.agg_data('triangle'), triangle_data) - assert_array_equal(func_gii_img.agg_data('time series'), func_data) - assert_array_equal(shape_gii_img.agg_data('shape'), shape_data) - - assert surf_gii_img.agg_data('time series') == () - assert func_gii_img.agg_data('triangle') == () - assert shape_gii_img.agg_data('pointset') == () - - assert surf_gii_img.agg_data(('pointset', 'triangle')) == (point_data, triangle_data) - assert surf_gii_img.agg_data(('triangle', 'pointset')) == (triangle_data, point_data) - - -def test_gifti_image(): - # Check that we're not modifying the default empty list in the default - # arguments. - gi = GiftiImage() - assert gi.darrays == [] - assert gi.meta == {} - assert gi.labeltable.labels == [] - arr = np.zeros((2, 3)) - gi.darrays.append(arr) - # Now check we didn't overwrite the default arg - gi = GiftiImage() - assert gi.darrays == [] - - # Test darrays / numDA - gi = GiftiImage() - assert gi.numDA == 0 - - # Test from numpy numeric array - data = rng.random(5, dtype=np.float32) - da = GiftiDataArray(data) - gi.add_gifti_data_array(da) - assert gi.numDA == 1 - assert_array_equal(gi.darrays[0].data, data) - - # Test removing - gi.remove_gifti_data_array(0) - assert gi.numDA == 0 - - # Remove from empty - gi = GiftiImage() - gi.remove_gifti_data_array_by_intent(0) - assert gi.numDA == 0 - - # Remove one - gi = GiftiImage() - da = GiftiDataArray(np.zeros((5,), np.float32), intent=0) - gi.add_gifti_data_array(da) - - gi.remove_gifti_data_array_by_intent(3) - assert gi.numDA == 1, "data array should exist on 'missed' remove" - - gi.remove_gifti_data_array_by_intent(da.intent) - assert gi.numDA == 0 - - -def test_gifti_image_bad_inputs(): - img = GiftiImage() - # Try to set a non-data-array - pytest.raises(TypeError, img.add_gifti_data_array, 'not-a-data-array') - - # Try to set to non-table - def assign_labeltable(val): - img.labeltable = val - - pytest.raises(TypeError, assign_labeltable, 'not-a-table') - - # Try to set to non-table - def assign_metadata(val): - img.meta = val - - pytest.raises(TypeError, assign_metadata, 'not-a-meta') - - -@pytest.mark.parametrize('label', data_type_codes.value_set('label')) -def test_image_typing(label): - dtype = data_type_codes.dtype[label] - if dtype == np.void: - return - arr = 127 * rng.random(20) - try: - cast = arr.astype(label) - except TypeError: - return - darr = GiftiDataArray(cast, datatype=label) - img = GiftiImage(darrays=[darr]) - - # Force-write always works - force_rt = img.from_bytes(img.to_bytes(mode='force')) - assert np.array_equal(cast, force_rt.darrays[0].data) - - # Compatibility mode does its best - if np.issubdtype(dtype, np.integer) or np.issubdtype(dtype, np.floating): - compat_rt = img.from_bytes(img.to_bytes(mode='compat')) - compat_darr = compat_rt.darrays[0].data - assert np.allclose(cast, compat_darr) - assert compat_darr.dtype in ('uint8', 'int32', 'float32') - else: - with pytest.raises(ValueError): - img.to_bytes(mode='compat') - - # Strict mode either works or fails - if label in ('uint8', 'int32', 'float32'): - strict_rt = img.from_bytes(img.to_bytes(mode='strict')) - assert np.array_equal(cast, strict_rt.darrays[0].data) - else: - with pytest.raises(ValueError): - img.to_bytes(mode='strict') - - -def test_dataarray_empty(): - # Test default initialization of DataArray - null_da = GiftiDataArray() - assert null_da.data is None - assert null_da.intent == 0 - assert null_da.datatype == 0 - assert null_da.encoding == 3 - assert null_da.endian == (2 if sys.byteorder == 'little' else 1) - assert null_da.coordsys.dataspace == 0 - assert null_da.coordsys.xformspace == 0 - assert_array_equal(null_da.coordsys.xform, np.eye(4)) - assert null_da.ind_ord == 1 - assert null_da.meta == {} - assert null_da.ext_fname == '' - assert null_da.ext_offset == 0 - - -def test_dataarray_init(): - # Test non-default dataarray initialization - gda = GiftiDataArray # shortcut - assert gda(None).data is None - arr = np.arange(12, dtype=np.float32).reshape((3, 4)) - assert_array_equal(gda(arr).data, arr) - # Intents - pytest.raises(KeyError, gda, intent=1) # Invalid code - pytest.raises(KeyError, gda, intent='not an intent') # Invalid string - assert gda(intent=2).intent == 2 - assert gda(intent='correlation').intent == 2 - assert gda(intent='NIFTI_INTENT_CORREL').intent == 2 - # Datatype - assert gda(datatype=2).datatype == 2 - assert gda(datatype='uint8').datatype == 2 - pytest.raises(KeyError, gda, datatype='not_datatype') - # Float32 datatype comes from array if datatype not set - assert gda(arr).datatype == 16 - # Can be overridden by init - assert gda(arr, datatype='uint8').datatype == 2 - # Encoding - assert gda(encoding=1).encoding == 1 - assert gda(encoding='ASCII').encoding == 1 - assert gda(encoding='GIFTI_ENCODING_ASCII').encoding == 1 - pytest.raises(KeyError, gda, encoding='not an encoding') - # Endian - assert gda(endian=1).endian == 1 - assert gda(endian='big').endian == 1 - assert gda(endian='GIFTI_ENDIAN_BIG').endian == 1 - pytest.raises(KeyError, gda, endian='not endian code') - # CoordSys - aff = np.diag([2, 3, 4, 1]) - cs = GiftiCoordSystem(1, 2, aff) - da = gda(coordsys=cs) - assert da.coordsys.dataspace == 1 - assert da.coordsys.xformspace == 2 - assert_array_equal(da.coordsys.xform, aff) - # Ordering - assert gda(ordering=2).ind_ord == 2 - assert gda(ordering='F').ind_ord == 2 - assert gda(ordering='ColumnMajorOrder').ind_ord == 2 - pytest.raises(KeyError, gda, ordering='not an ordering') - # metadata - meta_dict = dict(one=1, two=2) - assert gda(meta=GiftiMetaData(meta_dict)).meta == meta_dict - assert gda(meta=meta_dict).meta == meta_dict - assert gda(meta=None).meta == {} - # ext_fname and ext_offset - assert gda(ext_fname='foo').ext_fname == 'foo' - assert gda(ext_offset=12).ext_offset == 12 - - -@pytest.mark.parametrize('label', data_type_codes.value_set('label')) -def test_dataarray_typing(label): - dtype = data_type_codes.dtype[label] - code = data_type_codes.code[label] - arr = np.zeros((5,), dtype=dtype) - - # Default interface: accept standards-conformant arrays, reject else - if dtype in ('uint8', 'int32', 'float32'): - assert GiftiDataArray(arr).datatype == code - else: - with pytest.raises(ValueError): - GiftiDataArray(arr) - - # Explicit override - permit for now, may want to warn or eventually - # error - assert GiftiDataArray(arr, datatype=label).datatype == code - assert GiftiDataArray(arr, datatype=code).datatype == code - # Void is how we say we don't know how to do something, so it's not unique - if dtype != np.dtype('void'): - assert GiftiDataArray(arr, datatype=dtype).datatype == code - - # Side-load data array (as in parsing) - # We will probably always want this to load legacy images, but it's - # probably not ideal to make it easy to silently propagate nonconformant - # arrays - gda = GiftiDataArray() - gda.data = arr - gda.datatype = data_type_codes.code[label] - assert gda.data.dtype == dtype - assert gda.datatype == data_type_codes.code[label] - - -def test_labeltable(): - img = GiftiImage() - assert len(img.labeltable.labels) == 0 - - new_table = GiftiLabelTable() - new_table.labels += ['test', 'me'] - img.labeltable = new_table - assert len(img.labeltable.labels) == 2 - - -@expires('6.0.0') -def test_metadata(): - md = GiftiMetaData(key='value') - # Old initialization methods - with deprecated_to('6.0.0'): - nvpair = GiftiNVPairs('key', 'value') - with pytest.warns(FutureWarning) as w: - md2 = GiftiMetaData(nvpair=nvpair) - assert len(w) == 1 - with deprecated_to('6.0.0'): - md3 = GiftiMetaData.from_dict({'key': 'value'}) - assert md == md2 == md3 == {'key': 'value'} - # .data as a list of NVPairs is going away - with deprecated_to('6.0.0'): - assert md.data[0].name == 'key' - with deprecated_to('6.0.0'): - assert md.data[0].value == 'value' - - -@expires('6.0.0') -def test_metadata_list_interface(): - md = GiftiMetaData(key='value') - with deprecated_to('6.0.0'): - mdlist = md.data - assert len(mdlist) == 1 - assert mdlist[0].name == 'key' - assert mdlist[0].value == 'value' - - # Modify elements in-place - mdlist[0].name = 'foo' - assert mdlist[0].name == 'foo' - assert 'foo' in md - assert 'key' not in md - assert md['foo'] == 'value' - mdlist[0].value = 'bar' - assert mdlist[0].value == 'bar' - assert md['foo'] == 'bar' - - # Append new NVPair - with deprecated_to('6.0.0'): - nvpair = GiftiNVPairs('key', 'value') - mdlist.append(nvpair) - assert len(mdlist) == 2 - assert mdlist[1].name == 'key' - assert mdlist[1].value == 'value' - assert len(md) == 2 - assert md == {'foo': 'bar', 'key': 'value'} - - # Clearing empties both - mdlist.clear() - assert len(mdlist) == 0 - assert len(md) == 0 - - # Extension adds multiple keys - with deprecated_to('6.0'): - foobar = GiftiNVPairs('foo', 'bar') - mdlist.extend([nvpair, foobar]) - assert len(mdlist) == 2 - assert len(md) == 2 - assert md == {'key': 'value', 'foo': 'bar'} - - # Insertion updates list order, though we don't attempt to preserve it in the dict - with deprecated_to('6.0'): - lastone = GiftiNVPairs('last', 'one') - mdlist.insert(1, lastone) - assert len(mdlist) == 3 - assert len(md) == 3 - assert mdlist[1].name == 'last' - assert mdlist[1].value == 'one' - assert md == {'key': 'value', 'foo': 'bar', 'last': 'one'} - - # Popping returns a pair - mypair = mdlist.pop(0) - assert isinstance(mypair, GiftiNVPairs) - assert mypair.name == 'key' - assert mypair.value == 'value' - assert len(mdlist) == 2 - assert len(md) == 2 - assert 'key' not in md - assert md == {'foo': 'bar', 'last': 'one'} - # Modifying the pair now does not affect md - mypair.name = 'completelynew' - mypair.value = 'strings' - assert 'completelynew' not in md - assert md == {'foo': 'bar', 'last': 'one'} - # Check popping from the end (last one inserted before foobar) - mdlist.pop() - assert len(mdlist) == 1 - assert len(md) == 1 - assert md == {'last': 'one'} - - # And let's remove an old pair with a new object - with deprecated_to('6.0'): - lastoneagain = GiftiNVPairs('last', 'one') - mdlist.remove(lastoneagain) - assert len(mdlist) == 0 - assert len(md) == 0 - - -def test_gifti_label_rgba(): - rgba = rng.random(4) - kwargs = dict(zip(['red', 'green', 'blue', 'alpha'], rgba)) - - gl1 = GiftiLabel(**kwargs) - assert_array_equal(rgba, gl1.rgba) - - gl1.red = 2 * gl1.red - assert not np.allclose(rgba, gl1.rgba) # don't just store the list! - - gl2 = GiftiLabel() - gl2.rgba = rgba - assert_array_equal(rgba, gl2.rgba) - - gl2.blue = 2 * gl2.blue - assert not np.allclose(rgba, gl2.rgba) # don't just store the list! - - def assign_rgba(gl, val): - gl.rgba = val - - gl3 = GiftiLabel(**kwargs) - pytest.raises(ValueError, assign_rgba, gl3, rgba[:2]) - pytest.raises(ValueError, assign_rgba, gl3, rgba.tolist() + rgba.tolist()) - - # Test default value - gl4 = GiftiLabel() - assert len(gl4.rgba) == 4 - assert np.all([elem is None for elem in gl4.rgba]) - - -@pytest.mark.parametrize( - 'fname', [DATA_FILE1, DATA_FILE2, DATA_FILE3, DATA_FILE4, DATA_FILE5, DATA_FILE6] -) -def test_print_summary(fname, capsys): - gimg = load(fname) - gimg.print_summary() - captured = capsys.readouterr() - assert captured.out.startswith('----start----\n') - - -def test_gifti_coord(capsys): - from ..gifti import GiftiCoordSystem - - gcs = GiftiCoordSystem() - assert gcs.xform is not None - - # Smoke test - gcs.xform = None - gcs.print_summary() - captured = capsys.readouterr() - assert ( - captured.out - == """\ -Dataspace: NIFTI_XFORM_UNKNOWN -XFormSpace: NIFTI_XFORM_UNKNOWN -Affine Transformation Matrix: - None -""" - ) - gcs.to_xml() - - -def test_gifti_round_trip(): - # From section 14.4 in GIFTI Surface Data Format Version 1.0 - # (with some adaptations) - - test_data = b""" - - - - - - - - - - - - - - -1.000000 0.000000 0.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 -0.000000 0.000000 1.000000 0.000000 -0.000000 0.000000 0.000000 1.000000 - - - -10.5 0 0 -0 20.5 0 -0 0 30.5 -0 0 0 - - - - -0 1 2 -1 2 3 -0 1 3 -0 2 3 - - -""" - - exp_verts = np.zeros((4, 3)) - exp_verts[0, 0] = 10.5 - exp_verts[1, 1] = 20.5 - exp_verts[2, 2] = 30.5 - exp_faces = np.asarray([[0, 1, 2], [1, 2, 3], [0, 1, 3], [0, 2, 3]], dtype=np.int32) - - def _check_gifti(gio): - vertices = gio.get_arrays_from_intent('NIFTI_INTENT_POINTSET')[0].data - faces = gio.get_arrays_from_intent('NIFTI_INTENT_TRIANGLE')[0].data - assert_array_equal(vertices, exp_verts) - assert_array_equal(faces, exp_faces) - - bio = BytesIO() - fmap = dict(image=FileHolder(fileobj=bio)) - - bio.write(test_data) - bio.seek(0) - gio = GiftiImage.from_file_map(fmap) - _check_gifti(gio) - # Write and read again - bio.seek(0) - gio.to_file_map(fmap) - bio.seek(0) - gio2 = GiftiImage.from_file_map(fmap) - _check_gifti(gio2) - - -def test_data_array_round_trip(): - # Test valid XML generated from new in-memory array - # See: https://github.com/nipy/nibabel/issues/469 - verts = np.zeros((4, 3), np.float32) - verts[0, 0] = 10.5 - verts[1, 1] = 20.5 - verts[2, 2] = 30.5 - - vertices = GiftiDataArray(verts) - img = GiftiImage() - img.add_gifti_data_array(vertices) - bio = BytesIO() - fmap = dict(image=FileHolder(fileobj=bio)) - bio.write(img.to_xml()) - bio.seek(0) - gio = GiftiImage.from_file_map(fmap) - vertices = gio.darrays[0].data - assert_array_equal(vertices, verts) - - -def test_darray_dtype_coercion_failures(): - dtypes = (np.uint8, np.int32, np.int64, np.float32, np.float64) - encodings = ('ASCII', 'B64BIN', 'B64GZ') - for data_dtype, darray_dtype, encoding in itertools.product(dtypes, dtypes, encodings): - da = GiftiDataArray( - np.arange(10, dtype=data_dtype), - encoding=encoding, - intent='NIFTI_INTENT_NODE_INDEX', - datatype=darray_dtype, - ) - gii = GiftiImage(darrays=[da]) - gii_copy = GiftiImage.from_bytes(gii.to_bytes(mode='force')) - da_copy = gii_copy.darrays[0] - assert np.dtype(da_copy.data.dtype) == np.dtype(darray_dtype) - assert_array_equal(da_copy.data, da.data) - - -def test_gifti_file_close(recwarn): - gii = load(get_test_data('gifti', 'ascii.gii')) - with InTemporaryDirectory(): - gii.to_filename('test.gii') - assert not any(isinstance(r.message, ResourceWarning) for r in recwarn) diff --git a/nibabel/gifti/tests/test_parse_gifti_fast.py b/nibabel/gifti/tests/test_parse_gifti_fast.py deleted file mode 100644 index cfc8ce4ae2..0000000000 --- a/nibabel/gifti/tests/test_parse_gifti_fast.py +++ /dev/null @@ -1,470 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## - -import shutil -import sys -import warnings -from os.path import basename, dirname -from os.path import join as pjoin -from unittest import mock - -import numpy as np -import pytest -from numpy.testing import assert_array_almost_equal - -from ...loadsave import load, save -from ...nifti1 import xform_codes -from ...testing import clear_and_catch_warnings, suppress_warnings -from ...tmpdirs import InTemporaryDirectory -from .. import gifti as gi -from ..parse_gifti_fast import GiftiImageParser, GiftiParseError -from ..util import gifti_endian_codes - -IO_DATA_PATH = pjoin(dirname(__file__), 'data') - -DATA_FILE1 = pjoin(IO_DATA_PATH, 'ascii.gii') -DATA_FILE2 = pjoin(IO_DATA_PATH, 'gzipbase64.gii') -DATA_FILE3 = pjoin(IO_DATA_PATH, 'label.gii') -DATA_FILE4 = pjoin(IO_DATA_PATH, 'rh.shape.curv.gii') -# The base64bin file uses non-standard encoding and endian strings, and has -# line-breaks in the base64 encoded data, both of which will break other -# readers, such as Connectome workbench; for example: -# wb_command -gifti-convert ASCII base64bin.gii test.gii -DATA_FILE5 = pjoin(IO_DATA_PATH, 'base64bin.gii') -DATA_FILE6 = pjoin(IO_DATA_PATH, 'rh.aparc.annot.gii') -DATA_FILE7 = pjoin(IO_DATA_PATH, 'external.gii') -DATA_FILE8 = pjoin(IO_DATA_PATH, 'ascii_flat_data.gii') - -datafiles = [ - DATA_FILE1, - DATA_FILE2, - DATA_FILE3, - DATA_FILE4, - DATA_FILE5, - DATA_FILE6, - DATA_FILE7, - DATA_FILE8, -] -numDA = [2, 1, 1, 1, 2, 1, 2, 2] - -DATA_FILE1_darr1 = np.array( - [ - [-16.07201, -66.187515, 21.266994], - [-16.705893, -66.054337, 21.232786], - [-17.614349, -65.401642, 21.071466], - ] -) -DATA_FILE1_darr2 = np.array([[0, 1, 2]]) - -DATA_FILE2_darr1 = np.array( - [ - [0.43635699], - [0.270017], - [0.133239], - [0.35054299], - [0.26538199], - [0.32122701], - [0.23495001], - [0.26671499], - [0.306851], - [0.36302799], - ], - dtype=np.float32, -) - -DATA_FILE3_darr1 = np.array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0]) - -DATA_FILE4_darr1 = np.array( - [ - [-0.57811606], - [-0.53871965], - [-0.44602534], - [-0.56532663], - [-0.51392376], - [-0.43225467], - [-0.54646534], - [-0.48011276], - [-0.45624232], - [-0.31101292], - ], - dtype=np.float32, -) - -DATA_FILE5_darr1 = np.array( - [ - [155.17539978, 135.58103943, 98.30715179], - [140.33973694, 190.0491333, 73.24776459], - [157.3598938, 196.97969055, 83.65809631], - [171.46174622, 137.43661499, 78.4709549], - [148.54592896, 97.06752777, 65.96373749], - [123.45701599, 111.46841431, 66.3571167], - [135.30892944, 202.28720093, 36.38148499], - [178.28155518, 162.59469604, 37.75128937], - [178.11087036, 115.28820038, 57.17986679], - [142.81582642, 82.82115173, 31.02205276], - ], - dtype=np.float32, -) - -DATA_FILE5_darr2 = np.array( - [ - [6402, 17923, 25602], - [14085, 25602, 17923], - [25602, 14085, 4483], - [17923, 1602, 14085], - [4483, 25603, 25602], - [25604, 25602, 25603], - [25602, 25604, 6402], - [25603, 3525, 25604], - [1123, 17922, 12168], - [25604, 12168, 17922], - ], - dtype=np.int32, -) - -DATA_FILE6_darr1 = np.array([9182740, 9182740, 9182740], dtype=np.float32) - -DATA_FILE7_darr1 = np.array( - [ - [-1.0, -1.0, -1.0], - [-1.0, -1.0, 1.0], - [-1.0, 1.0, -1.0], - [-1.0, 1.0, 1.0], - [1.0, -1.0, -1.0], - [1.0, -1.0, 1.0], - [1.0, 1.0, -1.0], - [1.0, 1.0, 1.0], - ], - dtype=np.float32, -) - -DATA_FILE7_darr2 = np.array( - [ - [0, 6, 4], - [0, 2, 6], - [1, 5, 3], - [3, 5, 7], - [0, 4, 1], - [1, 4, 5], - [2, 7, 6], - [2, 3, 7], - [0, 1, 2], - [1, 3, 2], - [4, 7, 5], - [4, 6, 7], - ], - dtype=np.int32, -) - -DATA_FILE8_darr1 = np.copy(DATA_FILE5_darr1) - -DATA_FILE8_darr2 = np.copy(DATA_FILE5_darr2) - - -def assert_default_types(loaded): - default = loaded.__class__() - for attr in dir(default): - with suppress_warnings(): - defaulttype = type(getattr(default, attr)) - # Optional elements may have default of None - if defaulttype is type(None): - continue - with suppress_warnings(): - loadedtype = type(getattr(loaded, attr)) - assert loadedtype == defaulttype, ( - f'Type mismatch for attribute: {attr} ({loadedtype} != {defaulttype})' - ) - - -def test_default_types(): - # Test that variable types are same in loaded and default instances - for fname in datafiles: - img = load(fname) - # GiftiImage - assert_default_types(img) - # GiftiMetaData - assert_default_types(img.meta) - # GiftiNVPairs - Remove in NIB6 - with pytest.warns(DeprecationWarning): - for nvpair in img.meta.data: - assert_default_types(nvpair) - # GiftiLabelTable - assert_default_types(img.labeltable) - # GiftiLabel elements can be None or float; skip - # GiftiDataArray - for darray in img.darrays: - assert_default_types(darray) - # GiftiCoordSystem - assert_default_types(darray.coordsys) - # GiftiMetaData - assert_default_types(darray.meta) - # GiftiNVPairs - Remove in NIB6 - with pytest.warns(DeprecationWarning): - for nvpair in darray.meta.data: - assert_default_types(nvpair) - - -def test_read_ordering(): - # DATA_FILE1 has an expected darray[0].data shape of (3,3). However if we - # read another image first (DATA_FILE2) then the shape is wrong - # Read an image - img2 = load(DATA_FILE2) - assert img2.darrays[0].data.shape == (143479, 1) - # Read image for which we know output shape - img = load(DATA_FILE1) - assert img.darrays[0].data.shape == (3, 3) - - -def test_load_metadata(): - for i, dat in enumerate(datafiles): - img = load(dat) - img.meta - assert numDA[i] == img.numDA - assert img.version == '1.0' - - -def test_load_dataarray1(): - img1 = load(DATA_FILE1) - # Round trip - with InTemporaryDirectory(): - save(img1, 'test.gii') - bimg = load('test.gii') - for img in (img1, bimg): - assert_array_almost_equal(img.darrays[0].data, DATA_FILE1_darr1) - assert_array_almost_equal(img.darrays[1].data, DATA_FILE1_darr2) - me = img.darrays[0].meta - assert 'AnatomicalStructurePrimary' in me - assert 'AnatomicalStructureSecondary' in me - assert me['AnatomicalStructurePrimary'] == 'CortexLeft' - assert_array_almost_equal(img.darrays[0].coordsys.xform, np.eye(4, 4)) - assert xform_codes.niistring[img.darrays[0].coordsys.dataspace] == 'NIFTI_XFORM_TALAIRACH' - assert xform_codes.niistring[img.darrays[0].coordsys.xformspace] == 'NIFTI_XFORM_TALAIRACH' - - -def test_load_dataarray2(): - img2 = load(DATA_FILE2) - # Round trip - with InTemporaryDirectory(): - save(img2, 'test.gii') - bimg = load('test.gii') - for img in (img2, bimg): - assert_array_almost_equal(img.darrays[0].data[:10], DATA_FILE2_darr1) - - -def test_load_dataarray3(): - img3 = load(DATA_FILE3) - with InTemporaryDirectory(): - save(img3, 'test.gii') - bimg = load('test.gii') - for img in (img3, bimg): - assert_array_almost_equal(img.darrays[0].data[30:50], DATA_FILE3_darr1) - - -def test_load_dataarray4(): - img4 = load(DATA_FILE4) - # Round trip - with InTemporaryDirectory(): - save(img4, 'test.gii') - bimg = load('test.gii') - for img in (img4, bimg): - assert_array_almost_equal(img.darrays[0].data[:10], DATA_FILE4_darr1) - - -def test_dataarray5(): - img5 = load(DATA_FILE5) - for da in img5.darrays: - assert gifti_endian_codes.byteorder[da.endian] == 'little' - assert_array_almost_equal(img5.darrays[0].data, DATA_FILE5_darr1) - assert_array_almost_equal(img5.darrays[1].data, DATA_FILE5_darr2) - # Round trip tested below - - -def test_base64_written(): - with InTemporaryDirectory(): - with open(DATA_FILE5, 'rb') as fobj: - contents = fobj.read() - # Confirm the bad tags are still in the file - assert b'GIFTI_ENCODING_B64BIN' in contents - assert b'GIFTI_ENDIAN_LITTLE' in contents - # The good ones are missing - assert b'Base64Binary' not in contents - assert b'LittleEndian' not in contents - # Round trip - img5 = load(DATA_FILE5) - save(img5, 'fixed.gii') - with open('fixed.gii', 'rb') as fobj: - contents = fobj.read() - # The bad codes have gone, replaced by the good ones - assert b'GIFTI_ENCODING_B64BIN' not in contents - assert b'GIFTI_ENDIAN_LITTLE' not in contents - assert b'Base64Binary' in contents - if sys.byteorder == 'little': - assert b'LittleEndian' in contents - else: - assert b'BigEndian' in contents - img5_fixed = load('fixed.gii') - darrays = img5_fixed.darrays - assert_array_almost_equal(darrays[0].data, DATA_FILE5_darr1) - assert_array_almost_equal(darrays[1].data, DATA_FILE5_darr2) - - -def test_readwritedata(): - img = load(DATA_FILE2) - with InTemporaryDirectory(): - save(img, 'test.gii') - img2 = load('test.gii') - assert img.numDA == img2.numDA - assert_array_almost_equal(img.darrays[0].data, img2.darrays[0].data) - - -def test_modify_darray(): - for fname in (DATA_FILE1, DATA_FILE2, DATA_FILE5): - img = load(fname) - darray = img.darrays[0] - darray.data[:] = 0 - assert np.array_equiv(darray.data, 0) - - -def test_write_newmetadata(): - img = gi.GiftiImage() - newmeta = gi.GiftiMetaData(mykey='val1') - img.meta = newmeta - myme = img.meta - assert 'mykey' in myme - newmeta = gi.GiftiMetaData({'mykey1': 'val2'}) - img.meta = newmeta - myme = img.meta - assert 'mykey1' in myme - assert 'mykey' not in myme - - -def test_load_getbyintent(): - img = load(DATA_FILE1) - da = img.get_arrays_from_intent('NIFTI_INTENT_POINTSET') - assert len(da) == 1 - - da = img.get_arrays_from_intent('NIFTI_INTENT_TRIANGLE') - assert len(da) == 1 - - da = img.get_arrays_from_intent('NIFTI_INTENT_CORREL') - assert len(da) == 0 - assert da == [] - - -def test_load_labeltable(): - img6 = load(DATA_FILE6) - # Round trip - with InTemporaryDirectory(): - save(img6, 'test.gii') - bimg = load('test.gii') - for img in (img6, bimg): - assert_array_almost_equal(img.darrays[0].data[:3], DATA_FILE6_darr1) - assert len(img.labeltable.labels) == 36 - labeldict = img.labeltable.get_labels_as_dict() - assert 660700 in labeldict - assert labeldict[660700] == 'entorhinal' - assert img.labeltable.labels[1].key == 2647065 - assert img.labeltable.labels[1].red == 0.0980392 - assert img.labeltable.labels[1].green == 0.392157 - assert img.labeltable.labels[1].blue == 0.156863 - assert img.labeltable.labels[1].alpha == 1 - - -def test_parse_dataarrays(): - fn = 'bad_daa.gii' - img = gi.GiftiImage() - - with InTemporaryDirectory(): - save(img, fn) - with open(fn) as fp: - txt = fp.read() - # Make a bad gifti. - txt = txt.replace('NumberOfDataArrays="0"', 'NumberOfDataArrays ="1"') - with open(fn, 'w') as fp: - fp.write(txt) - - with clear_and_catch_warnings() as w: - warnings.filterwarnings('once', category=UserWarning) - load(fn) - assert len(w) == 1 - assert img.numDA == 0 - - -def test_parse_with_buffersize(): - for buff_sz in [None, 1, 2**12]: - img2 = load(DATA_FILE2, buffer_size=buff_sz) - assert img2.darrays[0].data.shape == (143479, 1) - - -def test_dataarray7(): - img7 = load(DATA_FILE7) - assert_array_almost_equal(img7.darrays[0].data, DATA_FILE7_darr1) - assert_array_almost_equal(img7.darrays[1].data, DATA_FILE7_darr2) - - -def test_parse_with_memmmap(): - img1 = load(DATA_FILE7) - img2 = load(DATA_FILE7, mmap=True) - img3 = load(DATA_FILE7, mmap=False) - assert len(img1.darrays) == len(img2.darrays) == 2 - assert isinstance(img1.darrays[0].data, np.memmap) - assert isinstance(img1.darrays[1].data, np.memmap) - assert isinstance(img2.darrays[0].data, np.memmap) - assert isinstance(img2.darrays[1].data, np.memmap) - assert not isinstance(img3.darrays[0].data, np.memmap) - assert not isinstance(img3.darrays[1].data, np.memmap) - assert_array_almost_equal(img1.darrays[0].data, DATA_FILE7_darr1) - assert_array_almost_equal(img1.darrays[1].data, DATA_FILE7_darr2) - assert_array_almost_equal(img2.darrays[0].data, DATA_FILE7_darr1) - assert_array_almost_equal(img2.darrays[1].data, DATA_FILE7_darr2) - assert_array_almost_equal(img3.darrays[0].data, DATA_FILE7_darr1) - assert_array_almost_equal(img3.darrays[1].data, DATA_FILE7_darr2) - - -def test_parse_with_memmap_fallback(): - img1 = load(DATA_FILE7, mmap=True) - with mock.patch('numpy.memmap', side_effect=ValueError): - img2 = load(DATA_FILE7, mmap=True) - assert isinstance(img1.darrays[0].data, np.memmap) - assert isinstance(img1.darrays[1].data, np.memmap) - assert not isinstance(img2.darrays[0].data, np.memmap) - assert not isinstance(img2.darrays[1].data, np.memmap) - assert_array_almost_equal(img1.darrays[0].data, DATA_FILE7_darr1) - assert_array_almost_equal(img1.darrays[1].data, DATA_FILE7_darr2) - assert_array_almost_equal(img2.darrays[0].data, DATA_FILE7_darr1) - assert_array_almost_equal(img2.darrays[1].data, DATA_FILE7_darr2) - - -def test_external_file_failure_cases(): - # external file cannot be found - with InTemporaryDirectory() as tmpdir: - shutil.copy(DATA_FILE7, '.') - filename = pjoin(tmpdir, basename(DATA_FILE7)) - with pytest.raises(GiftiParseError): - load(filename) - # load from in-memory xml string (parser requires it as bytes) - with open(DATA_FILE7, 'rb') as f: - xmldata = f.read() - parser = GiftiImageParser() - with pytest.raises(GiftiParseError): - parser.parse(xmldata) - - -def test_load_compressed(): - for ext in ('', '.gz', '.bz2'): - fn = pjoin(IO_DATA_PATH, 'external.gii' + ext) - img7 = load(fn) - assert_array_almost_equal(img7.darrays[0].data, DATA_FILE7_darr1) - assert_array_almost_equal(img7.darrays[1].data, DATA_FILE7_darr2) - - -def test_load_flat_ascii_data(): - img = load(DATA_FILE8) - assert_array_almost_equal(img.darrays[0].data, DATA_FILE8_darr1) - assert_array_almost_equal(img.darrays[1].data, DATA_FILE8_darr2) diff --git a/nibabel/gifti/util.py b/nibabel/gifti/util.py deleted file mode 100644 index 791f133022..0000000000 --- a/nibabel/gifti/util.py +++ /dev/null @@ -1,41 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## - -from ..volumeutils import Recoder - -# Translate dtype.kind char codes to XML text output strings -KIND2FMT = {'i': '%d', 'u': '%d', 'f': '%10.6f', 'c': '%10.6f', 'V': ''} - -array_index_order_codes = Recoder( - ( - (1, 'RowMajorOrder', 'C'), - (2, 'ColumnMajorOrder', 'F'), - ), - fields=('code', 'label', 'npcode'), -) - -gifti_encoding_codes = Recoder( - ( - (0, 'undef', 'GIFTI_ENCODING_UNDEF', 'undef'), - (1, 'ASCII', 'GIFTI_ENCODING_ASCII', 'ASCII'), - (2, 'B64BIN', 'GIFTI_ENCODING_B64BIN', 'Base64Binary'), - (3, 'B64GZ', 'GIFTI_ENCODING_B64GZ', 'GZipBase64Binary'), - (4, 'External', 'GIFTI_ENCODING_EXTBIN', 'ExternalFileBinary'), - ), - fields=('code', 'label', 'giistring', 'specs'), -) - -gifti_endian_codes = Recoder( - ( - (0, 'GIFTI_ENDIAN_UNDEF', 'Undef', 'undef'), - (1, 'GIFTI_ENDIAN_BIG', 'BigEndian', 'big'), - (2, 'GIFTI_ENDIAN_LITTLE', 'LittleEndian', 'little'), - ), - fields=('code', 'giistring', 'specs', 'byteorder'), -) diff --git a/nibabel/imageclasses.py b/nibabel/imageclasses.py deleted file mode 100644 index 66f984e268..0000000000 --- a/nibabel/imageclasses.py +++ /dev/null @@ -1,83 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Define supported image classes and names""" - -from __future__ import annotations - -from typing import TYPE_CHECKING - -from .analyze import AnalyzeImage -from .brikhead import AFNIImage -from .cifti2 import Cifti2Image -from .freesurfer import MGHImage -from .gifti import GiftiImage -from .minc1 import Minc1Image -from .minc2 import Minc2Image -from .nifti1 import Nifti1Image, Nifti1Pair -from .nifti2 import Nifti2Image, Nifti2Pair -from .parrec import PARRECImage -from .spm2analyze import Spm2AnalyzeImage -from .spm99analyze import Spm99AnalyzeImage - -if TYPE_CHECKING: - from .dataobj_images import DataobjImage - from .filebasedimages import FileBasedImage - -# Ordered by the load/save priority. -all_image_classes: list[type[FileBasedImage]] = [ - Nifti1Pair, - Nifti1Image, - Nifti2Pair, - Cifti2Image, - Nifti2Image, # Cifti2 before Nifti2 - Spm2AnalyzeImage, - Spm99AnalyzeImage, - AnalyzeImage, - Minc1Image, - Minc2Image, - MGHImage, - PARRECImage, - GiftiImage, - AFNIImage, -] - -# Image classes known to require spatial axes to be first in index ordering. -# When adding an image class, consider whether the new class should be listed -# here. -KNOWN_SPATIAL_FIRST: tuple[type[FileBasedImage], ...] = ( - Nifti1Pair, - Nifti1Image, - Nifti2Pair, - Nifti2Image, - Spm2AnalyzeImage, - Spm99AnalyzeImage, - AnalyzeImage, - MGHImage, - PARRECImage, - AFNIImage, -) - - -def spatial_axes_first(img: DataobjImage) -> bool: - """True if spatial image axes for `img` always precede other axes - - Parameters - ---------- - img : object - Image object implementing at least ``shape`` attribute. - - Returns - ------- - spatial_axes_first : bool - True if image only has spatial axes (number of axes < 4) or image type - known to have spatial axes preceding other axes. - """ - if len(img.shape) < 4: - return True - return type(img) in KNOWN_SPATIAL_FIRST diff --git a/nibabel/imageglobals.py b/nibabel/imageglobals.py deleted file mode 100644 index 81a1742809..0000000000 --- a/nibabel/imageglobals.py +++ /dev/null @@ -1,61 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Defaults for images and headers - -error_level is the problem level (see BatteryRunners) at which an error will be -raised, by the batteryrunners ``log_raise`` method. Thus a level of 0 will -result in an error for any problem at all, and a level of 50 will mean no errors -will be raised (unless someone's put some strange problem_level > 50 code in). - -``logger`` is the default logger (python log instance) - -To set the log level (log message appears for problem of level >= log level), -use e.g. ``logger.level = 40``. - -As for most loggers, if ``logger.level == 0`` then a default log level is used - -use ``logger.getEffectiveLevel()`` to see what that default is. - -Use ``logger.level = 1`` to see all messages. -""" - -import logging - -error_level = 40 -logger = logging.getLogger('nibabel.global') -logger.addHandler(logging.StreamHandler()) - - -class ErrorLevel: - """Context manager to set log error level""" - - def __init__(self, level): - self.level = level - - def __enter__(self): - global error_level - self._original_level = error_level - error_level = self.level - - def __exit__(self, exc, value, tb): - global error_level - error_level = self._original_level - return False - - -class LoggingOutputSuppressor: - """Context manager to prevent global logger from printing""" - - def __enter__(self): - self.orig_handlers = logger.handlers - for handler in self.orig_handlers: - logger.removeHandler(handler) - - def __exit__(self, exc, value, tb): - for handler in self.orig_handlers: - logger.addHandler(handler) diff --git a/nibabel/imagestats.py b/nibabel/imagestats.py deleted file mode 100644 index 36fbddee0e..0000000000 --- a/nibabel/imagestats.py +++ /dev/null @@ -1,65 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Functions for computing image statistics""" - -import numpy as np - -from nibabel.imageclasses import spatial_axes_first - - -def count_nonzero_voxels(img): - """ - Count number of non-zero voxels - - Parameters - ---------- - img : ``SpatialImage`` - All voxels of the mask should be of value 1, background should have value 0. - - Returns - ------- - count : int - Number of non-zero voxels - - """ - return np.count_nonzero(img.dataobj) - - -def mask_volume(img): - """Compute volume of mask image. - - Equivalent to "fslstats /path/file.nii -V" - - Parameters - ---------- - img : ``SpatialImage`` - All voxels of the mask should be of value 1, background should have value 0. - - - Returns - ------- - volume : float - Volume of mask expressed in mm3. - - Examples - -------- - >>> import numpy as np - >>> import nibabel as nb - >>> mask_data = np.zeros((20, 20, 20), dtype='u1') - >>> mask_data[5:15, 5:15, 5:15] = 1 - >>> nb.imagestats.mask_volume(nb.Nifti1Image(mask_data, np.eye(4))) - 1000.0 - """ - if not spatial_axes_first(img): - raise ValueError('Cannot calculate voxel volume for image with unknown spatial axes') - voxel_volume_mm3 = np.prod(img.header.get_zooms()[:3]) - mask_volume_vx = count_nonzero_voxels(img) - mask_volume_mm3 = mask_volume_vx * voxel_volume_mm3 - - return mask_volume_mm3 diff --git a/nibabel/info.py b/nibabel/info.py deleted file mode 100644 index 87727cab13..0000000000 --- a/nibabel/info.py +++ /dev/null @@ -1,111 +0,0 @@ -"""Define static nibabel metadata for nibabel - -The long description parameter is used in the nibabel top-level docstring, -and in building the docs. -We exec this file in several places, so it cannot import nibabel or use -relative imports. -""" - -# Note: this long_description is the canonical place to edit this text. -# It also appears in README.rst, but it should get there by running -# ``tools/refresh_readme.py`` which pulls in this version. -# We also include this text in the docs by ``..include::`` in -# ``docs/source/index.rst``. -long_description = """ -Read and write access to common neuroimaging file formats, including: -ANALYZE_ (plain, SPM99, SPM2 and later), GIFTI_, NIfTI1_, NIfTI2_, `CIFTI-2`_, -MINC1_, MINC2_, `AFNI BRIK/HEAD`_, ECAT_ and Philips PAR/REC. -In addition, NiBabel also supports FreeSurfer_'s MGH_, geometry, annotation and -morphometry files, and provides some limited support for DICOM_. - -NiBabel's API gives full or selective access to header information (metadata), -and image data is made available via NumPy arrays. For more information, see -NiBabel's `documentation site`_ and `API reference`_. - -.. _API reference: https://nipy.org/nibabel/api.html -.. _AFNI BRIK/HEAD: https://afni.nimh.nih.gov/pub/dist/src/README.attributes -.. _ANALYZE: http://www.grahamwideman.com/gw/brain/analyze/formatdoc.htm -.. _CIFTI-2: https://www.nitrc.org/projects/cifti/ -.. _DICOM: http://medical.nema.org/ -.. _documentation site: http://nipy.org/nibabel -.. _ECAT: http://xmedcon.sourceforge.net/Docs/Ecat -.. _Freesurfer: https://surfer.nmr.mgh.harvard.edu -.. _GIFTI: https://www.nitrc.org/projects/gifti -.. _MGH: https://surfer.nmr.mgh.harvard.edu/fswiki/FsTutorial/MghFormat -.. _MINC1: - https://en.wikibooks.org/wiki/MINC/Reference/MINC1_File_Format_Reference -.. _MINC2: - https://en.wikibooks.org/wiki/MINC/Reference/MINC2.0_File_Format_Reference -.. _NIfTI1: http://nifti.nimh.nih.gov/nifti-1/ -.. _NIfTI2: http://nifti.nimh.nih.gov/nifti-2/ - -Installation -============ - -To install NiBabel's `current release`_ with ``pip``, run:: - - pip install nibabel - -To install the latest development version, run:: - - pip install git+https://github.com/nipy/nibabel - -When working on NiBabel itself, it may be useful to install in "editable" mode:: - - git clone https://github.com/nipy/nibabel.git - pip install -e ./nibabel - -For more information on previous releases, see the `release archive`_ or -`development changelog`_. - -.. _current release: https://pypi.python.org/pypi/NiBabel -.. _release archive: https://github.com/nipy/NiBabel/releases -.. _development changelog: https://nipy.org/nibabel/changelog.html - -Testing -======= - -During development, we recommend using tox_ to run nibabel tests:: - - git clone https://github.com/nipy/nibabel.git - cd nibabel - tox - -To test an installed version of nibabel, install the test dependencies -and run pytest_:: - - pip install nibabel[test] - pytest --pyargs nibabel - -For more information, consult the `developer guidelines`_. - -.. _tox: https://tox.wiki -.. _pytest: https://docs.pytest.org -.. _developer guidelines: https://nipy.org/nibabel/devel/devguide.html - -Mailing List -============ - -Please send any questions or suggestions to the `neuroimaging mailing list -`_. - -License -======= - -NiBabel is licensed under the terms of the `MIT license -`__. -Some code included with NiBabel is licensed under the `BSD license`_. -For more information, please see the COPYING_ file. - -.. _BSD license: https://opensource.org/licenses/BSD-3-Clause -.. _COPYING: https://github.com/nipy/nibabel/blob/master/COPYING - -Citation -======== - -NiBabel releases have a Zenodo_ `Digital Object Identifier`_ (DOI) badge at -the top of the release notes. Click on the badge for more information. - -.. _Digital Object Identifier: https://en.wikipedia.org/wiki/Digital_object_identifier -.. _zenodo: https://zenodo.org -""" diff --git a/nibabel/loadsave.py b/nibabel/loadsave.py deleted file mode 100644 index e398092abd..0000000000 --- a/nibabel/loadsave.py +++ /dev/null @@ -1,295 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# module imports -"""Utilities to load and save image objects""" - -from __future__ import annotations - -import os - -import numpy as np - -from .arrayproxy import is_proxy -from .deprecated import deprecate_with_version -from .filebasedimages import ImageFileError -from .filename_parser import _stringify_path, splitext_addext -from .imageclasses import all_image_classes -from .openers import ImageOpener - -_compressed_suffixes = ('.gz', '.bz2', '.zst') - - -TYPE_CHECKING = False -if TYPE_CHECKING: - from typing import TypedDict - - from ._typing import ParamSpec - from .filebasedimages import FileBasedImage - from .filename_parser import FileSpec - - P = ParamSpec('P') - - class Signature(TypedDict): - signature: bytes - format_name: str - - -def _signature_matches_extension(filename: FileSpec) -> tuple[bool, str]: - """Check if signature aka magic number matches filename extension. - - Parameters - ---------- - filename : str or os.PathLike - Path to the file to check - - Returns - ------- - matches : bool - - `True` if the filename extension is not recognized (not .gz nor .bz2) - - `True` if the magic number was successfully read and corresponds to - the format indicated by the extension. - - `False` otherwise. - error_message : str - An error message if opening the file failed or a mismatch is detected; - the empty string otherwise. - - """ - signatures: dict[str, Signature] = { - '.gz': {'signature': b'\x1f\x8b', 'format_name': 'gzip'}, - '.bz2': {'signature': b'BZh', 'format_name': 'bzip2'}, - '.zst': {'signature': b'\x28\xb5\x2f\xfd', 'format_name': 'ztsd'}, - } - filename = _stringify_path(filename) - *_, ext = splitext_addext(filename) - ext = ext.lower() - if ext not in signatures: - return True, '' - expected_signature = signatures[ext]['signature'] - try: - with open(filename, 'rb') as fh: - sniff = fh.read(len(expected_signature)) - except OSError: - return False, f'Could not read file: {filename}' - if sniff.startswith(expected_signature): - return True, '' - format_name = signatures[ext]['format_name'] - return False, f'File {filename} is not a {format_name} file' - - -def load(filename: FileSpec, **kwargs) -> FileBasedImage: - r"""Load file given filename, guessing at file type - - Parameters - ---------- - filename : str or os.PathLike - specification of file to load - \*\*kwargs : keyword arguments - Keyword arguments to format-specific load - - Returns - ------- - img : ``SpatialImage`` - Image of guessed type - """ - filename = _stringify_path(filename) - - # Check file exists and is not empty - try: - stat_result = os.stat(filename) - except OSError: - raise FileNotFoundError(f"No such file or no access: '{filename}'") - if stat_result.st_size <= 0: - raise ImageFileError(f"Empty file: '{filename}'") - - sniff = None - for image_klass in all_image_classes: - is_valid, sniff = image_klass.path_maybe_image(filename, sniff) - if is_valid: - img = image_klass.from_filename(filename, **kwargs) - return img - - matches, msg = _signature_matches_extension(filename) - if not matches: - raise ImageFileError(msg) - - raise ImageFileError(f'Cannot work out file type of "{filename}"') - - -@deprecate_with_version('guessed_image_type deprecated.', '3.2', '5.0') -def guessed_image_type(filename): - """Guess image type from file `filename` - - Parameters - ---------- - filename : str - File name containing an image - - Returns - ------- - image_class : class - Class corresponding to guessed image type - """ - sniff = None - for image_klass in all_image_classes: - is_valid, sniff = image_klass.path_maybe_image(filename, sniff) - if is_valid: - return image_klass - - raise ImageFileError(f'Cannot work out file type of "{filename}"') - - -def save(img: FileBasedImage, filename: FileSpec, **kwargs) -> None: - r"""Save an image to file adapting format to `filename` - - Parameters - ---------- - img : ``SpatialImage`` - image to save - filename : str or os.PathLike - filename (often implying filenames) to which to save `img`. - \*\*kwargs : keyword arguments - Keyword arguments to format-specific save - - Returns - ------- - None - """ - filename = _stringify_path(filename) - - # Save the type as expected - try: - img.to_filename(filename, **kwargs) - except ImageFileError: - pass - else: - return - - # Be nice to users by making common implicit conversions - froot, ext, trailing = splitext_addext(filename, _compressed_suffixes) - lext = ext.lower() - - # Special-case Nifti singles and Pairs - # Inline imports, as this module really shouldn't reference any image type - from .nifti1 import Nifti1Image, Nifti1Pair - from .nifti2 import Nifti2Image, Nifti2Pair - - converted: FileBasedImage - if type(img) == Nifti1Image and lext in ('.img', '.hdr'): - converted = Nifti1Pair.from_image(img) - elif type(img) == Nifti2Image and lext in ('.img', '.hdr'): - converted = Nifti2Pair.from_image(img) - elif type(img) == Nifti1Pair and lext == '.nii': - converted = Nifti1Image.from_image(img) - elif type(img) == Nifti2Pair and lext == '.nii': - converted = Nifti2Image.from_image(img) - else: # arbitrary conversion - valid_klasses = [klass for klass in all_image_classes if lext in klass.valid_exts] - if not valid_klasses: # if list is empty - raise ImageFileError(f'Cannot work out file type of "{filename}"') - - # Got a list of valid extensions, but that's no guarantee - # the file conversion will work. So, try each image - # in order... - for klass in valid_klasses: - try: - converted = klass.from_image(img) - break - except Exception as e: - err = e - else: - raise err - - converted.to_filename(filename, **kwargs) - - -@deprecate_with_version( - 'read_img_data deprecated. Please use ``img.dataobj.get_unscaled()`` instead.', - '3.2', - '5.0', -) -def read_img_data(img, prefer='scaled'): - """Read data from image associated with files - - If you want unscaled data, please use ``img.dataobj.get_unscaled()`` - instead. If you want scaled data, use ``img.get_fdata()`` (which will cache - the loaded array) or ``np.array(img.dataobj)`` (which won't cache the - array). If you want to load the data as for a modified header, save the - image with the modified header, and reload. - - Parameters - ---------- - img : ``SpatialImage`` - Image with valid image file in ``img.file_map``. Unlike the - ``img.get_fdata()`` method, this function returns the data read - from the image file, as specified by the *current* image header - and *current* image files. - prefer : str, optional - Can be 'scaled' - in which case we return the data with the - scaling suggested by the format, or 'unscaled', in which case we - return, if we can, the raw data from the image file, without the - scaling applied. - - Returns - ------- - arr : ndarray - array as read from file, given parameters in header - - Notes - ----- - Summary: please use the ``get_data`` method of `img` instead of this - function unless you are sure what you are doing. - - In general, you will probably prefer ``prefer='scaled'``, because - this gives the data as the image format expects to return it. - - Use `prefer` == 'unscaled' with care; the modified Analyze-type - formats such as SPM formats, and nifti1, specify that the image data - array is given by the raw data on disk, multiplied by a scalefactor - and maybe with the addition of a constant. This function, with - ``unscaled`` returns the data on the disk, without these - format-specific scalings applied. Please use this function only if - you absolutely need the unscaled data, and the magnitude of the - data, as given by the scalefactor, is not relevant to your - application. The Analyze-type formats have a single scalefactor +/- - offset per image on disk. If you do not care about the absolute - values, and will be removing the mean from the data, then the - unscaled values will have preserved intensity ratios compared to the - mean-centered scaled data. However, this is not necessarily true of - other formats with more complicated scaling - such as MINC. - """ - if prefer not in ('scaled', 'unscaled'): - raise ValueError(f'Invalid string "{prefer}" for "prefer"') - hdr = img.header - if not hasattr(hdr, 'raw_data_from_fileobj'): - # We can only do scaled - if prefer == 'unscaled': - raise ValueError('Can only do unscaled for Analyze types') - return np.array(img.dataobj) - # Analyze types - img_fh = img.file_map['image'] - img_file_like = img_fh.filename if img_fh.fileobj is None else img_fh.fileobj - if img_file_like is None: - raise ImageFileError('No image file specified for this image') - # Check the consumable values in the header - hdr = img.header - dao = img.dataobj - default_offset = hdr.get_data_offset() == 0 - default_scaling = hdr.get_slope_inter() == (None, None) - # If we have a proxy object and the header has any consumed fields, we load - # the consumed values back from the proxy - if is_proxy(dao) and (default_offset or default_scaling): - hdr = hdr.copy() - if default_offset and dao.offset != 0: - hdr.set_data_offset(dao.offset) - if default_scaling and (dao.slope, dao.inter) != (1, 0): - hdr.set_slope_inter(dao.slope, dao.inter) - with ImageOpener(img_file_like) as fileobj: - if prefer == 'scaled': - return hdr.data_from_fileobj(fileobj) - return hdr.raw_data_from_fileobj(fileobj) diff --git a/nibabel/minc1.py b/nibabel/minc1.py deleted file mode 100644 index d0b9fd5375..0000000000 --- a/nibabel/minc1.py +++ /dev/null @@ -1,338 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Read MINC1 format images""" - -from __future__ import annotations - -from numbers import Integral - -import numpy as np - -from .externals.netcdf import netcdf_file -from .fileslice import canonical_slicers -from .spatialimages import SpatialHeader, SpatialImage - -_dt_dict = { - ('b', 'unsigned'): np.uint8, - ('b', 'signed__'): np.int8, - ('c', 'unsigned'): 'S1', - ('h', 'unsigned'): np.uint16, - ('h', 'signed__'): np.int16, - ('i', 'unsigned'): np.uint32, - ('i', 'signed__'): np.int32, -} - -# See -# https://en.wikibooks.org/wiki/MINC/Reference/MINC1-programmers-guide#MINC_specific_convenience_functions -_default_dir_cos = {'xspace': [1, 0, 0], 'yspace': [0, 1, 0], 'zspace': [0, 0, 1]} - - -class MincError(Exception): - """Error when reading MINC files""" - - -class Minc1File: - """Class to wrap MINC1 format opened netcdf object - - Although it has some of the same methods as a ``Header``, we use - this only when reading a MINC file, to pull out useful header - information, and for the method of reading the data out - """ - - def __init__(self, mincfile): - self._mincfile = mincfile - self._image = mincfile.variables['image'] - self._dim_names = self._image.dimensions - # The code below will error with vector_dimensions. See: - # https://en.wikibooks.org/wiki/MINC/Reference/MINC1-programmers-guide#An_Introduction_to_NetCDF - # https://en.wikibooks.org/wiki/MINC/Reference/MINC1-programmers-guide#Image_dimensions - self._dims = [self._mincfile.variables[s] for s in self._dim_names] - # We don't currently support irregular spacing - # https://en.wikibooks.org/wiki/MINC/Reference/MINC1-programmers-guide#MINC_specific_convenience_functions - for dim in self._dims: - if dim.spacing != b'regular__': - raise ValueError('Irregular spacing not supported') - self._spatial_dims = [name for name in self._dim_names if name.endswith('space')] - # the MINC standard appears to allow the following variables to - # be undefined. - # https://en.wikibooks.org/wiki/MINC/Reference/MINC1-programmers-guide#Image_conversion_variables - # It wasn't immediately obvious what the defaults were. - self._image_max = self._mincfile.variables['image-max'] - self._image_min = self._mincfile.variables['image-min'] - - def _get_dimensions(self, var): - # Dimensions for a particular variable - # Differs for MINC1 and MINC2 - see: - # https://en.wikibooks.org/wiki/MINC/Reference/MINC2.0_File_Format_Reference#Associating_HDF5_dataspaces_with_MINC_dimensions - return var.dimensions - - def get_data_dtype(self): - typecode = self._image.typecode() - if typecode == 'f': - dtt = np.dtype(np.float32) - elif typecode == 'd': - dtt = np.dtype(np.float64) - else: - signtype = self._image.signtype.decode('latin-1') - dtt = _dt_dict[(typecode, signtype)] - return np.dtype(dtt).newbyteorder('>') - - def get_data_shape(self): - return self._image.data.shape - - def get_zooms(self): - """Get real-world sizes of voxels""" - # zooms must be positive; but steps in MINC can be negative - return tuple(abs(float(dim.step)) if hasattr(dim, 'step') else 1.0 for dim in self._dims) - - def get_affine(self): - nspatial = len(self._spatial_dims) - rot_mat = np.eye(nspatial) - steps = np.zeros((nspatial,)) - starts = np.zeros((nspatial,)) - dim_names = list(self._dim_names) # for indexing in loop - for i, name in enumerate(self._spatial_dims): - dim = self._dims[dim_names.index(name)] - rot_mat[:, i] = ( - dim.direction_cosines - if hasattr(dim, 'direction_cosines') - else _default_dir_cos[name] - ) - steps[i] = dim.step if hasattr(dim, 'step') else 1.0 - starts[i] = dim.start if hasattr(dim, 'start') else 0.0 - origin = np.dot(rot_mat, starts) - aff = np.eye(nspatial + 1) - aff[:nspatial, :nspatial] = rot_mat * steps - aff[:nspatial, nspatial] = origin - return aff - - def _get_valid_range(self): - """Return valid range for image data - - The valid range can come from the image 'valid_range' or - image 'valid_min' and 'valid_max', or, failing that, from the - data type range - """ - ddt = self.get_data_dtype() - info = np.iinfo(ddt.type) - try: - valid_range = self._image.valid_range - except AttributeError: - try: - valid_range = [self._image.valid_min, self._image.valid_max] - except AttributeError: - valid_range = [info.min, info.max] - if valid_range[0] < info.min or valid_range[1] > info.max: - raise ValueError('Valid range outside input data type range') - return np.asarray(valid_range, dtype=np.float64) - - def _get_scalar(self, var): - """Get scalar value from NetCDF scalar""" - return var.getValue() - - def _get_array(self, var): - """Get array from NetCDF array""" - return var.data - - def _normalize(self, data, sliceobj=()): - """Apply scaling to image data `data` already sliced with `sliceobj` - - https://en.wikibooks.org/wiki/MINC/Reference/MINC1-programmers-guide#Pixel_values_and_real_values - - MINC normalization uses "image-min" and "image-max" variables to - map the data from the valid range of the image to the range - specified by "image-min" and "image-max". - - The "image-max" and "image-min" are variables that describe the - "max" and "min" of image over some dimensions of "image". - - The usual case is that "image" has dimensions ["zspace", "yspace", - "xspace"] and "image-max" has dimensions ["zspace"], but there can be - up to two dimensions for over which scaling is specified. - - Parameters - ---------- - data : ndarray - data after applying `sliceobj` slicing to full image - sliceobj : tuple, optional - slice definition. If not specified, assume no slicing has been - applied to `data` - """ - ddt = self.get_data_dtype() - if np.issubdtype(ddt.type, np.floating): - return data - image_max = self._image_max - image_min = self._image_min - mx_dims = self._get_dimensions(image_max) - mn_dims = self._get_dimensions(image_min) - if mx_dims != mn_dims: - raise MincError('"image-max" and "image-min" do not have the same dimensions') - nscales = len(mx_dims) - if nscales > 2: - raise MincError('More than two scaling dimensions') - if mx_dims != self._dim_names[:nscales]: - raise MincError('image-max and image dimensions do not match') - dmin, dmax = self._get_valid_range() - out_data = np.clip(data, dmin, dmax) - if nscales == 0: # scalar values - imax = self._get_scalar(image_max) - imin = self._get_scalar(image_min) - else: # 1D or 2D array of scaling values - # We need to get the correct values from image-max and image-min to - # do the scaling. - shape = self.get_data_shape() - sliceobj = canonical_slicers(sliceobj, shape) - # Indices into sliceobj referring to image axes - ax_inds = [i for i, obj in enumerate(sliceobj) if obj is not None] - assert len(ax_inds) == len(shape) - # Slice imax, imin using same slicer as for data - nscales_ax = ax_inds[nscales] - i_slicer = sliceobj[:nscales_ax] - # Fill slicer to broadcast against sliced data; add length 1 axis - # for each axis except int axes (which are dropped by slicing) - broad_part = tuple( - None for s in sliceobj[ax_inds[nscales] :] if not isinstance(s, Integral) - ) - i_slicer += broad_part - imax = self._get_array(image_max)[i_slicer] - imin = self._get_array(image_min)[i_slicer] - slope = (imax - imin) / (dmax - dmin) - inter = imin - dmin * slope - out_data *= slope - out_data += inter - return out_data - - def get_scaled_data(self, sliceobj=()): - """Return scaled data for slice definition `sliceobj` - - Parameters - ---------- - sliceobj : tuple, optional - slice definition. If not specified, return whole array - - Returns - ------- - scaled_arr : array - array from minc file with scaling applied - """ - if sliceobj == (): - raw_data = self._image.data - else: - raw_data = self._image.data[sliceobj] - dtype = self.get_data_dtype() - data = np.asarray(raw_data).view(dtype) - return self._normalize(data, sliceobj) - - -class MincImageArrayProxy: - """MINC implementation of array proxy protocol - - The array proxy allows us to freeze the passed fileobj and - header such that it returns the expected data array. - """ - - def __init__(self, minc_file): - self.minc_file = minc_file - self._shape = minc_file.get_data_shape() - - @property - def shape(self): - return self._shape - - @property - def ndim(self): - return len(self.shape) - - @property - def is_proxy(self): - return True - - def __array__(self, dtype=None): - """Read data from file and apply scaling, casting to ``dtype`` - - If ``dtype`` is unspecified, the dtype is automatically determined. - - Parameters - ---------- - dtype : numpy dtype specifier, optional - A numpy dtype specifier specifying the type of the returned array. - - Returns - ------- - array - Scaled image data with type `dtype`. - """ - arr = self.minc_file.get_scaled_data(sliceobj=()) - if dtype is not None: - arr = arr.astype(dtype, copy=False) - return arr - - def __getitem__(self, sliceobj): - """Read slice `sliceobj` of data from file""" - return self.minc_file.get_scaled_data(sliceobj) - - -class MincHeader(SpatialHeader): - """Class to contain header for MINC formats""" - - # We don't use the data layout - this just in case we do later - data_layout = 'C' - - def data_to_fileobj(self, data, fileobj, rescale=True): - """See Header class for an implementation we can't use""" - raise NotImplementedError - - def data_from_fileobj(self, fileobj): - """See Header class for an implementation we can't use""" - raise NotImplementedError - - -class Minc1Header(MincHeader): - @classmethod - def may_contain_header(klass, binaryblock): - return binaryblock[:4] == b'CDF\x01' - - -class Minc1Image(SpatialImage): - """Class for MINC1 format images - - The MINC1 image class uses the default header type, rather than a specific - MINC header type - and reads the relevant information from the MINC file on - load. - """ - - header_class: type[MincHeader] = Minc1Header - header: MincHeader - _meta_sniff_len: int = 4 - valid_exts: tuple[str, ...] = ('.mnc',) - files_types: tuple[tuple[str, str], ...] = (('image', '.mnc'),) - _compressed_suffixes: tuple[str, ...] = ('.gz', '.bz2', '.zst') - - makeable = True - rw = False - - ImageArrayProxy = MincImageArrayProxy - - @classmethod - def from_file_map(klass, file_map, *, mmap=True, keep_file_open=None): - # Note that mmap and keep_file_open are included for proper - with file_map['image'].get_prepare_fileobj() as fobj: - minc_file = Minc1File(netcdf_file(fobj)) - affine = minc_file.get_affine() - if affine.shape != (4, 4): - raise MincError('Image does not have 3 spatial dimensions') - data_dtype = minc_file.get_data_dtype() - shape = minc_file.get_data_shape() - zooms = minc_file.get_zooms() - header = klass.header_class(data_dtype, shape, zooms) - data = klass.ImageArrayProxy(minc_file) - return klass(data, affine, header, extra=None, file_map=file_map) - - -load = Minc1Image.from_filename diff --git a/nibabel/minc2.py b/nibabel/minc2.py deleted file mode 100644 index 161be5c111..0000000000 --- a/nibabel/minc2.py +++ /dev/null @@ -1,184 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Preliminary MINC2 support - -Use with care; I haven't tested this against a wide range of MINC files. - -If you have a file that isn't read correctly, please send an example. - -Test reading with something like:: - - import nibabel as nib - img = nib.load('my_funny.mnc') - data = img.get_fdata() - print(data.mean()) - print(data.max()) - print(data.min()) - -and compare against command line output of:: - - mincstats my_funny.mnc -""" - -import warnings - -import numpy as np - -from .minc1 import Minc1File, Minc1Image, MincError, MincHeader - - -class Hdf5Bunch: - """Make object for accessing attributes of variable""" - - def __init__(self, var): - for name, value in var.attrs.items(): - setattr(self, name, value) - - -class Minc2File(Minc1File): - """Class to wrap MINC2 format file - - Although it has some of the same methods as a ``Header``, we use - this only when reading a MINC2 file, to pull out useful header - information, and for the method of reading the data out - """ - - def __init__(self, mincfile): - self._mincfile = mincfile - minc_part = mincfile['minc-2.0'] - # The whole image is the first of the entries in 'image' - image = minc_part['image']['0'] - self._image = image['image'] - self._dim_names = self._get_dimensions(self._image) - dimensions = minc_part['dimensions'] - self._dims = [Hdf5Bunch(dimensions[s]) for s in self._dim_names] - # We don't currently support irregular spacing - # https://en.wikibooks.org/wiki/MINC/Reference/MINC2.0_File_Format_Reference#Dimension_variable_attributes - for dim in self._dims: - # "If this attribute is absent, a value of regular__ should be assumed." - spacing = getattr(dim, 'spacing', b'regular__') - if spacing == b'irregular': - raise ValueError('Irregular spacing not supported') - elif spacing != b'regular__': - warnings.warn(f'Invalid spacing declaration: {spacing}; assuming regular') - - self._spatial_dims = [name for name in self._dim_names if name.endswith('space')] - self._image_max = image['image-max'] - self._image_min = image['image-min'] - - def _get_dimensions(self, var): - # Dimensions for a particular variable - # Differs for MINC1 and MINC2 - see: - # https://en.wikibooks.org/wiki/MINC/Reference/MINC2.0_File_Format_Reference#Associating_HDF5_dataspaces_with_MINC_dimensions - try: - dimorder = var.attrs['dimorder'].decode() - except KeyError: # No specified dimensions - return [] - # The dimension name list must contain only as many entries - # as the variable has dimensions. This reduces errors when an - # unnecessary dimorder attribute is left behind. - return dimorder.split(',')[: len(var.shape)] - - def get_data_dtype(self): - return self._image.dtype - - def get_data_shape(self): - return self._image.shape - - def _get_valid_range(self): - """Return valid range for image data - - The valid range can come from the image 'valid_range' or - failing that, from the data type range - """ - ddt = self.get_data_dtype() - info = np.iinfo(ddt.type) - try: - valid_range = self._image.attrs['valid_range'] - except (AttributeError, KeyError): - valid_range = [info.min, info.max] - else: - if valid_range[0] < info.min or valid_range[1] > info.max: - raise ValueError('Valid range outside input data type range') - return np.asarray(valid_range, dtype=np.float64) - - def _get_scalar(self, var): - """Get scalar value from HDF5 scalar""" - return var[()] - - def _get_array(self, var): - """Get array from HDF5 array""" - return np.asanyarray(var) - - def get_scaled_data(self, sliceobj=()): - """Return scaled data for slice definition `sliceobj` - - Parameters - ---------- - sliceobj : tuple, optional - slice definition. If not specified, return whole array - - Returns - ------- - scaled_arr : array - array from minc file with scaling applied - """ - if sliceobj == (): - raw_data = np.asanyarray(self._image) - else: # Try slicing into the HDF array (maybe it's possible) - try: - raw_data = self._image[sliceobj] - except (ValueError, TypeError): - raw_data = np.asanyarray(self._image)[sliceobj] - else: - raw_data = np.asanyarray(raw_data) - return self._normalize(raw_data, sliceobj) - - -class Minc2Header(MincHeader): - @classmethod - def may_contain_header(klass, binaryblock): - return binaryblock[:4] == b'\211HDF' - - -class Minc2Image(Minc1Image): - """Class for MINC2 images - - The MINC2 image class uses the default header type, rather than a - specific MINC header type - and reads the relevant information from - the MINC file on load. - """ - - # MINC2 does not do compressed whole files - _compressed_suffixes = () - header_class = Minc2Header - header: 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 # type: ignore[import] - - holder = file_map['image'] - if holder.filename is None: - raise MincError('MINC2 needs filename for load') - minc_file = Minc2File(h5py.File(holder.filename, 'r')) - affine = minc_file.get_affine() - if affine.shape != (4, 4): - raise MincError('Image does not have 3 spatial dimensions') - data_dtype = minc_file.get_data_dtype() - shape = minc_file.get_data_shape() - zooms = minc_file.get_zooms() - header = klass.header_class(data_dtype, shape, zooms) - data = klass.ImageArrayProxy(minc_file) - return klass(data, affine, header, extra=None, file_map=file_map) - - -load = Minc2Image.from_filename diff --git a/nibabel/mriutils.py b/nibabel/mriutils.py deleted file mode 100644 index 09067cc1e9..0000000000 --- a/nibabel/mriutils.py +++ /dev/null @@ -1,51 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Utilities for calculations related to MRI""" - -__all__ = ['calculate_dwell_time'] - -GYROMAGNETIC_RATIO = 42.576 # MHz/T for hydrogen nucleus -PROTON_WATER_FAT_SHIFT = 3.4 # ppm - - -class MRIError(ValueError): - pass - - -def calculate_dwell_time(water_fat_shift, echo_train_length, field_strength): - """Calculate the dwell time - - Parameters - ---------- - water_fat_shift : float - The water fat shift of the recording, in pixels. - echo_train_length : int - The echo train length of the imaging sequence. - field_strength : float - Strength of the magnet in Tesla, e.g. 3.0 for a 3T magnet recording. - - Returns - ------- - dwell_time : float - The dwell time in seconds. - - Raises - ------ - MRIError - if values are out of range - """ - if field_strength < 0: - raise MRIError('Field strength should be positive') - if echo_train_length <= 0: - raise MRIError('Echo train length should be >= 1') - return ( - (echo_train_length - 1) - * water_fat_shift - / (GYROMAGNETIC_RATIO * PROTON_WATER_FAT_SHIFT * field_strength * (echo_train_length + 1)) - ) diff --git a/nibabel/nicom/__init__.py b/nibabel/nicom/__init__.py deleted file mode 100644 index d15e0846ff..0000000000 --- a/nibabel/nicom/__init__.py +++ /dev/null @@ -1,32 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""DICOM reader - -.. currentmodule:: nibabel.nicom - -.. autosummary:: - :toctree: ../generated - - csareader - dicomreaders - dicomwrappers - dwiparams - structreader -""" - -import warnings - -warnings.warn( - 'The DICOM readers are highly experimental, unstable,' - ' and only work for Siemens time-series at the moment\n' - 'Please use with caution. We would be grateful for your ' - 'help in improving them', - UserWarning, - stacklevel=2, -) diff --git a/nibabel/nicom/ascconv.py b/nibabel/nicom/ascconv.py deleted file mode 100644 index 2eca5a1579..0000000000 --- a/nibabel/nicom/ascconv.py +++ /dev/null @@ -1,216 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -""" -Parse the "ASCCONV" meta data format found in a variety of Siemens MR files. -""" - -import ast -import re -from collections import OrderedDict - -ASCCONV_RE = re.compile( - r'### ASCCONV BEGIN((?:\s*[^=\s]+=[^=\s]+)*) ###\n(.*?)\n### ASCCONV END ###', - flags=re.MULTILINE | re.DOTALL, -) - - -class AscconvParseError(Exception): - """Error parsing ascconv file""" - - -class Atom: - """Object to hold operation, object type and object identifier - - An atom represents an element in an expression. For example:: - - a.b[0].c - - has four elements. We call these elements "atoms". - - We represent objects (like ``a``) as dicts for convenience. - - The last element (``.c``) is an ``op = ast.Attribute`` operation where the - object type (`obj_type`) of ``c`` is not constrained (we can't tell from - the operation what type it is). The `obj_id` is the name of the object -- - "c". - - The second to last element ``[0]``, is ``op = ast.Subscript``, with object type - dict (we know from the subsequent operation ``.c`` that this must be an - object, we represent the object by a dict). The `obj_id` is the index 0. - - Parameters - ---------- - op : {'name', 'attr', 'list'} - Assignment type. Assignment to name (root namespace), attribute or - list element. - obj_type : {list, dict, other} - Object type being assigned to. - obj_id : str or int - Key (``obj_type is dict``) or index (``obj_type is list``) - """ - - def __init__(self, op, obj_type, obj_id): - self.op = op - self.obj_type = obj_type - self.obj_id = obj_id - - -class NoValue: - """Signals no value present""" - - -def assign2atoms(assign_ast, default_class=int): - """Parse single assignment ast from ascconv line into atoms - - Parameters - ---------- - assign_ast : assignment statement ast - ast derived from single line of ascconv file. - default_class : class, optional - Class that will create an object where we cannot yet know the object - type in the assignment. - - Returns - ------- - atoms : list - List of :class:`atoms`. See docstring for :class:`atoms`. Defines - left to right sequence of assignment in `line_ast`. - """ - if not len(assign_ast.targets) == 1: - raise AscconvParseError('Too many targets in assign') - target = assign_ast.targets[0] - atoms = [] - prev_target_type = default_class # Placeholder for any scalar value - while True: - if isinstance(target, ast.Name): - atoms.append(Atom(target, prev_target_type, target.id)) - break - if isinstance(target, ast.Attribute): - atoms.append(Atom(target, prev_target_type, target.attr)) - target = target.value - prev_target_type = OrderedDict - elif isinstance(target, ast.Subscript): - index = target.slice.value - atoms.append(Atom(target, prev_target_type, index)) - target = target.value - prev_target_type = list - else: - raise AscconvParseError(f'Unexpected LHS element {target}') - return reversed(atoms) - - -def _create_obj_in(atom, root): - """Find / create object defined in `atom` in dict-like given by `root` - - Returns corresponding value if there is already a key matching - `atom.obj_id` in `root`. - - Otherwise, create new object with ``atom.obj_type`, insert into dictionary, - and return new object. - - Can therefore modify `root` in place. - """ - name = atom.obj_id - obj = root.get(name, NoValue) - if obj is not NoValue: - return obj - obj = atom.obj_type() - root[name] = obj - return obj - - -def _create_subscript_in(atom, root): - """Find / create and insert object defined by `atom` from list `root` - - The `atom` has an index, defined in ``atom.obj_id``. If `root` is long - enough to contain this index, return the object at that index. Otherwise, - extend `root` with None elements to contain index ``atom.obj_id``, then - create a new object via ``atom.obj_type()``, insert at the end of the list, - and return this object. - - Can therefore modify `root` in place. - """ - curr_n = len(root) - index = atom.obj_id - if curr_n > index: - return root[index] - obj = atom.obj_type() - root += [None] * (index - curr_n) + [obj] - return obj - - -def obj_from_atoms(atoms, namespace): - """Return object defined by list `atoms` in dict-like `namespace` - - Parameters - ---------- - atoms : list - List of :class:`atoms` - namespace : dict-like - Namespace in which object will be defined. - - Returns - ------- - obj_root : object - Namespace such that we can set a desired value to the object defined in - `atoms` with ``obj_root[obj_key] = value``. - obj_key : str or int - Index into list or key into dictionary for `obj_root`. - """ - root_obj = namespace - for el in atoms: - prev_root = root_obj - if isinstance(el.op, (ast.Attribute, ast.Name)): - root_obj = _create_obj_in(el, root_obj) - else: - root_obj = _create_subscript_in(el, root_obj) - if not isinstance(root_obj, el.obj_type): - raise AscconvParseError(f'Unexpected type for {el.obj_id} in {prev_root}') - return prev_root, el.obj_id - - -def _get_value(assign): - value = assign.value - if isinstance(value, ast.Constant): - return value.value - if isinstance(value, ast.UnaryOp) and isinstance(value.op, ast.USub): - return -value.operand.value - raise AscconvParseError(f'Unexpected RHS of assignment: {value}') - - -def parse_ascconv(ascconv_str, str_delim='"'): - """Parse the 'ASCCONV' format from `input_str`. - - Parameters - ---------- - ascconv_str : str - The string we are parsing - str_delim : str, optional - String delimiter. Typically '"' or '""' - - Returns - ------- - prot_dict : OrderedDict - Meta data pulled from the ASCCONV section. - attrs : OrderedDict - Any attributes stored in the 'ASCCONV BEGIN' line - - Raises - ------ - AsconvParseError - A line of the ASCCONV section could not be parsed. - """ - attrs, content = ASCCONV_RE.match(ascconv_str).groups() - attrs = OrderedDict(tuple(x.split('=')) for x in attrs.split()) - # Normalize string start / end markers to something Python understands - content = content.replace(str_delim, '"""').replace('\\', '\\\\') - # Use Python's own parser to parse modified ASCCONV assignments - tree = ast.parse(content) - - prot_dict = OrderedDict() - for assign in tree.body: - atoms = assign2atoms(assign) - obj_to_index, key = obj_from_atoms(atoms, prot_dict) - obj_to_index[key] = _get_value(assign) - - return prot_dict, attrs diff --git a/nibabel/nicom/csareader.py b/nibabel/nicom/csareader.py deleted file mode 100644 index b98dae7403..0000000000 --- a/nibabel/nicom/csareader.py +++ /dev/null @@ -1,260 +0,0 @@ -"""CSA header reader from SPM spec""" - -import numpy as np - -from .structreader import Unpacker -from .utils import find_private_section - -# DICOM VR code to Python type -_CONVERTERS = { - 'FL': float, # float - 'FD': float, # double - 'DS': float, # decimal string - 'SS': int, # signed short - 'US': int, # unsigned short - 'SL': int, # signed long - 'UL': int, # unsigned long - 'IS': int, # integer string -} - -MAX_CSA_ITEMS = 1000 - - -class CSAError(Exception): - pass - - -class CSAReadError(CSAError): - pass - - -def get_csa_header(dcm_data, csa_type='image'): - """Get CSA header information from DICOM header - - Return None if the header does not contain CSA information of the - specified `csa_type` - - Parameters - ---------- - dcm_data : dicom.Dataset - DICOM dataset. Should implement ``__getitem__`` and, if initial check - for presence of ``dcm_data[(0x29, 0x10)]`` passes, should satisfy - interface for ``find_private_section``. - csa_type : {'image', 'series'}, optional - Type of CSA field to read; default is 'image' - - Returns - ------- - csa_info : None or dict - Parsed CSA field of `csa_type` or None, if we cannot find the CSA - information. - """ - csa_type = csa_type.lower() - if csa_type == 'image': - element_offset = 0x10 - elif csa_type == 'series': - element_offset = 0x20 - else: - raise ValueError(f'Invalid CSA header type "{csa_type}"') - if not (0x29, 0x10) in dcm_data: # Cannot be Siemens CSA - return None - section_start = find_private_section(dcm_data, 0x29, 'SIEMENS CSA HEADER') - if section_start is None: - return None - element_no = section_start + element_offset - try: - tag = dcm_data[(0x29, element_no)] - except KeyError: - # The element could be missing due to anonymization - return None - return read(tag.value) - - -def read(csa_str): - """Read CSA header from string `csa_str` - - Parameters - ---------- - csa_str : str - byte string containing CSA header information - - Returns - ------- - header : dict - header information as dict, where `header` has fields (at least) - ``type, n_tags, tags``. ``header['tags']`` is also a dictionary - with one key, value pair for each tag in the header. - """ - csa_len = len(csa_str) - csa_dict = {'tags': {}} - hdr_id = csa_str[:4] - up_str = Unpacker(csa_str, endian='<') - if hdr_id == b'SV10': # CSA2 - hdr_type = 2 - up_str.ptr = 4 # omit the SV10 - csa_dict['unused0'] = up_str.read(4) - else: # CSA1 - hdr_type = 1 - csa_dict['type'] = hdr_type - csa_dict['n_tags'], csa_dict['check'] = up_str.unpack('2I') - if not 0 < csa_dict['n_tags'] <= MAX_CSA_ITEMS: - raise CSAReadError( - f'Number of tags `t` should be 0 < t <= {MAX_CSA_ITEMS}. ' - f'Instead found {csa_dict["n_tags"]} tags.' - ) - for tag_no in range(csa_dict['n_tags']): - name, vm, vr, syngodt, n_items, last3 = up_str.unpack('64si4s3i') - vr = nt_str(vr) - name = nt_str(name) - tag = { - 'n_items': n_items, - 'vm': vm, # value multiplicity - 'vr': vr, # value representation - 'syngodt': syngodt, - 'last3': last3, - 'tag_no': tag_no, - } - if vm == 0: - n_values = n_items - else: - n_values = vm - # data converter - converter = _CONVERTERS.get(vr) - # CSA1 specific length modifier - if tag_no == 1: - tag0_n_items = n_items - if n_items > MAX_CSA_ITEMS: - raise CSAReadError(f'Expected <= {MAX_CSA_ITEMS} tags, got {n_items}') - items = [] - for item_no in range(n_items): - x0, x1, x2, x3 = up_str.unpack('4i') - ptr = up_str.ptr - if hdr_type == 1: # CSA1 - odd length calculation - item_len = x0 - tag0_n_items - if item_len < 0 or (ptr + item_len) > csa_len: - if item_no < vm: - items.append('') - break - else: # CSA2 - item_len = x1 - if (ptr + item_len) > csa_len: - raise CSAReadError('Item is too long, aborting read') - if item_no >= n_values: - assert item_len == 0 - continue - item = nt_str(up_str.read(item_len)) - if converter: - # we may have fewer real items than are given in - # n_items, but we don't know how many - assume that - # we've reached the end when we hit an empty item - if item_len == 0: - n_values = item_no - continue - item = converter(item) - items.append(item) - # go to 4 byte boundary - plus4 = item_len % 4 - if plus4 != 0: - up_str.ptr += 4 - plus4 - tag['items'] = items - csa_dict['tags'][name] = tag - return csa_dict - - -def get_scalar(csa_dict, tag_name): - try: - items = csa_dict['tags'][tag_name]['items'] - except KeyError: - return None - if len(items) == 0: - return None - return items[0] - - -def get_vector(csa_dict, tag_name, n): - try: - items = csa_dict['tags'][tag_name]['items'] - except KeyError: - return None - if len(items) == 0: - return None - if len(items) != n: - raise ValueError(f'Expecting {n} vector') - return np.array(items) - - -def is_mosaic(csa_dict): - """Return True if the data is of Mosaic type - - Parameters - ---------- - csa_dict : dict - dict containing read CSA data - - Returns - ------- - tf : bool - True if the `dcm_data` appears to be of Siemens mosaic type, - False otherwise - """ - if csa_dict is None: - return False - if get_acq_mat_txt(csa_dict) is None: - return False - n_o_m = get_n_mosaic(csa_dict) - return not (n_o_m is None) and n_o_m != 0 - - -def get_n_mosaic(csa_dict): - return get_scalar(csa_dict, 'NumberOfImagesInMosaic') - - -def get_acq_mat_txt(csa_dict): - return get_scalar(csa_dict, 'AcquisitionMatrixText') - - -def get_slice_normal(csa_dict): - return get_vector(csa_dict, 'SliceNormalVector', 3) - - -def get_b_matrix(csa_dict): - vals = get_vector(csa_dict, 'B_matrix', 6) - if vals is None: - return - # the 6 vector is the upper triangle of the symmetric B matrix - inds = np.array([0, 1, 2, 1, 3, 4, 2, 4, 5]) - B = np.array(vals)[inds] - return B.reshape(3, 3) - - -def get_b_value(csa_dict): - return get_scalar(csa_dict, 'B_value') - - -def get_g_vector(csa_dict): - return get_vector(csa_dict, 'DiffusionGradientDirection', 3) - - -def get_ice_dims(csa_dict): - dims = get_scalar(csa_dict, 'ICE_Dims') - if dims is None: - return None - return dims.split('_') - - -def nt_str(s): - """Strip string to first null - - Parameters - ---------- - s : bytes - - Returns - ------- - sdash : str - s stripped to first occurrence of null (0) - """ - zero_pos = s.find(b'\x00') - if zero_pos == -1: - return s - return s[:zero_pos].decode('latin-1') diff --git a/nibabel/nicom/dicomreaders.py b/nibabel/nicom/dicomreaders.py deleted file mode 100644 index 07362ee47d..0000000000 --- a/nibabel/nicom/dicomreaders.py +++ /dev/null @@ -1,197 +0,0 @@ -import glob -from os.path import join as pjoin - -import numpy as np - -from .. import Nifti1Image -from .dicomwrappers import wrapper_from_data, wrapper_from_file - - -class DicomReadError(Exception): - pass - - -DPCS_TO_TAL = np.diag([-1, -1, 1, 1]) - - -def mosaic_to_nii(dcm_data): - """Get Nifti file from Siemens - - Parameters - ---------- - dcm_data : ``dicom.DataSet`` - DICOM header / image as read by ``dicom`` package - - Returns - ------- - img : ``Nifti1Image`` - Nifti image object - """ - dcm_w = wrapper_from_data(dcm_data) - if not dcm_w.is_mosaic: - raise DicomReadError('data does not appear to be in mosaic format') - data = dcm_w.get_data() - aff = np.dot(DPCS_TO_TAL, dcm_w.affine) - return Nifti1Image(data, aff) - - -def read_mosaic_dwi_dir(dicom_path, globber='*.dcm', dicom_kwargs=None): - return read_mosaic_dir(dicom_path, globber, check_is_dwi=True, dicom_kwargs=dicom_kwargs) - - -def read_mosaic_dir(dicom_path, globber='*.dcm', check_is_dwi=False, dicom_kwargs=None): - """Read all Siemens mosaic DICOMs in directory, return arrays, params - - Parameters - ---------- - dicom_path : str - path containing mosaic DICOM images - globber : str, optional - glob to apply within `dicom_path` to select DICOM files. Default - is ``*.dcm`` - check_is_dwi : bool, optional - If True, raises an error if we don't find DWI information in the - DICOM headers. - dicom_kwargs : None or dict - Extra keyword arguments to pass to the pydicom ``dcmread`` function. - - Returns - ------- - data : 4D array - data array with last dimension being acquisition. If there were N - acquisitions, each of shape (X, Y, Z), `data` will be shape (X, - Y, Z, N) - affine : (4,4) array - affine relating 3D voxel space in data to RAS world space - b_values : (N,) array - b values for each acquisition. nan if we did not find diffusion - information for these images. - unit_gradients : (N, 3) array - gradient directions of unit length for each acquisition. (nan, - nan, nan) if we did not find diffusion information. - """ - if dicom_kwargs is None: - dicom_kwargs = {} - full_globber = pjoin(dicom_path, globber) - filenames = sorted(glob.glob(full_globber)) - b_values = [] - gradients = [] - arrays = [] - if len(filenames) == 0: - raise OSError(f'Found no files with "{full_globber}"') - for fname in filenames: - dcm_w = wrapper_from_file(fname, **dicom_kwargs) - # Because the routine sorts by filename, it only makes sense to use - # this order for mosaic images. Slice by slice dicoms need more - # sensible sorting - if not dcm_w.is_mosaic: - raise DicomReadError('data does not appear to be in mosaic format') - arrays.append(dcm_w.get_data()[..., None]) - q = dcm_w.q_vector - if q is None: # probably not diffusion - if check_is_dwi: - raise DicomReadError( - f'Could not find diffusion information reading file "{fname}"; ' - 'is it possible this is not a _raw_ diffusion directory? ' - 'Could it be a processed dataset like ADC etc?' - ) - b = np.nan - g = np.ones((3,)) + np.nan - else: - b = dcm_w.b_value - g = dcm_w.b_vector - b_values.append(b) - gradients.append(g) - affine = np.dot(DPCS_TO_TAL, dcm_w.affine) - return (np.concatenate(arrays, -1), affine, np.array(b_values), np.array(gradients)) - - -def slices_to_series(wrappers): - """Sort sequence of slice wrappers into series - - This follows the SPM model fairly closely - - Parameters - ---------- - wrappers : sequence - sequence of ``Wrapper`` objects for sorting into volumes - - Returns - ------- - series : sequence - sequence of sequences of wrapper objects, where each sequence is - wrapper objects comprising a series, sorted into slice order - """ - # first pass - volume_lists = [wrappers[0:1]] - for dw in wrappers[1:]: - for vol_list in volume_lists: - if dw.is_same_series(vol_list[0]): - vol_list.append(dw) - break - else: # no match in current volume lists - volume_lists.append([dw]) - print(f'We appear to have {len(volume_lists)} Series') - # second pass - out_vol_lists = [] - for vol_list in volume_lists: - if len(vol_list) > 1: - vol_list.sort(key=_slice_sorter) - zs = [s.slice_indicator for s in vol_list] - if len(set(zs)) < len(zs): # not unique zs - # third pass - out_vol_lists += _third_pass(vol_list) - continue - out_vol_lists.append(vol_list) - print(f'We have {len(out_vol_lists)} volumes after second pass') - # final pass check - for vol_list in out_vol_lists: - zs = [s.slice_indicator for s in vol_list] - diffs = np.diff(zs) - if not np.allclose(diffs, np.mean(diffs)): - raise DicomReadError('Largeish slice gaps - missing DICOMs?') - return out_vol_lists - - -def _slice_sorter(s): - return s.slice_indicator - - -def _instance_sorter(s): - return s.instance_number - - -def _third_pass(wrappers): - """What we do when there are not unique zs in a slice set""" - inos = [s.instance_number for s in wrappers] - msg_fmt = ( - 'Plausibly matching slices, but where some have ' - 'the same apparent slice location, and %s; ' - '- slices are probably unsortable' - ) - if None in inos: - raise DicomReadError(msg_fmt % 'some or all slices with missing InstanceNumber') - if len(set(inos)) < len(inos): - raise DicomReadError(msg_fmt % 'some or all slices with the same InstanceNumber') - # sort by instance number - wrappers.sort(key=_instance_sorter) - # start loop, in which we start a new volume, each time we see a z - # we've seen already in the current volume - dw = wrappers[0] - these_zs = [dw.slice_indicator] - vol_list = [dw] - out_vol_lists = [vol_list] - for dw in wrappers[1:]: - z = dw.slice_indicator - if z not in these_zs: - # same volume - vol_list.append(dw) - these_zs.append(z) - continue - # new volume - vol_list.sort(_slice_sorter) - vol_list = [dw] - these_zs = [z] - out_vol_lists.append(vol_list) - vol_list.sort(_slice_sorter) - return out_vol_lists diff --git a/nibabel/nicom/dicomwrappers.py b/nibabel/nicom/dicomwrappers.py deleted file mode 100755 index 26ca75b156..0000000000 --- a/nibabel/nicom/dicomwrappers.py +++ /dev/null @@ -1,1281 +0,0 @@ -"""Classes to wrap DICOM objects and files - -The wrappers encapsulate the capabilities of the different DICOM -formats. - -They also allow dictionary-like access to named fields. - -For calculated attributes, we return None where needed data is missing. -It seemed strange to raise an error during attribute processing, other -than an AttributeError - breaking the 'properties manifesto'. So, any -processing that needs to raise an error, should be in a method, rather -than in a property, or property-like thing. -""" - -import operator -import re -import warnings -from functools import cached_property - -import numpy as np - -from nibabel.optpkg import optional_package - -from ..openers import ImageOpener -from . import csareader as csar -from .dwiparams import B2q, nearest_pos_semi_def, q2bg -from .utils import Vendor, find_private_section, vendor_from_private - -pydicom = optional_package('pydicom')[0] - - -class WrapperError(Exception): - pass - - -class WrapperPrecisionError(WrapperError): - pass - - -def wrapper_from_file(file_like, *args, **kwargs): - r"""Create DICOM wrapper from `file_like` object - - Parameters - ---------- - file_like : object - filename string or file-like object, pointing to a valid DICOM - file readable by ``pydicom`` - \*args : positional - args to ``dicom.dcmread`` command. - \*\*kwargs : keyword - args to ``dicom.dcmread`` command. ``force=True`` might be a - likely keyword argument. - - Returns - ------- - dcm_w : ``dicomwrappers.Wrapper`` or subclass - DICOM wrapper corresponding to DICOM data type - """ - with ImageOpener(file_like) as fobj: - dcm_data = pydicom.dcmread(fobj, *args, **kwargs) - return wrapper_from_data(dcm_data) - - -def wrapper_from_data(dcm_data, frame_filters=None): - """Create DICOM wrapper from DICOM data object - - Parameters - ---------- - dcm_data : ``dicom.dataset.Dataset`` instance or similar - Object allowing attribute access, with DICOM attributes. - Probably a dataset as read by ``pydicom``. - - frame_filters - Optionally override the `frame_filters` used to create a `MultiFrameWrapper` - - Returns - ------- - dcm_w : ``dicomwrappers.Wrapper`` or subclass - DICOM wrapper corresponding to DICOM data type - """ - sop_class = dcm_data.get('SOPClassUID') - # try to detect what type of dicom object to wrap - if sop_class == '1.2.840.10008.5.1.4.1.1.4.1': # Enhanced MR Image Storage - return MultiframeWrapper(dcm_data, frame_filters) - # Check for non-enhanced (legacy) Siemens DICOM format types - # Only Siemens will have data for the CSA header - try: - csa = csar.get_csa_header(dcm_data) - except csar.CSAReadError as e: - warnings.warn( - f'Error while attempting to read CSA header: {e.args}\n' - 'Ignoring Siemens private (CSA) header info.' - ) - csa = None - if csa is None: - return Wrapper(dcm_data) - if csar.is_mosaic(csa): - # Mosaic is a "tiled" image - return MosaicWrapper(dcm_data, csa) - # Assume data is in a single slice format per file - return SiemensWrapper(dcm_data, csa) - - -class Wrapper: - """Class to wrap general DICOM files - - Methods: - - * get_data() - * get_unscaled_data() - * get_pixel_array() - * is_same_series(other) - * __getitem__ : return attributes from `dcm_data` - * get(key[, default]) - as usual given __getitem__ above - - Attributes and things that look like attributes: - - * affine : (4, 4) array - * dcm_data : object - * image_shape : tuple - * image_orient_patient : (3,2) array - * slice_normal : (3,) array - * rotation_matrix : (3,3) array - * voxel_sizes : tuple length 3 - * image_position : sequence length 3 - * slice_indicator : float - * series_signature : tuple - * scale_factors : (N, 2) array - * vendor : Vendor - """ - - is_csa = False - is_mosaic = False - is_multiframe = False - b_matrix = None - q_vector = None - - def __init__(self, dcm_data): - """Initialize wrapper - - Parameters - ---------- - dcm_data : object - object should allow 'get' and '__getitem__' access. Usually this - will be a ``dicom.dataset.Dataset`` object resulting from reading a - DICOM file. - """ - self.dcm_data = dcm_data - - @cached_property - def vendor(self): - """The vendor of the instrument that produced the DICOM""" - # Look at manufacturer tag first - mfgr = self.get('Manufacturer') - if mfgr: - if re.search(r'Siemens', mfgr, re.IGNORECASE): - return Vendor.SIEMENS - if re.search(r'Philips', mfgr, re.IGNORECASE): - return Vendor.PHILIPS - if re.search(r'GE Medical', mfgr, re.IGNORECASE): - return Vendor.GE - # Next look at UID prefixes - for uid_src in ('StudyInstanceUID', 'SeriesInstanceUID', 'SOPInstanceUID'): - uid = str(self.get(uid_src)) - if uid.startswith(('1.3.12.2.1007.', '1.3.12.2.1107.')): - return Vendor.SIEMENS - if uid.startswith(('1.3.46', '1.3.12.2.1017')): - return Vendor.PHILIPS - if uid.startswith('1.2.840.113619'): - return Vendor.GE - # Finally look for vendor specific private blocks - return vendor_from_private(self.dcm_data) - - @cached_property - def image_shape(self): - """The array shape as it will be returned by ``get_data()``""" - shape = (self.get('Rows'), self.get('Columns')) - if None in shape: - return None - return shape - - @cached_property - def image_orient_patient(self): - """Note that this is _not_ LR flipped""" - iop = self.get('ImageOrientationPatient') - if iop is None: - return None - # Values are python Decimals in pydicom 0.9.7 - iop = np.array(list(map(float, iop))) - return np.array(iop).reshape(2, 3).T - - @cached_property - def slice_normal(self): - iop = self.image_orient_patient - if iop is None: - return None - # iop[:, 0] is column index cosine, iop[:, 1] is row index cosine - return np.cross(iop[:, 1], iop[:, 0]) - - @cached_property - def rotation_matrix(self): - """Return rotation matrix between array indices and mm - - Note that we swap the two columns of the 'ImageOrientPatient' - when we create the rotation matrix. This is takes into account - the slightly odd ij transpose construction of the DICOM - orientation fields - see doc/theory/dicom_orientaiton.rst. - """ - iop = self.image_orient_patient - s_norm = self.slice_normal - if iop is None or s_norm is None: - return None - R = np.eye(3) - # np.fliplr(iop) gives matrix F in - # doc/theory/dicom_orientation.rst The fliplr accounts for the - # fact that the first column in ``iop`` refers to changes in - # column index, and the second to changes in row index. - R[:, :2] = np.fliplr(iop) - R[:, 2] = s_norm - # check this is in fact a rotation matrix. Error comes from compromise - # motivated in ``doc/source/notebooks/ata_error.ipynb``, and from - # discussion at https://github.com/nipy/nibabel/pull/156 - if not np.allclose(np.eye(3), np.dot(R, R.T), atol=5e-5): - raise WrapperPrecisionError('Rotation matrix not nearly orthogonal') - return R - - @cached_property - def voxel_sizes(self): - """voxel sizes for array as returned by ``get_data()``""" - # pix space gives (row_spacing, column_spacing). That is, the - # mm you move when moving from one row to the next, and the mm - # you move when moving from one column to the next - pix_space = self.get('PixelSpacing') - if pix_space is None: - return None - zs = self.get('SpacingBetweenSlices') - if zs is None: - zs = self.get('SliceThickness') - if zs is None or zs == '': - zs = 1 - # Protect from python decimals in pydicom 0.9.7 - zs = float(zs) - pix_space = list(map(float, pix_space)) - return tuple(pix_space + [zs]) - - @cached_property - def image_position(self): - """Return position of first voxel in data block - - Parameters - ---------- - None - - Returns - ------- - img_pos : (3,) array - position in mm of voxel (0,0) in image array - """ - ipp = self.get('ImagePositionPatient') - if ipp is None: - return None - # Values are python Decimals in pydicom 0.9.7 - return np.array(list(map(float, ipp))) - - @cached_property - def slice_indicator(self): - """A number that is higher for higher slices in Z - - Comparing this number between two adjacent slices should give a - difference equal to the voxel size in Z. - - See doc/theory/dicom_orientation for description - """ - ipp = self.image_position - s_norm = self.slice_normal - if ipp is None or s_norm is None: - return None - return np.inner(ipp, s_norm) - - @cached_property - def instance_number(self): - """Just because we use this a lot for sorting""" - return self.get('InstanceNumber') - - @cached_property - def series_signature(self): - """Signature for matching slices into series - - We use `signature` in ``self.is_same_series(other)``. - - Returns - ------- - signature : dict - with values of 2-element sequences, where first element is - value, and second element is function to compare this value - with another. This allows us to pass things like arrays, - that might need to be ``allclose`` instead of equal - """ - # dictionary with value, comparison func tuple - signature = {} - eq = operator.eq - for key in ( - 'SeriesInstanceUID', - 'SeriesNumber', - 'ImageType', - 'SequenceName', - 'EchoNumbers', - ): - signature[key] = (self.get(key), eq) - signature['image_shape'] = (self.image_shape, eq) - signature['iop'] = (self.image_orient_patient, none_or_close) - signature['vox'] = (self.voxel_sizes, none_or_close) - return signature - - def __getitem__(self, key): - """Return values from DICOM object""" - if key not in self.dcm_data: - raise KeyError(f'"{key}" not in self.dcm_data') - return self.dcm_data.get(key) - - def get(self, key, default=None): - """Get values from underlying dicom data""" - return self.dcm_data.get(key, default) - - @property - def affine(self): - """Mapping between voxel and DICOM coordinate system - - (4, 4) affine matrix giving transformation between voxels in data array - and mm in the DICOM patient coordinate system. - """ - # rotation matrix already accounts for the ij transpose in the - # DICOM image orientation patient transform. So. column 0 is - # direction cosine for changes in row index, column 1 is - # direction cosine for changes in column index - orient = self.rotation_matrix - # therefore, these voxel sizes are in the right order (row, - # column, slice) - vox = self.voxel_sizes - ipp = self.image_position - if any(p is None for p in (orient, vox, ipp)): - raise WrapperError('Not enough information for affine') - aff = np.eye(4) - aff[:3, :3] = orient * np.array(vox) - aff[:3, 3] = ipp - return aff - - def get_pixel_array(self): - """Return raw pixel array without reshaping or scaling - - Returns - ------- - data : array - array with raw pixel data from DICOM - """ - data = self.dcm_data.get('pixel_array') - if data is None: - raise WrapperError('Cannot find data in DICOM') - return data - - def get_unscaled_data(self): - """Return pixel array that is potentially reshaped, but without any scaling - - Returns - ------- - data : array - array with raw pixel data from DICOM - """ - return self.get_pixel_array() - - def get_data(self): - """Get potentially scaled and reshaped image data from DICOMs - - We return the data as DICOM understands it, first dimension is - rows, second dimension is columns - - Returns - ------- - data : array - array with data as scaled from any scaling in the DICOM - fields. - """ - return self._scale_data(self.get_unscaled_data()) - - def is_same_series(self, other): - """Return True if `other` appears to be in same series - - Parameters - ---------- - other : object - object with ``series_signature`` attribute that is a - mapping. Usually it's a ``Wrapper`` or sub-class instance. - - Returns - ------- - tf : bool - True if `other` might be in the same series as `self`, False - otherwise. - """ - # compare signature dictionaries. The dictionaries each contain - # comparison rules, we prefer our own when we have them. If a - # key is not present in either dictionary, assume the value is - # None. - my_sig = self.series_signature - your_sig = other.series_signature - my_keys = set(my_sig) - your_keys = set(your_sig) - # we have values in both signatures - for key in my_keys.intersection(your_keys): - v1, func = my_sig[key] - v2, _ = your_sig[key] - if not func(v1, v2): - return False - # values present in one or the other but not both - for keys, sig in ((my_keys - your_keys, my_sig), (your_keys - my_keys, your_sig)): - for key in keys: - v1, func = sig[key] - if not func(v1, None): - return False - return True - - @cached_property - def scale_factors(self): - """Return (2, N) array of slope/intercept pairs""" - scaling = self._get_best_scale_factor(self.dcm_data) - if scaling is None: - if self.vendor == Vendor.PHILIPS: - warnings.warn( - 'Unable to find Philips private scale factor, cross-series comparisons may be invalid' - ) - scaling = (1, 0) - return np.array((scaling,)) - - def _get_rwv_scale_factor(self, dcm_data): - """Return the first set of 'real world' scale factors with defined units""" - rw_seq = dcm_data.get('RealWorldValueMappingSequence') - if rw_seq: - for rw_map in rw_seq: - try: - units = rw_map.MeasurementUnitsCodeSequence[0].CodeMeaning - except (AttributeError, IndexError): - continue - if units not in ('', 'no units', 'UNDEFINED'): - return ( - rw_map.get('RealWorldValueSlope', 1), - rw_map.get('RealWorldValueIntercept', 0), - ) - - def _get_legacy_scale_factor(self, dcm_data): - """Return scale factors from older 'Modality LUT' macro - - For Philips data we require RescaleType is defined and not set to 'normalized' - """ - pix_trans_seq = dcm_data.get('PixelValueTransformationSequence') - if pix_trans_seq is not None: - pix_trans = pix_trans_seq[0] - if self.vendor != Vendor.PHILIPS or pix_trans.get('RescaleType', 'US') not in ( - '', - 'US', - 'normalized', - ): - return (pix_trans.get('RescaleSlope', 1), pix_trans.get('RescaleIntercept', 0)) - if ( - dcm_data.get('RescaleSlope') is not None - or dcm_data.get('RescaleIntercept') is not None - ): - if self.vendor != Vendor.PHILIPS or dcm_data.get('RescaleType', 'US') not in ( - '', - 'US', - 'normalized', - ): - return (dcm_data.get('RescaleSlope', 1), dcm_data.get('RescaleIntercept', 0)) - - def _get_philips_scale_factor(self, dcm_data): - """Return scale factors from Philips private element - - If we don't have any other scale factors that are tied to real world units, then - this is the best scaling to use to enable cross-series comparisons - """ - offset = find_private_section(dcm_data, 0x2005, 'Philips MR Imaging DD 001') - priv_scale = None if offset is None else dcm_data.get((0x2005, offset + 0xE)) - if priv_scale is not None: - return (priv_scale.value, 0.0) - - def _get_best_scale_factor(self, dcm_data): - """Return the most appropriate scale factor found or None""" - scaling = self._get_rwv_scale_factor(dcm_data) - if scaling is not None: - return scaling - scaling = self._get_legacy_scale_factor(dcm_data) - if scaling is not None: - return scaling - if self.vendor == Vendor.PHILIPS: - scaling = self._get_philips_scale_factor(dcm_data) - if scaling is not None: - return scaling - - def _scale_data(self, data): - # depending on pydicom and dicom files, values might need casting from - # Decimal to float - scale, offset = self.scale_factors[0] - return self._apply_scale_offset(data, scale, offset) - - def _apply_scale_offset(self, data, scale, offset): - # a little optimization. If we are applying either the scale or - # the offset, we need to allow upcasting to float. - if scale != 1: - if offset == 0: - return data * scale - return data * scale + offset - if offset != 0: - return data + offset - return data - - @cached_property - def b_value(self): - """Return b value for diffusion or None if not available""" - q_vec = self.q_vector - if q_vec is None: - return None - return q2bg(q_vec)[0] - - @cached_property - def b_vector(self): - """Return b vector for diffusion or None if not available""" - q_vec = self.q_vector - if q_vec is None: - return None - return q2bg(q_vec)[1] - - -class FrameFilter: - """Base class for defining how to filter out (ignore) frames from a multiframe file - - It is guaranteed that the `applies` method will called on a dataset before the `keep` - method is called on any of the frames inside. - """ - - def applies(self, dcm_wrp) -> bool: - """Returns true if the filter should be applied to a dataset""" - return True - - def keep(self, frame_data) -> bool: - """Return true if the frame should be kept""" - raise NotImplementedError - - -class FilterMultiStack(FrameFilter): - """Filter out all but one `StackID`""" - - def __init__(self, keep_id=None): - self._keep_id = str(keep_id) if keep_id is not None else None - - def applies(self, dcm_wrp) -> bool: - first_fcs = dcm_wrp.frames[0].get('FrameContentSequence', (None,))[0] - if first_fcs is None or not hasattr(first_fcs, 'StackID'): - return False - stack_ids = {frame.FrameContentSequence[0].StackID for frame in dcm_wrp.frames} - if self._keep_id is not None: - if self._keep_id not in stack_ids: - raise WrapperError('Explicitly requested StackID not found') - self._selected = self._keep_id - if len(stack_ids) > 1: - if self._keep_id is None: - try: - sids = [int(x) for x in stack_ids] - except: - self._selected = dcm_wrp.frames[0].FrameContentSequence[0].StackID - else: - self._selected = str(min(sids)) - warnings.warn( - 'A multi-stack file was passed without an explicit filter, ' - f'using StackID = {self._selected}' - ) - return True - return False - - def keep(self, frame) -> bool: - return frame.FrameContentSequence[0].StackID == self._selected - - -class FilterDwiIso(FrameFilter): - """Filter out derived ISOTROPIC frames from DWI series""" - - def applies(self, dcm_wrp) -> bool: - if not hasattr(dcm_wrp.frames[0], 'MRDiffusionSequence'): - return False - diff_dirs = { - f.MRDiffusionSequence[0].get('DiffusionDirectionality') for f in dcm_wrp.frames - } - if len(diff_dirs) > 1 and 'ISOTROPIC' in diff_dirs: - warnings.warn('Derived images found and removed') - return True - return False - - def keep(self, frame) -> bool: - return frame.MRDiffusionSequence[0].DiffusionDirectionality != 'ISOTROPIC' - - -DEFUALT_FRAME_FILTERS = (FilterMultiStack(), FilterDwiIso()) - - -class MultiframeWrapper(Wrapper): - """Wrapper for Enhanced MR Storage SOP Class - - Tested with Philips' Enhanced DICOM implementation. - - The specification for the Enhanced MR image IOP / SOP began life as `DICOM - supplement 49 `_, - but as of 2016 it is part of the standard. In particular see: - - * `A.36 Enhanced MR Information Object Definitions - `_; - * `C.7.6.16 Multi-Frame Functional Groups Module - `_; - * `C.7.6.17 Multi-Frame Dimension Module - `_. - - Attributes - ---------- - is_multiframe : boolean - Identifies `dcmdata` as multi-frame - frames : sequence - A sequence of ``dicom.dataset.Dataset`` objects populated by the - ``dicom.dataset.Dataset.PerFrameFunctionalGroupsSequence`` attribute - shared : object - The first (and only) ``dicom.dataset.Dataset`` object from a - ``dicom.dataset.Dataset.SharedFunctionalgroupSequence``. - - Methods - ------- - vendor(self) - frame_order(self) - image_shape(self) - image_orient_patient(self) - voxel_sizes(self) - image_position(self) - series_signature(self) - scale_factors(self) - get_data(self) - """ - - is_multiframe = True - - def __init__(self, dcm_data, frame_filters=None): - """Initializes MultiframeWrapper - - Parameters - ---------- - dcm_data : object - object should allow 'get' and '__getitem__' access. Usually this - will be a ``dicom.dataset.Dataset`` object resulting from reading a - DICOM file. - - frame_filters : Iterable of FrameFilter - defines which frames inside the dataset should be ignored. If None then - `dicomwrappers.DEFAULT_FRAME_FILTERS` will be used. - """ - Wrapper.__init__(self, dcm_data) - self.frames = dcm_data.get('PerFrameFunctionalGroupsSequence') - try: - self.frames[0] - except TypeError: - raise WrapperError('PerFrameFunctionalGroupsSequence is empty.') - try: - self.shared = dcm_data.get('SharedFunctionalGroupsSequence')[0] - except TypeError: - raise WrapperError('SharedFunctionalGroupsSequence is empty.') - # Apply frame filters one at a time in the order provided - if frame_filters is None: - frame_filters = DEFUALT_FRAME_FILTERS - frame_filters = [filt for filt in frame_filters if filt.applies(self)] - for filt in frame_filters: - self.frames = [f for f in self.frames if filt.keep(f)] - # Make sure there is only one StackID remaining - first_fcs = self.frames[0].get('FrameContentSequence', (None,))[0] - if first_fcs is not None and hasattr(first_fcs, 'StackID'): - if len({frame.FrameContentSequence[0].StackID for frame in self.frames}) > 1: - raise WrapperError('More than one StackID remains after filtering') - # Try to determine slice order and minimal image position patient - self._frame_slc_ord = self._ipp = self._slice_spacing = None - try: - frame_ipps = [f.PlanePositionSequence[0].ImagePositionPatient for f in self.frames] - except AttributeError: - try: - frame_ipps = [self.shared.PlanePositionSequence[0].ImagePositionPatient] - except AttributeError: - frame_ipps = None - if frame_ipps is not None and all(ipp is not None for ipp in frame_ipps): - frame_ipps = [np.array(list(map(float, ipp))) for ipp in frame_ipps] - frame_slc_pos = [np.inner(ipp, self.slice_normal) for ipp in frame_ipps] - rnd_slc_pos = np.round(frame_slc_pos, 4) - uniq_slc_pos = np.unique(rnd_slc_pos) - pos_ord_map = dict(zip(uniq_slc_pos, np.argsort(uniq_slc_pos))) - self._frame_slc_ord = [pos_ord_map[pos] for pos in rnd_slc_pos] - if len(self._frame_slc_ord) > 1: - self._slice_spacing = ( - frame_slc_pos[self._frame_slc_ord[1]] - frame_slc_pos[self._frame_slc_ord[0]] - ) - self._ipp = frame_ipps[np.argmin(frame_slc_pos)] - self._frame_indices = None - - @cached_property - def vendor(self): - """The vendor of the instrument that produced the DICOM""" - vendor = super().vendor - if vendor is not None: - return vendor - vendor = vendor_from_private(self.shared) - if vendor is not None: - return vendor - return vendor_from_private(self.frames[0]) - - @cached_property - def frame_order(self): - """The ordering of frames to make nD array""" - if self._frame_indices is None: - _ = self.image_shape - return np.lexsort(self._frame_indices.T) - - @cached_property - def image_shape(self): - """The array shape as it will be returned by ``get_data()`` - - The shape is determined by the *Rows* DICOM attribute, *Columns* - DICOM attribute, and the set of frame indices given by the - *FrameContentSequence[0].DimensionIndexValues* DICOM attribute of each - element in the *PerFrameFunctionalGroupsSequence*. The first two - axes of the returned shape correspond to the rows, and columns - respectively. The remaining axes correspond to those of the frame - indices with order preserved. - - What each axis in the frame indices refers to is given by the - corresponding entry in the *DimensionIndexSequence* DICOM attribute. - **WARNING**: Any axis referring to the *StackID* DICOM attribute will - have been removed from the frame indices in determining the shape. This - is because only a file containing a single stack is currently allowed by - this wrapper. - - References - ---------- - * C.7.6.16 Multi-Frame Functional Groups Module: - http://dicom.nema.org/medical/dicom/current/output/pdf/part03.pdf#sect_C.7.6.16 - * C.7.6.17 Multi-Frame Dimension Module: - http://dicom.nema.org/medical/dicom/current/output/pdf/part03.pdf#sect_C.7.6.17 - * Diagram of DimensionIndexSequence and DimensionIndexValues: - http://dicom.nema.org/medical/dicom/current/output/pdf/part03.pdf#figure_C.7.6.17-1 - """ - rows, cols = self.get('Rows'), self.get('Columns') - if None in (rows, cols): - raise WrapperError('Rows and/or Columns are empty.') - # Check number of frames and handle single frame files - n_frames = len(self.frames) - if n_frames == 1: - self._frame_indices = np.array([[0]], dtype=np.int64) - return (rows, cols) - # Initialize array of frame indices - try: - frame_indices = np.array( - [frame.FrameContentSequence[0].DimensionIndexValues for frame in self.frames] - ) - except AttributeError: - raise WrapperError("Can't find frame 'DimensionIndexValues'") - if len(frame_indices.shape) == 1: - frame_indices = frame_indices.reshape(frame_indices.shape + (1,)) - # Determine the shape and which indices to use - shape = [rows, cols] - curr_parts = n_frames - frames_per_part = 1 - del_indices = {} - dim_seq = [dim.DimensionIndexPointer for dim in self.get('DimensionIndexSequence')] - stackpos_tag = pydicom.datadict.tag_for_keyword('InStackPositionNumber') - slice_dim_idx = dim_seq.index(stackpos_tag) - for row_idx, row in enumerate(frame_indices.T): - unique = np.unique(row) - count = len(unique) - if curr_parts == 1 or (count == 1 and row_idx != slice_dim_idx): - del_indices[row_idx] = count - continue - # Replace slice indices with order determined from slice positions along normal - if row_idx == slice_dim_idx: - if len(shape) > 2: - raise WrapperError('Non-singular index precedes the slice index') - row = self._frame_slc_ord - frame_indices.T[row_idx, :] = row - unique = np.unique(row) - if len(unique) != count: - raise WrapperError("Number of slice indices and positions don't match") - elif count == n_frames: - if shape[-1] == 'remaining': - raise WrapperError('At most one index have ambiguous size') - shape.append('remaining') - continue - new_parts, leftover = divmod(curr_parts, count) - expected = new_parts * frames_per_part - if leftover != 0 or any(np.count_nonzero(row == val) != expected for val in unique): - if row_idx == slice_dim_idx: - raise WrapperError('Missing slices from multiframe') - del_indices[row_idx] = count - continue - if shape[-1] == 'remaining': - shape[-1] = new_parts - frames_per_part *= shape[-1] - new_parts = 1 - frames_per_part *= count - shape.append(count) - curr_parts = new_parts - if shape[-1] == 'remaining': - if curr_parts > 1: - shape[-1] = curr_parts - curr_parts = 1 - else: - del_indices[len(shape)] = 1 - shape = shape[:-1] - if del_indices: - if curr_parts > 1: - ns_failed = [k for k, v in del_indices.items() if v != 1] - if len(ns_failed) > 1: - # If some indices weren't used yet but we still have unaccounted for - # partitions, try combining indices into single tuple and using that - tup_dtype = np.dtype(','.join(['I'] * len(ns_failed))) - row = [tuple(x for x in vals) for vals in frame_indices[:, ns_failed]] - row = np.array(row, dtype=tup_dtype) - frame_indices = np.delete(frame_indices, np.array(list(del_indices.keys())), axis=1) - if curr_parts > 1 and len(ns_failed) > 1: - unique = np.unique(row, axis=0) - count = len(unique) - new_parts, rem = divmod(curr_parts, count) - allowed_val_counts = [new_parts * frames_per_part, n_frames] - if rem == 0 and all( - np.count_nonzero(row == val) in allowed_val_counts for val in unique - ): - shape.append(count) - curr_parts = new_parts - ord_vals = np.argsort(unique) - order = {tuple(unique[i]): ord_vals[i] for i in range(count)} - ord_row = np.array([order[tuple(v)] for v in row]) - frame_indices = np.hstack( - [frame_indices, np.array(ord_row).reshape((n_frames, 1))] - ) - if curr_parts > 1: - raise WrapperError('Unable to determine sorting of final dimension(s)') - # Store frame indices - self._frame_indices = frame_indices - return tuple(shape) - - @cached_property - def image_orient_patient(self): - """ - Note that this is _not_ LR flipped - """ - try: - iop = self.shared.PlaneOrientationSequence[0].ImageOrientationPatient - except AttributeError: - try: - iop = self.frames[0].PlaneOrientationSequence[0].ImageOrientationPatient - except AttributeError: - raise WrapperError('Not enough information for image_orient_patient') - if iop is None: - return None - iop = np.array(list(map(float, iop))) - return np.array(iop).reshape(2, 3).T - - @cached_property - def voxel_sizes(self): - """Get i, j, k voxel sizes""" - try: - pix_measures = self.shared.PixelMeasuresSequence[0] - except AttributeError: - try: - pix_measures = self.frames[0].PixelMeasuresSequence[0] - except AttributeError: - raise WrapperError('Not enough data for pixel spacing') - pix_space = pix_measures.PixelSpacing - if self._slice_spacing is not None: - zs = self._slice_spacing - else: - try: - zs = pix_measures.SliceThickness - except AttributeError: - zs = self.get('SpacingBetweenSlices') - if zs is None: - raise WrapperError('Not enough data for slice thickness') - # Ensure values are float rather than Decimal - return tuple(map(float, list(pix_space) + [zs])) - - @property - def image_position(self): - if self._ipp is None: - raise WrapperError('Not enough information for image_position_patient') - return self._ipp - - @cached_property - def series_signature(self): - signature = {} - eq = operator.eq - for key in ('SeriesInstanceUID', 'SeriesNumber', 'ImageType'): - signature[key] = (self.get(key), eq) - signature['image_shape'] = (self.image_shape, eq) - signature['iop'] = (self.image_orient_patient, none_or_close) - signature['vox'] = (self.voxel_sizes, none_or_close) - return signature - - @cached_property - def scale_factors(self): - """Return `(2, N)` array of slope/intercept pairs - - If there is a single global scale factor then `N` will be one, otherwise it will - be the number of frames - """ - # Look for shared / global RWV scale factor first - shared_scale = self._get_rwv_scale_factor(self.shared) - if shared_scale is not None: - return np.array([shared_scale]) - shared_scale = self._get_rwv_scale_factor(self.dcm_data) - if shared_scale is not None: - return np.array([shared_scale]) - # Try pulling out best scale factors from each individual frame - frame_scales = [self._get_best_scale_factor(f) for f in self.frames] - if any(s is not None for s in frame_scales): - if any(s is None for s in frame_scales): - if self.vendor == Vendor.PHILIPS: - warnings.warn( - 'Unable to find Philips private scale factor, cross-series comparisons may be invalid' - ) - frame_scales = [s if s is not None else (1, 0) for s in frame_scales] - if all(s == frame_scales[0] for s in frame_scales[1:]): - return np.array([frame_scales[0]]) - return np.array(frame_scales)[self.frame_order] - # Finally look for shared non-RWV scale factors - shared_scale = self._get_best_scale_factor(self.shared) - if shared_scale is not None: - return np.array([shared_scale]) - shared_scale = self._get_best_scale_factor(self.dcm_data) - if shared_scale is None: - if self.vendor == Vendor.PHILIPS: - warnings.warn( - 'Unable to find Philips private scale factor, cross-series comparisons may be invalid' - ) - shared_scale = (1, 0) - return np.array([shared_scale]) - - def get_unscaled_data(self): - shape = self.image_shape - if shape is None: - raise WrapperError('No valid information for image shape') - data = self.get_pixel_array() - # Roll frames axis to last and reorder - if len(data.shape) > 2: - data = data.transpose((1, 2, 0))[..., self.frame_order] - return data.reshape(shape, order='F') - - def _scale_data(self, data): - scale_factors = self.scale_factors - if scale_factors.shape[0] == 1: - scale, offset = scale_factors[0] - return self._apply_scale_offset(data, scale, offset) - orig_shape = data.shape - data = data.reshape(data.shape[:2] + (len(self.frames),)) - return (data * scale_factors[:, 0] + scale_factors[:, 1]).reshape(orig_shape) - - -class SiemensWrapper(Wrapper): - """Wrapper for Siemens format DICOMs - - Adds attributes: - - * csa_header : mapping - * b_matrix : (3,3) array - * q_vector : (3,) array - """ - - is_csa = True - - def __init__(self, dcm_data, csa_header=None): - """Initialize Siemens wrapper - - The Siemens-specific information is in the `csa_header`, either - passed in here, or read from the input `dcm_data`. - - Parameters - ---------- - dcm_data : object - object should allow 'get' and '__getitem__' access. If `csa_header` - is None, it should also be possible to extract a CSA header from - `dcm_data`. Usually this will be a ``dicom.dataset.Dataset`` object - resulting from reading a DICOM file. - csa_header : None or mapping, optional - mapping giving values for Siemens CSA image sub-header. If - None, we try and read the CSA information from `dcm_data`. - If this fails, we fall back to an empty dict. - """ - super().__init__(dcm_data) - if dcm_data is None: - dcm_data = {} - self.dcm_data = dcm_data - if csa_header is None: - csa_header = csar.get_csa_header(dcm_data) - if csa_header is None: - csa_header = {} - self.csa_header = csa_header - - @cached_property - def vendor(self): - """The vendor of the instrument that produced the DICOM""" - return Vendor.SIEMENS - - @cached_property - def slice_normal(self): - # The std_slice_normal comes from the cross product of the directions - # in the ImageOrientationPatient - std_slice_normal = super().slice_normal - csa_slice_normal = csar.get_slice_normal(self.csa_header) - if std_slice_normal is None and csa_slice_normal is None: - return None - elif std_slice_normal is None: - return np.array(csa_slice_normal) - elif csa_slice_normal is None: - return std_slice_normal - else: - # Make sure the two normals are very close to parallel unit vectors - dot_prod = np.dot(csa_slice_normal, std_slice_normal) - assert np.allclose(np.fabs(dot_prod), 1.0, atol=1e-5) - # Use the slice normal computed with the cross product as it will - # always be the most orthogonal, but take the sign from the CSA - # slice normal - if dot_prod < 0: - return -std_slice_normal - else: - return std_slice_normal - - @cached_property - def series_signature(self): - """Add ICE dims from CSA header to signature""" - signature = super().series_signature - ice = csar.get_ice_dims(self.csa_header) - if ice is not None: - ice = ice[:6] + ice[8:9] - signature['ICE_Dims'] = (ice, operator.eq) - return signature - - @cached_property - def b_matrix(self): - """Get DWI B matrix referring to voxel space - - Parameters - ---------- - None - - Returns - ------- - B : (3,3) array or None - B matrix in *voxel* orientation space. Returns None if this is - not a Siemens header with the required information. We return - None if this is a b0 acquisition - """ - hdr = self.csa_header - # read B matrix as recorded in CSA header. This matrix refers to - # the space of the DICOM patient coordinate space. - B = csar.get_b_matrix(hdr) - if B is None: # may be not diffusion or B0 image - bval_requested = csar.get_b_value(hdr) - if bval_requested is None: - return None - if bval_requested != 0: - raise csar.CSAError('No B matrix and b value != 0') - return np.zeros((3, 3)) - # rotation from voxels to DICOM PCS, inverted to give the rotation - # from DPCS to voxels. Because this is an orthonormal matrix, its - # transpose is its inverse - R = self.rotation_matrix.T - # because B results from V dot V.T, the rotation B is given by R dot - # V dot V.T dot R.T == R dot B dot R.T - B_vox = np.dot(R, np.dot(B, R.T)) - # fix presumed rounding errors in the B matrix by making it positive - # semi-definite. - return nearest_pos_semi_def(B_vox) - - @cached_property - def q_vector(self): - """Get DWI q vector referring to voxel space - - Parameters - ---------- - None - - Returns - ------- - q: (3,) array - Estimated DWI q vector in *voxel* orientation space. Returns - None if this is not (detectably) a DWI - """ - B = self.b_matrix - if B is None: - return None - # We've enforced more or less positive semi definite with the - # b_matrix routine - return B2q(B, tol=1e-8) - - -class MosaicWrapper(SiemensWrapper): - """Class for Siemens mosaic format data - - Mosaic format is a way of storing a 3D image in a 2D slice - and - it's as simple as you'd imagine it would be - just storing the slices - in a mosaic similar to a light-box print. - - We need to allow for this when getting the data and (because of an - idiosyncrasy in the way Siemens stores the images) calculating the - position of the first voxel. - - Adds attributes: - - * n_mosaic : int - * mosaic_size : int - """ - - is_mosaic = True - - def __init__(self, dcm_data, csa_header=None, n_mosaic=None): - """Initialize Siemens Mosaic wrapper - - The Siemens-specific information is in the `csa_header`, either - passed in here, or read from the input `dcm_data`. - - Parameters - ---------- - dcm_data : object - object should allow 'get' and '__getitem__' access. If `csa_header` - is None, it should also be possible for to extract a CSA header from - `dcm_data`. Usually this will be a ``dicom.dataset.Dataset`` object - resulting from reading a DICOM file. A dict should also work. - csa_header : None or mapping, optional - mapping giving values for Siemens CSA image sub-header. - n_mosaic : None or int, optional - number of images in mosaic. If None, try to get this number - from `csa_header`. If this fails, raise an error - """ - SiemensWrapper.__init__(self, dcm_data, csa_header) - if n_mosaic is None: - try: - n_mosaic = csar.get_n_mosaic(self.csa_header) - except KeyError: - pass - if n_mosaic is None or n_mosaic == 0: - raise WrapperError( - 'No valid mosaic number in CSA header; is this really Siemens mosiac data?' - ) - self.n_mosaic = n_mosaic - self.mosaic_size = int(np.ceil(np.sqrt(n_mosaic))) - - @cached_property - def image_shape(self): - """Return image shape as returned by ``get_data()``""" - # reshape pixel slice array back from mosaic - rows = self.get('Rows') - cols = self.get('Columns') - if None in (rows, cols): - return None - return (rows // self.mosaic_size, cols // self.mosaic_size, self.n_mosaic) - - @cached_property - def image_position(self): - """Return position of first voxel in data block - - Adjusts Siemens mosaic position vector for bug in mosaic format - position. See ``dicom_mosaic`` in doc/theory for details. - - Parameters - ---------- - None - - Returns - ------- - img_pos : (3,) array - position in mm of voxel (0,0,0) in Mosaic array - """ - ipp = super().image_position - # mosaic image size - md_rows, md_cols = (self.get('Rows'), self.get('Columns')) - iop = self.image_orient_patient - pix_spacing = self.get('PixelSpacing') - if any(x is None for x in (ipp, md_rows, md_cols, iop, pix_spacing)): - return None - # PixelSpacing values are python Decimal in pydicom 0.9.7 - pix_spacing = np.array(list(map(float, pix_spacing))) - # size of mosaic array before rearranging to 3D. - md_rc = np.array([md_rows, md_cols]) - # size of slice array after reshaping to 3D - rd_rc = md_rc / self.mosaic_size - # apply algorithm for undoing mosaic translation error - see - # ``dicom_mosaic`` doc - vox_trans_fixes = (md_rc - rd_rc) / 2 - # flip IOP field to refer to rows then columns index change - - # see dicom_orientation doc - Q = np.fliplr(iop) * pix_spacing - return ipp + np.dot(Q, vox_trans_fixes[:, None]).ravel() - - def get_unscaled_data(self): - """Get scaled image data from DICOMs - - Resorts data block from mosaic to 3D - - Returns - ------- - data : array - array with data as scaled from any scaling in the DICOM - fields. - - Notes - ----- - The apparent image in the DICOM file is a 2D array that consists of - blocks, that are the output 2D slices. Let's call the original array - the *slab*, and the contained slices *slices*. The slices are of - pixel dimension ``n_slice_rows`` x ``n_slice_cols``. The slab is of - pixel dimension ``n_slab_rows`` x ``n_slab_cols``. Because the - arrangement of blocks in the slab is defined as being square, the - number of blocks per slab row and slab column is the same. Let - ``n_blocks`` be the number of blocks contained in the slab. There is - also ``n_slices`` - the number of slices actually collected, some - number <= ``n_blocks``. We have the value ``n_slices`` from the - 'NumberOfImagesInMosaic' field of the Siemens private (CSA) header. - ``n_row_blocks`` and ``n_col_blocks`` are therefore given by - ``ceil(sqrt(n_slices))``, and ``n_blocks`` is ``n_row_blocks ** 2``. - Also ``n_slice_rows == n_slab_rows / n_row_blocks``, etc. Using these - numbers we can therefore reconstruct the slices from the 2D DICOM pixel - array. - """ - shape = self.image_shape - if shape is None: - raise WrapperError('No valid information for image shape') - n_slice_rows, n_slice_cols, n_mosaic = shape - n_slab_rows = self.mosaic_size - n_blocks = n_slab_rows**2 - data = self.get_pixel_array() - v4 = data.reshape(n_slab_rows, n_slice_rows, n_slab_rows, n_slice_cols) - # move the mosaic dims to the end - v4 = v4.transpose((1, 3, 0, 2)) - # pool mosaic-generated dims - v3 = v4.reshape((n_slice_rows, n_slice_cols, n_blocks)) - # delete any padding slices - return v3[..., :n_mosaic] - - -def none_or_close(val1, val2, rtol=1e-5, atol=1e-6): - """Match if `val1` and `val2` are both None, or are close - - Parameters - ---------- - val1 : None or array-like - val2 : None or array-like - rtol : float, optional - Relative tolerance; see ``np.allclose`` - atol : float, optional - Absolute tolerance; see ``np.allclose`` - - Returns - ------- - tf : bool - True iff (both `val1` and `val2` are None) or (`val1` and `val2` - are close arrays, as detected by ``np.allclose`` with parameters - `rtol` and `atal`). - - Examples - -------- - >>> none_or_close(None, None) - True - >>> none_or_close(1, None) - False - >>> none_or_close(None, 1) - False - >>> none_or_close([1,2], [1,2]) - True - >>> none_or_close([0,1], [0,2]) - False - """ - if val1 is None and val2 is None: - return True - if val1 is None or val2 is None: - return False - return np.allclose(val1, val2, rtol, atol) diff --git a/nibabel/nicom/dwiparams.py b/nibabel/nicom/dwiparams.py deleted file mode 100644 index 5930e96f91..0000000000 --- a/nibabel/nicom/dwiparams.py +++ /dev/null @@ -1,159 +0,0 @@ -"""Process diffusion imaging parameters - -* ``q`` is a vector in Q space -* ``b`` is a b value -* ``g`` is the unit vector along the direction of q (the gradient - direction) - -Thus: - - b = norm(q) - - g = q / norm(q) - -(``norm(q)`` is the Euclidean norm of ``q``) - -The B matrix ``B`` is a symmetric positive semi-definite matrix. If -``q_est`` is the closest q vector equivalent to the B matrix, then: - - B ~ (q_est . q_est.T) / norm(q_est) -""" - -import numpy as np -import numpy.linalg as npl - - -def B2q(B, tol=None): - """Estimate q vector from input B matrix `B` - - We require that the input `B` is symmetric positive definite. - - Because the solution is a square root, the sign of the returned - vector is arbitrary. We set the vector to have a positive x - component by convention. - - Parameters - ---------- - B : (3,3) array-like - B matrix - symmetric. We do not check the symmetry. - tol : None or float - absolute tolerance below which to consider eigenvalues of the B - matrix to be small enough not to worry about them being negative, - in check for positive semi-definite-ness. None (default) results - in a fairly tight numerical threshold proportional to the maximum - eigenvalue - - Returns - ------- - q : (3,) vector - Estimated q vector from B matrix `B` - """ - B = np.asarray(B) - if not np.allclose(B - B.T, 0): - raise ValueError('B matrix is not symmetric enough') - w, v = npl.eigh(B) - if tol is None: - tol = np.abs(w.max()) * B.shape[0] * np.finfo(w.dtype).eps - non_trivial = np.abs(w) > tol - if np.any(w[non_trivial] < 0): - raise ValueError('B not positive semi-definite') - inds = np.argsort(w)[::-1] - max_ind = inds[0] - vector = v[:, max_ind] - # because the factor is a sqrt, the sign of the vector is arbitrary. - # We arbitrarily set it to have a positive x value. - if vector[0] < 0: - vector *= -1 - return vector * w[max_ind] - - -def nearest_pos_semi_def(B): - """Least squares positive semi-definite tensor estimation - - Reference: Niethammer M, San Jose Estepar R, Bouix S, Shenton M, - Westin CF. On diffusion tensor estimation. Conf Proc IEEE Eng Med - Biol Soc. 2006;1:2622-5. PubMed PMID: 17946125; PubMed Central - PMCID: PMC2791793. - - Parameters - ---------- - B : (3,3) array-like - B matrix - symmetric. We do not check the symmetry. - - Returns - ------- - npds : (3,3) array - Estimated nearest positive semi-definite array to matrix `B`. - - Examples - -------- - >>> B = np.diag([1, 1, -1]) - >>> nearest_pos_semi_def(B) - array([[0.75, 0. , 0. ], - [0. , 0.75, 0. ], - [0. , 0. , 0. ]]) - """ - B = np.asarray(B) - vals, vecs = npl.eigh(B) - # indices of eigenvalues in descending order - inds = np.argsort(vals)[::-1] - vals = vals[inds] - cardneg = np.sum(vals < 0) - if cardneg == 0: - return B - if cardneg == 3: - return np.zeros((3, 3)) - lam1a, lam2a, lam3a = vals - scalers = np.zeros((3,)) - if cardneg == 2: - b112 = np.max([0, lam1a + (lam2a + lam3a) / 3.0]) - scalers[0] = b112 - elif cardneg == 1: - lam1b = lam1a + 0.25 * lam3a - lam2b = lam2a + 0.25 * lam3a - if lam1b >= 0 and lam2b >= 0: - scalers[:2] = lam1b, lam2b - else: # one of the lam1b, lam2b is < 0 - if lam2b < 0: - b111 = np.max([0, lam1a + (lam2a + lam3a) / 3.0]) - scalers[0] = b111 - if lam1b < 0: - b221 = np.max([0, lam2a + (lam1a + lam3a) / 3.0]) - scalers[1] = b221 - # resort the scalers to match the original vecs - scalers = scalers[np.argsort(inds)] - return np.dot(vecs, np.dot(np.diag(scalers), vecs.T)) - - -def q2bg(q_vector, tol=1e-5): - """Return b value and q unit vector from q vector `q_vector` - - Parameters - ---------- - q_vector : (3,) array-like - q vector - tol : float, optional - q vector L2 norm below which `q_vector` considered to be `b_value` of - zero, and therefore `g_vector` also considered to zero. - - Returns - ------- - b_value : float - L2 Norm of `q_vector` or 0 if L2 norm < `tol` - g_vector : shape (3,) ndarray - `q_vector` / `b_value` or 0 if L2 norma < `tol` - - Examples - -------- - >>> q2bg([1, 0, 0]) - (1.0, array([1., 0., 0.])) - >>> q2bg([0, 10, 0]) - (10.0, array([0., 1., 0.])) - >>> q2bg([0, 0, 0]) - (0.0, array([0., 0., 0.])) - """ - q_vec = np.asarray(q_vector) - norm = np.sqrt(np.sum(q_vec * q_vec)) - if norm < tol: - return (0.0, np.zeros((3,))) - return norm, q_vec / norm diff --git a/nibabel/nicom/structreader.py b/nibabel/nicom/structreader.py deleted file mode 100644 index 086a463d2e..0000000000 --- a/nibabel/nicom/structreader.py +++ /dev/null @@ -1,113 +0,0 @@ -"""Stream-like reader for packed data""" - -from struct import Struct - -_ENDIAN_CODES = '@=<>!' - - -class Unpacker: - """Class to unpack values from buffer object - - The buffer object is usually a string. Caches compiled :mod:`struct` - format strings so that repeated unpacking with the same format - string should be faster than using ``struct.unpack`` directly. - - Examples - -------- - >>> a = b'1234567890' - >>> upk = Unpacker(a) - >>> upk.unpack('2s') == (b'12',) - True - >>> upk.unpack('2s') == (b'34',) - True - >>> upk.ptr - 4 - >>> upk.read(3) == b'567' - True - >>> upk.ptr - 7 - """ - - def __init__(self, buf, ptr=0, endian=None): - """Initialize unpacker - - Parameters - ---------- - buf : buffer - object implementing buffer protocol (e.g. str) - ptr : int, optional - offset at which to begin reads from `buf` - endian : None or str, optional - endian code to prepend to format, as for ``unpack`` endian - codes. None (the default) corresponds to the default - behavior of ``struct`` - assuming system endian unless you - specify the byte order specifically in the format string - passed to ``unpack`` - """ - self.buf = buf - self.ptr = ptr - self.endian = endian - self._cache = {} - - def unpack(self, fmt): - """Unpack values from contained buffer - - Unpacks values from ``self.buf`` and updates ``self.ptr`` to the - position after the read data. - - Parameters - ---------- - fmt : str - format string as for ``unpack`` - - Returns - ------- - values : tuple - values as unpacked from ``self.buf`` according to `fmt` - """ - # try and get a struct corresponding to the format string from - # the cache - pkst = self._cache.get(fmt) - if pkst is None: # struct not in cache - # if we've not got a default endian, or the format has an - # explicit endianness, then we make a new struct directly - # from the format string - if self.endian is None or fmt[0] in _ENDIAN_CODES: - pkst = Struct(fmt) - else: # we're going to modify the endianness with our - # default. - endian_fmt = self.endian + fmt - pkst = Struct(endian_fmt) - # add an entry in the cache for the modified format - # string as well as (below) the unmodified format - # string, in case we get a format string with the same - # endianness as default, but specified explicitly. - self._cache[endian_fmt] = pkst - self._cache[fmt] = pkst - values = pkst.unpack_from(self.buf, self.ptr) - self.ptr += pkst.size - return values - - def read(self, n_bytes=-1): - """Return byte string of length `n_bytes` at current position - - Returns sub-string from ``self.buf`` and updates ``self.ptr`` to the - position after the read data. - - Parameters - ---------- - n_bytes : int, optional - number of bytes to read. Can be -1 (the default) in which - case we return all the remaining bytes in ``self.buf`` - - Returns - ------- - s : byte string - """ - start = self.ptr - if n_bytes == -1: - end = len(self.buf) - else: - end = start + n_bytes - self.ptr = end - return self.buf[start:end] diff --git a/nibabel/nicom/tests/__init__.py b/nibabel/nicom/tests/__init__.py deleted file mode 100644 index ec2c5b2f38..0000000000 --- a/nibabel/nicom/tests/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -import unittest - -from nibabel.optpkg import optional_package - -pydicom, have_dicom, _ = optional_package('pydicom') - -dicom_test = unittest.skipUnless(have_dicom, 'Could not import pydicom') diff --git a/nibabel/nicom/tests/data/0.dcm b/nibabel/nicom/tests/data/0.dcm deleted file mode 100644 index 05d7c875d7..0000000000 Binary files a/nibabel/nicom/tests/data/0.dcm and /dev/null differ diff --git a/nibabel/nicom/tests/data/1.dcm b/nibabel/nicom/tests/data/1.dcm deleted file mode 100644 index 0920b60eaf..0000000000 Binary files a/nibabel/nicom/tests/data/1.dcm and /dev/null differ diff --git a/nibabel/nicom/tests/data/4d_multiframe_test.dcm b/nibabel/nicom/tests/data/4d_multiframe_test.dcm deleted file mode 100644 index 4278d140c5..0000000000 Binary files a/nibabel/nicom/tests/data/4d_multiframe_test.dcm and /dev/null differ diff --git a/nibabel/nicom/tests/data/ascconv_sample.txt b/nibabel/nicom/tests/data/ascconv_sample.txt deleted file mode 100644 index 1fd78f788f..0000000000 --- a/nibabel/nicom/tests/data/ascconv_sample.txt +++ /dev/null @@ -1,919 +0,0 @@ -### ASCCONV BEGIN ### -ulVersion = 0x14b44b6 -tSequenceFileName = ""%SiemensSeq%\ep2d_diff"" -tProtocolName = ""CBU+AF8-DTI+AF8-64D+AF8-1A"" -tReferenceImage0 = ""1.3.12.2.1107.5.2.32.35119.2010011420070434054586384"" -tReferenceImage1 = ""1.3.12.2.1107.5.2.32.35119.2010011420070721803086388"" -tReferenceImage2 = ""1.3.12.2.1107.5.2.32.35119.201001142007109937386392"" -ucScanRegionPosValid = 0x1 -ucTablePositioningMode = 0x1 -sProtConsistencyInfo.tBaselineString = ""N4_VB17A_LATEST_20090307"" -sProtConsistencyInfo.tSystemType = ""092"" -sProtConsistencyInfo.flNominalB0 = 2.89362 -sProtConsistencyInfo.flGMax = 26 -sProtConsistencyInfo.flRiseTime = 5.88 -sProtConsistencyInfo.lMaximumNofRxReceiverChannels = 18 -sGRADSPEC.sEddyCompensationX.aflAmplitude[0] = 0.00141208 -sGRADSPEC.sEddyCompensationX.aflAmplitude[1] = 0.000569241 -sGRADSPEC.sEddyCompensationX.aflAmplitude[2] = -0.000514958 -sGRADSPEC.sEddyCompensationX.aflAmplitude[3] = 0.000499075 -sGRADSPEC.sEddyCompensationX.aflAmplitude[4] = 0.000821246 -sGRADSPEC.sEddyCompensationX.aflTimeConstant[0] = 1.81531 -sGRADSPEC.sEddyCompensationX.aflTimeConstant[1] = 0.995025 -sGRADSPEC.sEddyCompensationX.aflTimeConstant[2] = 0.0492598 -sGRADSPEC.sEddyCompensationX.aflTimeConstant[3] = 0.0194645 -sGRADSPEC.sEddyCompensationX.aflTimeConstant[4] = 0.000499659 -sGRADSPEC.sEddyCompensationY.aflAmplitude[0] = 0.00112797 -sGRADSPEC.sEddyCompensationY.aflAmplitude[1] = -0.000565372 -sGRADSPEC.sEddyCompensationY.aflAmplitude[2] = -0.00182913 -sGRADSPEC.sEddyCompensationY.aflAmplitude[3] = -2.65859e-005 -sGRADSPEC.sEddyCompensationY.aflAmplitude[4] = 0.000601077 -sGRADSPEC.sEddyCompensationY.aflTimeConstant[0] = 1.09142 -sGRADSPEC.sEddyCompensationY.aflTimeConstant[1] = 0.661632 -sGRADSPEC.sEddyCompensationY.aflTimeConstant[2] = 0.446457 -sGRADSPEC.sEddyCompensationY.aflTimeConstant[3] = 0.0118729 -sGRADSPEC.sEddyCompensationY.aflTimeConstant[4] = 0.00134346 -sGRADSPEC.sEddyCompensationZ.aflAmplitude[0] = 0.00221038 -sGRADSPEC.sEddyCompensationZ.aflAmplitude[1] = 0.00592667 -sGRADSPEC.sEddyCompensationZ.aflAmplitude[2] = 0.000254437 -sGRADSPEC.sEddyCompensationZ.aflAmplitude[3] = -8.35135e-005 -sGRADSPEC.sEddyCompensationZ.aflAmplitude[4] = -4.25678e-005 -sGRADSPEC.sEddyCompensationZ.aflTimeConstant[0] = 4.32108 -sGRADSPEC.sEddyCompensationZ.aflTimeConstant[1] = 0.923398 -sGRADSPEC.sEddyCompensationZ.aflTimeConstant[2] = 0.0379209 -sGRADSPEC.sEddyCompensationZ.aflTimeConstant[3] = 0.0104227 -sGRADSPEC.sEddyCompensationZ.aflTimeConstant[4] = 0.00199944 -sGRADSPEC.bEddyCompensationValid = 1 -sGRADSPEC.sB0CompensationX.aflAmplitude[0] = -0.0494045 -sGRADSPEC.sB0CompensationX.aflAmplitude[1] = 0.0730311 -sGRADSPEC.sB0CompensationX.aflAmplitude[2] = -0.00670347 -sGRADSPEC.sB0CompensationX.aflTimeConstant[0] = 0.618983 -sGRADSPEC.sB0CompensationX.aflTimeConstant[1] = 0.341914 -sGRADSPEC.sB0CompensationX.aflTimeConstant[2] = 0.002 -sGRADSPEC.sB0CompensationY.aflAmplitude[0] = 0.136281 -sGRADSPEC.sB0CompensationY.aflAmplitude[1] = 0.0376382 -sGRADSPEC.sB0CompensationY.aflAmplitude[2] = -0.0500779 -sGRADSPEC.sB0CompensationY.aflTimeConstant[0] = 0.71999 -sGRADSPEC.sB0CompensationY.aflTimeConstant[1] = 0.00341892 -sGRADSPEC.sB0CompensationY.aflTimeConstant[2] = 0.002 -sGRADSPEC.sB0CompensationZ.aflAmplitude[0] = 0.0776537 -sGRADSPEC.sB0CompensationZ.aflAmplitude[1] = 0.0168151 -sGRADSPEC.sB0CompensationZ.aflAmplitude[2] = -0.0550622 -sGRADSPEC.sB0CompensationZ.aflTimeConstant[0] = 0.669998 -sGRADSPEC.sB0CompensationZ.aflTimeConstant[1] = 0.0213343 -sGRADSPEC.sB0CompensationZ.aflTimeConstant[2] = 0.00186002 -sGRADSPEC.bB0CompensationValid = 1 -sGRADSPEC.sCrossTermCompensationXY.aflAmplitude[0] = -0.00049613 -sGRADSPEC.sCrossTermCompensationXY.aflTimeConstant[0] = 0.562233 -sGRADSPEC.sCrossTermCompensationXZ.aflAmplitude[0] = -0.000499641 -sGRADSPEC.sCrossTermCompensationXZ.aflTimeConstant[0] = 0.693605 -sGRADSPEC.sCrossTermCompensationYX.aflAmplitude[0] = 5.35458e-005 -sGRADSPEC.sCrossTermCompensationYX.aflTimeConstant[0] = 0.598216 -sGRADSPEC.sCrossTermCompensationYZ.aflAmplitude[0] = 0.0004678 -sGRADSPEC.sCrossTermCompensationYZ.aflTimeConstant[0] = 0.705977 -sGRADSPEC.sCrossTermCompensationZX.aflAmplitude[0] = -0.000529382 -sGRADSPEC.sCrossTermCompensationZX.aflTimeConstant[0] = 0.551175 -sGRADSPEC.sCrossTermCompensationZY.aflAmplitude[0] = 8.74925e-005 -sGRADSPEC.sCrossTermCompensationZY.aflTimeConstant[0] = 0.890761 -sGRADSPEC.bCrossTermCompensationValid = 1 -sGRADSPEC.lOffsetX = -7806 -sGRADSPEC.lOffsetY = -8833 -sGRADSPEC.lOffsetZ = -2097 -sGRADSPEC.bOffsetValid = 1 -sGRADSPEC.lDelayX = 14 -sGRADSPEC.lDelayY = 14 -sGRADSPEC.lDelayZ = 10 -sGRADSPEC.bDelayValid = 1 -sGRADSPEC.flSensitivityX = 7.95149e-005 -sGRADSPEC.flSensitivityY = 7.82833e-005 -sGRADSPEC.flSensitivityZ = 9.09015e-005 -sGRADSPEC.bSensitivityValid = 1 -sGRADSPEC.flGSWDMinRiseTime = 9.88 -sGRADSPEC.alShimCurrent[0] = 867 -sGRADSPEC.alShimCurrent[1] = 80 -sGRADSPEC.alShimCurrent[2] = -61 -sGRADSPEC.alShimCurrent[3] = -4 -sGRADSPEC.alShimCurrent[4] = -16 -sGRADSPEC.bShimCurrentValid = 1 -sGRADSPEC.ucMode = 0x11 -sTXSPEC.asNucleusInfo[0].tNucleus = ""1H"" -sTXSPEC.asNucleusInfo[0].lFrequency = 123251815 -sTXSPEC.asNucleusInfo[0].bFrequencyValid = 1 -sTXSPEC.asNucleusInfo[0].flReferenceAmplitude = 384.855 -sTXSPEC.asNucleusInfo[0].bReferenceAmplitudeValid = 1 -sTXSPEC.asNucleusInfo[0].flAmplitudeCorrection = 1 -sTXSPEC.asNucleusInfo[0].bAmplitudeCorrectionValid = 1 -sTXSPEC.asNucleusInfo[0].bRFPAIndexValid = 1 -sTXSPEC.asNucleusInfo[1].bFrequencyValid = 1 -sTXSPEC.asNucleusInfo[1].bReferenceAmplitudeValid = 1 -sTXSPEC.asNucleusInfo[1].flAmplitudeCorrection = 1 -sTXSPEC.asNucleusInfo[1].bAmplitudeCorrectionValid = 1 -sTXSPEC.asNucleusInfo[1].lRFPAIndex = -1 -sTXSPEC.asNucleusInfo[1].bRFPAIndexValid = 1 -sTXSPEC.aRFPULSE[0].tName = ""ExtExciteRF"" -sTXSPEC.aRFPULSE[0].bAmplitudeValid = 0x1 -sTXSPEC.aRFPULSE[0].flAmplitude = 357.891 -sTXSPEC.aRFPULSE[1].tName = ""CSatCSatNS"" -sTXSPEC.aRFPULSE[1].bAmplitudeValid = 0x1 -sTXSPEC.aRFPULSE[1].flAmplitude = 94.871 -sTXSPEC.aRFPULSE[2].tName = ""SLoopFCSatNS"" -sTXSPEC.aRFPULSE[2].bAmplitudeValid = 0x1 -sTXSPEC.aRFPULSE[2].flAmplitude = 94.871 -sTXSPEC.lNoOfTraPulses = 3 -sTXSPEC.lBCExcitationMode = 1 -sTXSPEC.lBCSeqExcitationMode = 4 -sTXSPEC.flKDynMagnitudeMin = 0.5 -sTXSPEC.flKDynMagnitudeMax = 1.5 -sTXSPEC.flKDynMagnitudeClipLow = 1 -sTXSPEC.flKDynMagnitudeClipHigh = 1 -sTXSPEC.flKDynPhaseMax = 0.698132 -sTXSPEC.flKDynPhaseClip = 0.174533 -sTXSPEC.bKDynValid = 1 -sTXSPEC.ucRFPulseType = 0x2 -sTXSPEC.ucExcitMode = 0x1 -sTXSPEC.ucSimultaneousExcitation = 0x1 -sTXSPEC.ucBCExcitationModeValid = 0x1 -sRXSPEC.lGain = 1 -sRXSPEC.bGainValid = 1 -sRXSPEC.alDwellTime[0] = 2800 -sAdjData.uiAdjFreMode = 0x1 -sAdjData.uiAdjShimMode = 0x2 -sAdjData.uiAdjWatSupMode = 0x1 -sAdjData.uiAdjRFMapMode = 0x1 -sAdjData.uiAdjMDSMode = 0x1 -sAdjData.uiAdjTableTolerance = 0x1 -sAdjData.uiAdjProtID = 0x56 -sAdjData.uiAdjFreProtRelated = 0x1 -sAdjData.sAdjVolume.sPosition.dCor = -19.66101724 -sAdjData.sAdjVolume.sPosition.dTra = -8.81356001 -sAdjData.sAdjVolume.sNormal.dCor = 0.005235963828 -sAdjData.sAdjVolume.sNormal.dTra = 0.9999862922 -sAdjData.sAdjVolume.dThickness = 144 -sAdjData.sAdjVolume.dPhaseFOV = 230 -sAdjData.sAdjVolume.dReadoutFOV = 230 -ucEnableNoiseAdjust = 0x1 -alTR[0] = 6600000 -alTI[0] = 2500000 -lContrasts = 1 -alTE[0] = 93000 -acFlowComp[0] = 1 -lCombinedEchoes = 1 -sSliceArray.asSlice[0].sPosition.dCor = -20.03015269 -sSliceArray.asSlice[0].sPosition.dTra = -79.31259361 -sSliceArray.asSlice[0].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[0].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[0].dThickness = 2.5 -sSliceArray.asSlice[0].dPhaseFOV = 230 -sSliceArray.asSlice[0].dReadoutFOV = 230 -sSliceArray.asSlice[1].sPosition.dCor = -20.0144448 -sSliceArray.asSlice[1].sPosition.dTra = -76.31263473 -sSliceArray.asSlice[1].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[1].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[1].dThickness = 2.5 -sSliceArray.asSlice[1].dPhaseFOV = 230 -sSliceArray.asSlice[1].dReadoutFOV = 230 -sSliceArray.asSlice[2].sPosition.dCor = -19.99873691 -sSliceArray.asSlice[2].sPosition.dTra = -73.31267586 -sSliceArray.asSlice[2].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[2].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[2].dThickness = 2.5 -sSliceArray.asSlice[2].dPhaseFOV = 230 -sSliceArray.asSlice[2].dReadoutFOV = 230 -sSliceArray.asSlice[3].sPosition.dCor = -19.98302902 -sSliceArray.asSlice[3].sPosition.dTra = -70.31271698 -sSliceArray.asSlice[3].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[3].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[3].dThickness = 2.5 -sSliceArray.asSlice[3].dPhaseFOV = 230 -sSliceArray.asSlice[3].dReadoutFOV = 230 -sSliceArray.asSlice[4].sPosition.dCor = -19.96732113 -sSliceArray.asSlice[4].sPosition.dTra = -67.3127581 -sSliceArray.asSlice[4].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[4].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[4].dThickness = 2.5 -sSliceArray.asSlice[4].dPhaseFOV = 230 -sSliceArray.asSlice[4].dReadoutFOV = 230 -sSliceArray.asSlice[5].sPosition.dCor = -19.95161324 -sSliceArray.asSlice[5].sPosition.dTra = -64.31279923 -sSliceArray.asSlice[5].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[5].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[5].dThickness = 2.5 -sSliceArray.asSlice[5].dPhaseFOV = 230 -sSliceArray.asSlice[5].dReadoutFOV = 230 -sSliceArray.asSlice[6].sPosition.dCor = -19.93590535 -sSliceArray.asSlice[6].sPosition.dTra = -61.31284035 -sSliceArray.asSlice[6].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[6].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[6].dThickness = 2.5 -sSliceArray.asSlice[6].dPhaseFOV = 230 -sSliceArray.asSlice[6].dReadoutFOV = 230 -sSliceArray.asSlice[7].sPosition.dCor = -19.92019745 -sSliceArray.asSlice[7].sPosition.dTra = -58.31288147 -sSliceArray.asSlice[7].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[7].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[7].dThickness = 2.5 -sSliceArray.asSlice[7].dPhaseFOV = 230 -sSliceArray.asSlice[7].dReadoutFOV = 230 -sSliceArray.asSlice[8].sPosition.dCor = -19.90448956 -sSliceArray.asSlice[8].sPosition.dTra = -55.3129226 -sSliceArray.asSlice[8].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[8].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[8].dThickness = 2.5 -sSliceArray.asSlice[8].dPhaseFOV = 230 -sSliceArray.asSlice[8].dReadoutFOV = 230 -sSliceArray.asSlice[9].sPosition.dCor = -19.88878167 -sSliceArray.asSlice[9].sPosition.dTra = -52.31296372 -sSliceArray.asSlice[9].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[9].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[9].dThickness = 2.5 -sSliceArray.asSlice[9].dPhaseFOV = 230 -sSliceArray.asSlice[9].dReadoutFOV = 230 -sSliceArray.asSlice[10].sPosition.dCor = -19.87307378 -sSliceArray.asSlice[10].sPosition.dTra = -49.31300484 -sSliceArray.asSlice[10].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[10].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[10].dThickness = 2.5 -sSliceArray.asSlice[10].dPhaseFOV = 230 -sSliceArray.asSlice[10].dReadoutFOV = 230 -sSliceArray.asSlice[11].sPosition.dCor = -19.85736589 -sSliceArray.asSlice[11].sPosition.dTra = -46.31304597 -sSliceArray.asSlice[11].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[11].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[11].dThickness = 2.5 -sSliceArray.asSlice[11].dPhaseFOV = 230 -sSliceArray.asSlice[11].dReadoutFOV = 230 -sSliceArray.asSlice[12].sPosition.dCor = -19.841658 -sSliceArray.asSlice[12].sPosition.dTra = -43.31308709 -sSliceArray.asSlice[12].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[12].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[12].dThickness = 2.5 -sSliceArray.asSlice[12].dPhaseFOV = 230 -sSliceArray.asSlice[12].dReadoutFOV = 230 -sSliceArray.asSlice[13].sPosition.dCor = -19.8259501 -sSliceArray.asSlice[13].sPosition.dTra = -40.31312821 -sSliceArray.asSlice[13].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[13].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[13].dThickness = 2.5 -sSliceArray.asSlice[13].dPhaseFOV = 230 -sSliceArray.asSlice[13].dReadoutFOV = 230 -sSliceArray.asSlice[14].sPosition.dCor = -19.81024221 -sSliceArray.asSlice[14].sPosition.dTra = -37.31316934 -sSliceArray.asSlice[14].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[14].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[14].dThickness = 2.5 -sSliceArray.asSlice[14].dPhaseFOV = 230 -sSliceArray.asSlice[14].dReadoutFOV = 230 -sSliceArray.asSlice[15].sPosition.dCor = -19.79453432 -sSliceArray.asSlice[15].sPosition.dTra = -34.31321046 -sSliceArray.asSlice[15].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[15].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[15].dThickness = 2.5 -sSliceArray.asSlice[15].dPhaseFOV = 230 -sSliceArray.asSlice[15].dReadoutFOV = 230 -sSliceArray.asSlice[16].sPosition.dCor = -19.77882643 -sSliceArray.asSlice[16].sPosition.dTra = -31.31325158 -sSliceArray.asSlice[16].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[16].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[16].dThickness = 2.5 -sSliceArray.asSlice[16].dPhaseFOV = 230 -sSliceArray.asSlice[16].dReadoutFOV = 230 -sSliceArray.asSlice[17].sPosition.dCor = -19.76311854 -sSliceArray.asSlice[17].sPosition.dTra = -28.31329271 -sSliceArray.asSlice[17].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[17].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[17].dThickness = 2.5 -sSliceArray.asSlice[17].dPhaseFOV = 230 -sSliceArray.asSlice[17].dReadoutFOV = 230 -sSliceArray.asSlice[18].sPosition.dCor = -19.74741065 -sSliceArray.asSlice[18].sPosition.dTra = -25.31333383 -sSliceArray.asSlice[18].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[18].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[18].dThickness = 2.5 -sSliceArray.asSlice[18].dPhaseFOV = 230 -sSliceArray.asSlice[18].dReadoutFOV = 230 -sSliceArray.asSlice[19].sPosition.dCor = -19.73170276 -sSliceArray.asSlice[19].sPosition.dTra = -22.31337495 -sSliceArray.asSlice[19].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[19].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[19].dThickness = 2.5 -sSliceArray.asSlice[19].dPhaseFOV = 230 -sSliceArray.asSlice[19].dReadoutFOV = 230 -sSliceArray.asSlice[20].sPosition.dCor = -19.71599486 -sSliceArray.asSlice[20].sPosition.dTra = -19.31341608 -sSliceArray.asSlice[20].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[20].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[20].dThickness = 2.5 -sSliceArray.asSlice[20].dPhaseFOV = 230 -sSliceArray.asSlice[20].dReadoutFOV = 230 -sSliceArray.asSlice[21].sPosition.dCor = -19.70028697 -sSliceArray.asSlice[21].sPosition.dTra = -16.3134572 -sSliceArray.asSlice[21].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[21].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[21].dThickness = 2.5 -sSliceArray.asSlice[21].dPhaseFOV = 230 -sSliceArray.asSlice[21].dReadoutFOV = 230 -sSliceArray.asSlice[22].sPosition.dCor = -19.68457908 -sSliceArray.asSlice[22].sPosition.dTra = -13.31349832 -sSliceArray.asSlice[22].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[22].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[22].dThickness = 2.5 -sSliceArray.asSlice[22].dPhaseFOV = 230 -sSliceArray.asSlice[22].dReadoutFOV = 230 -sSliceArray.asSlice[23].sPosition.dCor = -19.66887119 -sSliceArray.asSlice[23].sPosition.dTra = -10.31353945 -sSliceArray.asSlice[23].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[23].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[23].dThickness = 2.5 -sSliceArray.asSlice[23].dPhaseFOV = 230 -sSliceArray.asSlice[23].dReadoutFOV = 230 -sSliceArray.asSlice[24].sPosition.dCor = -19.6531633 -sSliceArray.asSlice[24].sPosition.dTra = -7.313580571 -sSliceArray.asSlice[24].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[24].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[24].dThickness = 2.5 -sSliceArray.asSlice[24].dPhaseFOV = 230 -sSliceArray.asSlice[24].dReadoutFOV = 230 -sSliceArray.asSlice[25].sPosition.dCor = -19.63745541 -sSliceArray.asSlice[25].sPosition.dTra = -4.313621695 -sSliceArray.asSlice[25].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[25].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[25].dThickness = 2.5 -sSliceArray.asSlice[25].dPhaseFOV = 230 -sSliceArray.asSlice[25].dReadoutFOV = 230 -sSliceArray.asSlice[26].sPosition.dCor = -19.62174752 -sSliceArray.asSlice[26].sPosition.dTra = -1.313662818 -sSliceArray.asSlice[26].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[26].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[26].dThickness = 2.5 -sSliceArray.asSlice[26].dPhaseFOV = 230 -sSliceArray.asSlice[26].dReadoutFOV = 230 -sSliceArray.asSlice[27].sPosition.dCor = -19.60603962 -sSliceArray.asSlice[27].sPosition.dTra = 1.686296059 -sSliceArray.asSlice[27].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[27].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[27].dThickness = 2.5 -sSliceArray.asSlice[27].dPhaseFOV = 230 -sSliceArray.asSlice[27].dReadoutFOV = 230 -sSliceArray.asSlice[28].sPosition.dCor = -19.59033173 -sSliceArray.asSlice[28].sPosition.dTra = 4.686254935 -sSliceArray.asSlice[28].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[28].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[28].dThickness = 2.5 -sSliceArray.asSlice[28].dPhaseFOV = 230 -sSliceArray.asSlice[28].dReadoutFOV = 230 -sSliceArray.asSlice[29].sPosition.dCor = -19.57462384 -sSliceArray.asSlice[29].sPosition.dTra = 7.686213812 -sSliceArray.asSlice[29].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[29].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[29].dThickness = 2.5 -sSliceArray.asSlice[29].dPhaseFOV = 230 -sSliceArray.asSlice[29].dReadoutFOV = 230 -sSliceArray.asSlice[30].sPosition.dCor = -19.55891595 -sSliceArray.asSlice[30].sPosition.dTra = 10.68617269 -sSliceArray.asSlice[30].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[30].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[30].dThickness = 2.5 -sSliceArray.asSlice[30].dPhaseFOV = 230 -sSliceArray.asSlice[30].dReadoutFOV = 230 -sSliceArray.asSlice[31].sPosition.dCor = -19.54320806 -sSliceArray.asSlice[31].sPosition.dTra = 13.68613156 -sSliceArray.asSlice[31].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[31].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[31].dThickness = 2.5 -sSliceArray.asSlice[31].dPhaseFOV = 230 -sSliceArray.asSlice[31].dReadoutFOV = 230 -sSliceArray.asSlice[32].sPosition.dCor = -19.52750017 -sSliceArray.asSlice[32].sPosition.dTra = 16.68609044 -sSliceArray.asSlice[32].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[32].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[32].dThickness = 2.5 -sSliceArray.asSlice[32].dPhaseFOV = 230 -sSliceArray.asSlice[32].dReadoutFOV = 230 -sSliceArray.asSlice[33].sPosition.dCor = -19.51179228 -sSliceArray.asSlice[33].sPosition.dTra = 19.68604932 -sSliceArray.asSlice[33].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[33].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[33].dThickness = 2.5 -sSliceArray.asSlice[33].dPhaseFOV = 230 -sSliceArray.asSlice[33].dReadoutFOV = 230 -sSliceArray.asSlice[34].sPosition.dCor = -19.49608438 -sSliceArray.asSlice[34].sPosition.dTra = 22.68600819 -sSliceArray.asSlice[34].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[34].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[34].dThickness = 2.5 -sSliceArray.asSlice[34].dPhaseFOV = 230 -sSliceArray.asSlice[34].dReadoutFOV = 230 -sSliceArray.asSlice[35].sPosition.dCor = -19.48037649 -sSliceArray.asSlice[35].sPosition.dTra = 25.68596707 -sSliceArray.asSlice[35].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[35].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[35].dThickness = 2.5 -sSliceArray.asSlice[35].dPhaseFOV = 230 -sSliceArray.asSlice[35].dReadoutFOV = 230 -sSliceArray.asSlice[36].sPosition.dCor = -19.4646686 -sSliceArray.asSlice[36].sPosition.dTra = 28.68592595 -sSliceArray.asSlice[36].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[36].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[36].dThickness = 2.5 -sSliceArray.asSlice[36].dPhaseFOV = 230 -sSliceArray.asSlice[36].dReadoutFOV = 230 -sSliceArray.asSlice[37].sPosition.dCor = -19.44896071 -sSliceArray.asSlice[37].sPosition.dTra = 31.68588482 -sSliceArray.asSlice[37].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[37].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[37].dThickness = 2.5 -sSliceArray.asSlice[37].dPhaseFOV = 230 -sSliceArray.asSlice[37].dReadoutFOV = 230 -sSliceArray.asSlice[38].sPosition.dCor = -19.43325282 -sSliceArray.asSlice[38].sPosition.dTra = 34.6858437 -sSliceArray.asSlice[38].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[38].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[38].dThickness = 2.5 -sSliceArray.asSlice[38].dPhaseFOV = 230 -sSliceArray.asSlice[38].dReadoutFOV = 230 -sSliceArray.asSlice[39].sPosition.dCor = -19.41754493 -sSliceArray.asSlice[39].sPosition.dTra = 37.68580258 -sSliceArray.asSlice[39].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[39].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[39].dThickness = 2.5 -sSliceArray.asSlice[39].dPhaseFOV = 230 -sSliceArray.asSlice[39].dReadoutFOV = 230 -sSliceArray.asSlice[40].sPosition.dCor = -19.40183703 -sSliceArray.asSlice[40].sPosition.dTra = 40.68576145 -sSliceArray.asSlice[40].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[40].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[40].dThickness = 2.5 -sSliceArray.asSlice[40].dPhaseFOV = 230 -sSliceArray.asSlice[40].dReadoutFOV = 230 -sSliceArray.asSlice[41].sPosition.dCor = -19.38612914 -sSliceArray.asSlice[41].sPosition.dTra = 43.68572033 -sSliceArray.asSlice[41].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[41].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[41].dThickness = 2.5 -sSliceArray.asSlice[41].dPhaseFOV = 230 -sSliceArray.asSlice[41].dReadoutFOV = 230 -sSliceArray.asSlice[42].sPosition.dCor = -19.37042125 -sSliceArray.asSlice[42].sPosition.dTra = 46.68567921 -sSliceArray.asSlice[42].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[42].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[42].dThickness = 2.5 -sSliceArray.asSlice[42].dPhaseFOV = 230 -sSliceArray.asSlice[42].dReadoutFOV = 230 -sSliceArray.asSlice[43].sPosition.dCor = -19.35471336 -sSliceArray.asSlice[43].sPosition.dTra = 49.68563808 -sSliceArray.asSlice[43].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[43].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[43].dThickness = 2.5 -sSliceArray.asSlice[43].dPhaseFOV = 230 -sSliceArray.asSlice[43].dReadoutFOV = 230 -sSliceArray.asSlice[44].sPosition.dCor = -19.33900547 -sSliceArray.asSlice[44].sPosition.dTra = 52.68559696 -sSliceArray.asSlice[44].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[44].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[44].dThickness = 2.5 -sSliceArray.asSlice[44].dPhaseFOV = 230 -sSliceArray.asSlice[44].dReadoutFOV = 230 -sSliceArray.asSlice[45].sPosition.dCor = -19.32329758 -sSliceArray.asSlice[45].sPosition.dTra = 55.68555584 -sSliceArray.asSlice[45].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[45].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[45].dThickness = 2.5 -sSliceArray.asSlice[45].dPhaseFOV = 230 -sSliceArray.asSlice[45].dReadoutFOV = 230 -sSliceArray.asSlice[46].sPosition.dCor = -19.30758969 -sSliceArray.asSlice[46].sPosition.dTra = 58.68551471 -sSliceArray.asSlice[46].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[46].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[46].dThickness = 2.5 -sSliceArray.asSlice[46].dPhaseFOV = 230 -sSliceArray.asSlice[46].dReadoutFOV = 230 -sSliceArray.asSlice[47].sPosition.dCor = -19.29188179 -sSliceArray.asSlice[47].sPosition.dTra = 61.68547359 -sSliceArray.asSlice[47].sNormal.dCor = 0.005235963828 -sSliceArray.asSlice[47].sNormal.dTra = 0.9999862922 -sSliceArray.asSlice[47].dThickness = 2.5 -sSliceArray.asSlice[47].dPhaseFOV = 230 -sSliceArray.asSlice[47].dReadoutFOV = 230 -sSliceArray.anAsc[1] = 1 -sSliceArray.anAsc[2] = 2 -sSliceArray.anAsc[3] = 3 -sSliceArray.anAsc[4] = 4 -sSliceArray.anAsc[5] = 5 -sSliceArray.anAsc[6] = 6 -sSliceArray.anAsc[7] = 7 -sSliceArray.anAsc[8] = 8 -sSliceArray.anAsc[9] = 9 -sSliceArray.anAsc[10] = 10 -sSliceArray.anAsc[11] = 11 -sSliceArray.anAsc[12] = 12 -sSliceArray.anAsc[13] = 13 -sSliceArray.anAsc[14] = 14 -sSliceArray.anAsc[15] = 15 -sSliceArray.anAsc[16] = 16 -sSliceArray.anAsc[17] = 17 -sSliceArray.anAsc[18] = 18 -sSliceArray.anAsc[19] = 19 -sSliceArray.anAsc[20] = 20 -sSliceArray.anAsc[21] = 21 -sSliceArray.anAsc[22] = 22 -sSliceArray.anAsc[23] = 23 -sSliceArray.anAsc[24] = 24 -sSliceArray.anAsc[25] = 25 -sSliceArray.anAsc[26] = 26 -sSliceArray.anAsc[27] = 27 -sSliceArray.anAsc[28] = 28 -sSliceArray.anAsc[29] = 29 -sSliceArray.anAsc[30] = 30 -sSliceArray.anAsc[31] = 31 -sSliceArray.anAsc[32] = 32 -sSliceArray.anAsc[33] = 33 -sSliceArray.anAsc[34] = 34 -sSliceArray.anAsc[35] = 35 -sSliceArray.anAsc[36] = 36 -sSliceArray.anAsc[37] = 37 -sSliceArray.anAsc[38] = 38 -sSliceArray.anAsc[39] = 39 -sSliceArray.anAsc[40] = 40 -sSliceArray.anAsc[41] = 41 -sSliceArray.anAsc[42] = 42 -sSliceArray.anAsc[43] = 43 -sSliceArray.anAsc[44] = 44 -sSliceArray.anAsc[45] = 45 -sSliceArray.anAsc[46] = 46 -sSliceArray.anAsc[47] = 47 -sSliceArray.anPos[1] = 1 -sSliceArray.anPos[2] = 2 -sSliceArray.anPos[3] = 3 -sSliceArray.anPos[4] = 4 -sSliceArray.anPos[5] = 5 -sSliceArray.anPos[6] = 6 -sSliceArray.anPos[7] = 7 -sSliceArray.anPos[8] = 8 -sSliceArray.anPos[9] = 9 -sSliceArray.anPos[10] = 10 -sSliceArray.anPos[11] = 11 -sSliceArray.anPos[12] = 12 -sSliceArray.anPos[13] = 13 -sSliceArray.anPos[14] = 14 -sSliceArray.anPos[15] = 15 -sSliceArray.anPos[16] = 16 -sSliceArray.anPos[17] = 17 -sSliceArray.anPos[18] = 18 -sSliceArray.anPos[19] = 19 -sSliceArray.anPos[20] = 20 -sSliceArray.anPos[21] = 21 -sSliceArray.anPos[22] = 22 -sSliceArray.anPos[23] = 23 -sSliceArray.anPos[24] = 24 -sSliceArray.anPos[25] = 25 -sSliceArray.anPos[26] = 26 -sSliceArray.anPos[27] = 27 -sSliceArray.anPos[28] = 28 -sSliceArray.anPos[29] = 29 -sSliceArray.anPos[30] = 30 -sSliceArray.anPos[31] = 31 -sSliceArray.anPos[32] = 32 -sSliceArray.anPos[33] = 33 -sSliceArray.anPos[34] = 34 -sSliceArray.anPos[35] = 35 -sSliceArray.anPos[36] = 36 -sSliceArray.anPos[37] = 37 -sSliceArray.anPos[38] = 38 -sSliceArray.anPos[39] = 39 -sSliceArray.anPos[40] = 40 -sSliceArray.anPos[41] = 41 -sSliceArray.anPos[42] = 42 -sSliceArray.anPos[43] = 43 -sSliceArray.anPos[44] = 44 -sSliceArray.anPos[45] = 45 -sSliceArray.anPos[46] = 46 -sSliceArray.anPos[47] = 47 -sSliceArray.lSize = 48 -sSliceArray.lConc = 1 -sSliceArray.ucMode = 0x2 -sSliceArray.sTSat.dThickness = 50 -sGroupArray.asGroup[0].nSize = 48 -sGroupArray.asGroup[0].dDistFact = 0.2 -sGroupArray.anMember[1] = 1 -sGroupArray.anMember[2] = 2 -sGroupArray.anMember[3] = 3 -sGroupArray.anMember[4] = 4 -sGroupArray.anMember[5] = 5 -sGroupArray.anMember[6] = 6 -sGroupArray.anMember[7] = 7 -sGroupArray.anMember[8] = 8 -sGroupArray.anMember[9] = 9 -sGroupArray.anMember[10] = 10 -sGroupArray.anMember[11] = 11 -sGroupArray.anMember[12] = 12 -sGroupArray.anMember[13] = 13 -sGroupArray.anMember[14] = 14 -sGroupArray.anMember[15] = 15 -sGroupArray.anMember[16] = 16 -sGroupArray.anMember[17] = 17 -sGroupArray.anMember[18] = 18 -sGroupArray.anMember[19] = 19 -sGroupArray.anMember[20] = 20 -sGroupArray.anMember[21] = 21 -sGroupArray.anMember[22] = 22 -sGroupArray.anMember[23] = 23 -sGroupArray.anMember[24] = 24 -sGroupArray.anMember[25] = 25 -sGroupArray.anMember[26] = 26 -sGroupArray.anMember[27] = 27 -sGroupArray.anMember[28] = 28 -sGroupArray.anMember[29] = 29 -sGroupArray.anMember[30] = 30 -sGroupArray.anMember[31] = 31 -sGroupArray.anMember[32] = 32 -sGroupArray.anMember[33] = 33 -sGroupArray.anMember[34] = 34 -sGroupArray.anMember[35] = 35 -sGroupArray.anMember[36] = 36 -sGroupArray.anMember[37] = 37 -sGroupArray.anMember[38] = 38 -sGroupArray.anMember[39] = 39 -sGroupArray.anMember[40] = 40 -sGroupArray.anMember[41] = 41 -sGroupArray.anMember[42] = 42 -sGroupArray.anMember[43] = 43 -sGroupArray.anMember[44] = 44 -sGroupArray.anMember[45] = 45 -sGroupArray.anMember[46] = 46 -sGroupArray.anMember[47] = 47 -sGroupArray.anMember[48] = -1 -sGroupArray.lSize = 1 -sGroupArray.sPSat.dThickness = 50 -sGroupArray.sPSat.dGap = 10 -sAutoAlign.dAAMatrix[0] = 1 -sAutoAlign.dAAMatrix[5] = 1 -sAutoAlign.dAAMatrix[10] = 1 -sAutoAlign.dAAMatrix[15] = 1 -sNavigatorPara.lBreathHoldMeas = 1 -sNavigatorPara.lRespComp = 4 -sNavigatorPara.alFree[22] = 2 -sNavigatorPara.adFree[13] = 150000 -sBladePara.dBladeCoverage = 100 -sBladePara.ucMotionCorr = 0x2 -sPrepPulses.ucFatSat = 0x1 -sPrepPulses.ucWaterSat = 0x4 -sPrepPulses.ucInversion = 0x4 -sPrepPulses.ucSatRecovery = 0x1 -sPrepPulses.ucT2Prep = 0x1 -sPrepPulses.ucTIScout = 0x1 -sPrepPulses.ucFatSatMode = 0x2 -sPrepPulses.dDarkBloodThickness = 200 -sPrepPulses.dDarkBloodFlipAngle = 200 -sPrepPulses.dT2PrepDuration = 40 -sPrepPulses.dIRPulseThicknessFactor = 0.77 -sKSpace.dPhaseResolution = 1 -sKSpace.dSliceResolution = 1 -sKSpace.dAngioDynCentralRegionA = 20 -sKSpace.dAngioDynSamplingDensityB = 25 -sKSpace.lBaseResolution = 128 -sKSpace.lPhaseEncodingLines = 128 -sKSpace.lPartitions = 64 -sKSpace.lImagesPerSlab = 64 -sKSpace.lRadialViews = 64 -sKSpace.lRadialInterleavesPerImage = 2 -sKSpace.lLinesPerShot = 1 -sKSpace.unReordering = 0x1 -sKSpace.dSeqPhasePartialFourierForSNR = 1 -sKSpace.ucPhasePartialFourier = 0x4 -sKSpace.ucSlicePartialFourier = 0x10 -sKSpace.ucAveragingMode = 0x2 -sKSpace.ucMultiSliceMode = 0x2 -sKSpace.ucDimension = 0x2 -sKSpace.ucTrajectory = 0x1 -sKSpace.ucViewSharing = 0x1 -sKSpace.ucAsymmetricEchoMode = 0x1 -sKSpace.ucPOCS = 0x1 -sFastImaging.lEPIFactor = 128 -sFastImaging.lTurboFactor = 1 -sFastImaging.lSliceTurboFactor = 1 -sFastImaging.lSegments = 1 -sFastImaging.ulEnableRFSpoiling = 0x1 -sFastImaging.ucSegmentationMode = 0x1 -sFastImaging.lShots = 1 -sFastImaging.lEchoTrainDuration = 700 -sPhysioImaging.lSignal1 = 1 -sPhysioImaging.lMethod1 = 1 -sPhysioImaging.lSignal2 = 1 -sPhysioImaging.lMethod2 = 1 -sPhysioImaging.lPhases = 1 -sPhysioImaging.lRetroGatedImages = 16 -sPhysioImaging.sPhysioECG.lTriggerPulses = 1 -sPhysioImaging.sPhysioECG.lTriggerWindow = 5 -sPhysioImaging.sPhysioECG.lArrhythmiaDetection = 1 -sPhysioImaging.sPhysioECG.lCardiacGateOnThreshold = 100000 -sPhysioImaging.sPhysioECG.lCardiacGateOffThreshold = 700000 -sPhysioImaging.sPhysioECG.lTriggerIntervals = 1 -sPhysioImaging.sPhysioPulse.lTriggerPulses = 1 -sPhysioImaging.sPhysioPulse.lTriggerWindow = 5 -sPhysioImaging.sPhysioPulse.lArrhythmiaDetection = 1 -sPhysioImaging.sPhysioPulse.lCardiacGateOnThreshold = 100000 -sPhysioImaging.sPhysioPulse.lCardiacGateOffThreshold = 700000 -sPhysioImaging.sPhysioPulse.lTriggerIntervals = 1 -sPhysioImaging.sPhysioExt.lTriggerPulses = 1 -sPhysioImaging.sPhysioExt.lTriggerWindow = 5 -sPhysioImaging.sPhysioExt.lArrhythmiaDetection = 1 -sPhysioImaging.sPhysioExt.lCardiacGateOnThreshold = 100000 -sPhysioImaging.sPhysioExt.lCardiacGateOffThreshold = 700000 -sPhysioImaging.sPhysioExt.lTriggerIntervals = 1 -sPhysioImaging.sPhysioResp.lRespGateThreshold = 20 -sPhysioImaging.sPhysioResp.lRespGatePhase = 2 -sPhysioImaging.sPhysioResp.dGatingRatio = 0.3 -sPhysioImaging.sPhysioNative.ucMode = 0x1 -sPhysioImaging.sPhysioNative.ucFlowSenMode = 0x1 -sSpecPara.lPhaseCyclingType = 1 -sSpecPara.lPhaseEncodingType = 1 -sSpecPara.lRFExcitationBandwidth = 1 -sSpecPara.ucRemoveOversampling = 0x1 -sSpecPara.lAutoRefScanNo = 1 -sSpecPara.lDecouplingType = 1 -sSpecPara.lNOEType = 1 -sSpecPara.lExcitationType = 1 -sSpecPara.lSpecAppl = 1 -sSpecPara.lSpectralSuppression = 1 -sDiffusion.lDiffWeightings = 2 -sDiffusion.alBValue[1] = 1000 -sDiffusion.lNoiseLevel = 40 -sDiffusion.lDiffDirections = 64 -sDiffusion.ulMode = 0x100 -sAngio.ucPCFlowMode = 0x2 -sAngio.ucTOFInflow = 0x4 -sAngio.lDynamicReconMode = 1 -sAngio.lTemporalInterpolation = 1 -sRawFilter.lSlope_256 = 25 -sRawFilter.ucOn = 0x1 -sRawFilter.ucMode = 0x1 -sDistortionCorrFilter.ucMode = 0x1 -sPat.lAccelFactPE = 2 -sPat.lAccelFact3D = 1 -sPat.lRefLinesPE = 38 -sPat.ucPATMode = 0x2 -sPat.ucRefScanMode = 0x4 -sPat.ucTPatAverageAllFrames = 0x1 -sMDS.ulMdsModeMask = 0x1 -sMDS.ulMdsVariableResolution = 0x1 -sMDS.lTableSpeedNumerator = 1 -sMDS.lmdsLinesPerSegment = 15 -sMDS.sMdsEndPosSBCS_mm.dTra = 600 -sMDS.ulMdsReconMode = 0x1 -sMDS.dMdsRangeExtension = 600 -ucEnableIntro = 0x1 -ucDisableChangeStoreImages = 0x1 -ucAAMode = 0x1 -ucAARegionMode = 1 -ucAARefMode = 1 -ucReconstructionMode = 0x1 -ucOneSeriesForAllMeas = 0x1 -ucPHAPSMode = 0x1 -ucDixon = 0x1 -ucDixonSaveOriginal = 0x1 -ucWaitForPrepareCompletion = 0x1 -lAverages = 1 -dAveragesDouble = 1 -adFlipAngleDegree[0] = 90 -lScanTimeSec = 449 -lTotalScanTimeSec = 450 -dRefSNR = 33479.60771 -dRefSNR_VOI = 33479.60771 -tdefaultEVAProt = ""%SiemensEvaDefProt%\DTI\DTI.evp"" -asCoilSelectMeas[0].tNucleus = ""1H"" -asCoilSelectMeas[0].iUsedRFactor = 3 -asCoilSelectMeas[0].asList[0].sCoilElementID.tCoilID = ""HeadMatrix"" -asCoilSelectMeas[0].asList[0].sCoilElementID.lCoilCopy = 1 -asCoilSelectMeas[0].asList[0].sCoilElementID.tElement = ""H3P"" -asCoilSelectMeas[0].asList[0].lElementSelected = 1 -asCoilSelectMeas[0].asList[0].lRxChannelConnected = 1 -asCoilSelectMeas[0].asList[1].sCoilElementID.tCoilID = ""HeadMatrix"" -asCoilSelectMeas[0].asList[1].sCoilElementID.lCoilCopy = 1 -asCoilSelectMeas[0].asList[1].sCoilElementID.tElement = ""H4P"" -asCoilSelectMeas[0].asList[1].lElementSelected = 1 -asCoilSelectMeas[0].asList[1].lRxChannelConnected = 2 -asCoilSelectMeas[0].asList[2].sCoilElementID.tCoilID = ""HeadMatrix"" -asCoilSelectMeas[0].asList[2].sCoilElementID.lCoilCopy = 1 -asCoilSelectMeas[0].asList[2].sCoilElementID.tElement = ""H4S"" -asCoilSelectMeas[0].asList[2].lElementSelected = 1 -asCoilSelectMeas[0].asList[2].lRxChannelConnected = 3 -asCoilSelectMeas[0].asList[3].sCoilElementID.tCoilID = ""HeadMatrix"" -asCoilSelectMeas[0].asList[3].sCoilElementID.lCoilCopy = 1 -asCoilSelectMeas[0].asList[3].sCoilElementID.tElement = ""H4T"" -asCoilSelectMeas[0].asList[3].lElementSelected = 1 -asCoilSelectMeas[0].asList[3].lRxChannelConnected = 4 -asCoilSelectMeas[0].asList[4].sCoilElementID.tCoilID = ""HeadMatrix"" -asCoilSelectMeas[0].asList[4].sCoilElementID.lCoilCopy = 1 -asCoilSelectMeas[0].asList[4].sCoilElementID.tElement = ""H3S"" -asCoilSelectMeas[0].asList[4].lElementSelected = 1 -asCoilSelectMeas[0].asList[4].lRxChannelConnected = 5 -asCoilSelectMeas[0].asList[5].sCoilElementID.tCoilID = ""HeadMatrix"" -asCoilSelectMeas[0].asList[5].sCoilElementID.lCoilCopy = 1 -asCoilSelectMeas[0].asList[5].sCoilElementID.tElement = ""H3T"" -asCoilSelectMeas[0].asList[5].lElementSelected = 1 -asCoilSelectMeas[0].asList[5].lRxChannelConnected = 6 -asCoilSelectMeas[0].asList[6].sCoilElementID.tCoilID = ""HeadMatrix"" -asCoilSelectMeas[0].asList[6].sCoilElementID.lCoilCopy = 1 -asCoilSelectMeas[0].asList[6].sCoilElementID.tElement = ""H1P"" -asCoilSelectMeas[0].asList[6].lElementSelected = 1 -asCoilSelectMeas[0].asList[6].lRxChannelConnected = 7 -asCoilSelectMeas[0].asList[7].sCoilElementID.tCoilID = ""HeadMatrix"" -asCoilSelectMeas[0].asList[7].sCoilElementID.lCoilCopy = 1 -asCoilSelectMeas[0].asList[7].sCoilElementID.tElement = ""H2P"" -asCoilSelectMeas[0].asList[7].lElementSelected = 1 -asCoilSelectMeas[0].asList[7].lRxChannelConnected = 8 -asCoilSelectMeas[0].asList[8].sCoilElementID.tCoilID = ""HeadMatrix"" -asCoilSelectMeas[0].asList[8].sCoilElementID.lCoilCopy = 1 -asCoilSelectMeas[0].asList[8].sCoilElementID.tElement = ""H2S"" -asCoilSelectMeas[0].asList[8].lElementSelected = 1 -asCoilSelectMeas[0].asList[8].lRxChannelConnected = 9 -asCoilSelectMeas[0].asList[9].sCoilElementID.tCoilID = ""HeadMatrix"" -asCoilSelectMeas[0].asList[9].sCoilElementID.lCoilCopy = 1 -asCoilSelectMeas[0].asList[9].sCoilElementID.tElement = ""H2T"" -asCoilSelectMeas[0].asList[9].lElementSelected = 1 -asCoilSelectMeas[0].asList[9].lRxChannelConnected = 10 -asCoilSelectMeas[0].asList[10].sCoilElementID.tCoilID = ""HeadMatrix"" -asCoilSelectMeas[0].asList[10].sCoilElementID.lCoilCopy = 1 -asCoilSelectMeas[0].asList[10].sCoilElementID.tElement = ""H1S"" -asCoilSelectMeas[0].asList[10].lElementSelected = 1 -asCoilSelectMeas[0].asList[10].lRxChannelConnected = 11 -asCoilSelectMeas[0].asList[11].sCoilElementID.tCoilID = ""HeadMatrix"" -asCoilSelectMeas[0].asList[11].sCoilElementID.lCoilCopy = 1 -asCoilSelectMeas[0].asList[11].sCoilElementID.tElement = ""H1T"" -asCoilSelectMeas[0].asList[11].lElementSelected = 1 -asCoilSelectMeas[0].asList[11].lRxChannelConnected = 12 -asCoilSelectMeas[0].sCOILPLUGS.aulPlugId[0] = 0xff -asCoilSelectMeas[0].sCOILPLUGS.aulPlugId[1] = 0xee -asCoilSelectMeas[0].sCOILPLUGS.aulPlugId[2] = 0xee -asCoilSelectMeas[0].sCOILPLUGS.aulPlugId[3] = 0xad -asCoilSelectMeas[0].sCOILPLUGS.aulPlugId[4] = 0xee -asCoilSelectMeas[0].sCOILPLUGS.aulPlugId[5] = 0xee -asCoilSelectMeas[0].sCOILPLUGS.aulPlugId[6] = 0x5d -asCoilSelectMeas[0].sCOILPLUGS.aulPlugId[7] = 0xb1 -asCoilSelectMeas[0].sCOILPLUGS.aulPlugId[8] = 0xee -asCoilSelectMeas[0].sCOILPLUGS.aulPlugId[9] = 0xb2 -asCoilSelectMeas[0].sCOILPLUGS.aulPlugId[10] = 0xee -asCoilSelectMeas[0].sCOILPLUGS.auiNmbrOfNibbles[0] = 0x2 -asCoilSelectMeas[0].sCOILPLUGS.auiNmbrOfNibbles[1] = 0x2 -asCoilSelectMeas[0].sCOILPLUGS.auiNmbrOfNibbles[2] = 0x2 -asCoilSelectMeas[0].sCOILPLUGS.auiNmbrOfNibbles[3] = 0x2 -asCoilSelectMeas[0].sCOILPLUGS.auiNmbrOfNibbles[4] = 0x2 -asCoilSelectMeas[0].sCOILPLUGS.auiNmbrOfNibbles[5] = 0x2 -asCoilSelectMeas[0].sCOILPLUGS.auiNmbrOfNibbles[6] = 0x2 -asCoilSelectMeas[0].sCOILPLUGS.auiNmbrOfNibbles[7] = 0x2 -asCoilSelectMeas[0].sCOILPLUGS.auiNmbrOfNibbles[8] = 0x2 -asCoilSelectMeas[0].sCOILPLUGS.auiNmbrOfNibbles[9] = 0x2 -asCoilSelectMeas[0].sCOILPLUGS.auiNmbrOfNibbles[10] = 0x2 -asCoilSelectMeas[0].aFFT_SCALE[0].flFactor = 3.77259 -asCoilSelectMeas[0].aFFT_SCALE[0].bValid = 1 -asCoilSelectMeas[0].aFFT_SCALE[0].lRxChannel = 1 -asCoilSelectMeas[0].aFFT_SCALE[1].flFactor = 3.83164 -asCoilSelectMeas[0].aFFT_SCALE[1].bValid = 1 -asCoilSelectMeas[0].aFFT_SCALE[1].lRxChannel = 2 -asCoilSelectMeas[0].aFFT_SCALE[2].flFactor = 3.7338 -asCoilSelectMeas[0].aFFT_SCALE[2].bValid = 1 -asCoilSelectMeas[0].aFFT_SCALE[2].lRxChannel = 3 -asCoilSelectMeas[0].aFFT_SCALE[3].flFactor = 4.08449 -asCoilSelectMeas[0].aFFT_SCALE[3].bValid = 1 -asCoilSelectMeas[0].aFFT_SCALE[3].lRxChannel = 4 -asCoilSelectMeas[0].aFFT_SCALE[4].flFactor = 3.82172 -asCoilSelectMeas[0].aFFT_SCALE[4].bValid = 1 -asCoilSelectMeas[0].aFFT_SCALE[4].lRxChannel = 5 -asCoilSelectMeas[0].aFFT_SCALE[5].flFactor = 3.86816 -asCoilSelectMeas[0].aFFT_SCALE[5].bValid = 1 -asCoilSelectMeas[0].aFFT_SCALE[5].lRxChannel = 6 -asCoilSelectMeas[0].aFFT_SCALE[6].flFactor = 4.48252 -asCoilSelectMeas[0].aFFT_SCALE[6].bValid = 1 -asCoilSelectMeas[0].aFFT_SCALE[6].lRxChannel = 7 -asCoilSelectMeas[0].aFFT_SCALE[7].flFactor = 4.39406 -asCoilSelectMeas[0].aFFT_SCALE[7].bValid = 1 -asCoilSelectMeas[0].aFFT_SCALE[7].lRxChannel = 8 -asCoilSelectMeas[0].aFFT_SCALE[8].flFactor = 4.50498 -asCoilSelectMeas[0].aFFT_SCALE[8].bValid = 1 -asCoilSelectMeas[0].aFFT_SCALE[8].lRxChannel = 9 -asCoilSelectMeas[0].aFFT_SCALE[9].flFactor = 4.57011 -asCoilSelectMeas[0].aFFT_SCALE[9].bValid = 1 -asCoilSelectMeas[0].aFFT_SCALE[9].lRxChannel = 10 -asCoilSelectMeas[0].aFFT_SCALE[10].flFactor = 4.6211 -asCoilSelectMeas[0].aFFT_SCALE[10].bValid = 1 -asCoilSelectMeas[0].aFFT_SCALE[10].lRxChannel = 11 -asCoilSelectMeas[0].aFFT_SCALE[11].flFactor = 4.69845 -asCoilSelectMeas[0].aFFT_SCALE[11].bValid = 1 -asCoilSelectMeas[0].aFFT_SCALE[11].lRxChannel = 12 -sEFISPEC.bEFIDataValid = 1 -ucCineMode = 0x1 -ucSequenceType = 0x4 -ucCoilCombineMode = 0x2 -ucFlipAngleMode = 0x1 -lTOM = 1 -lProtID = -434 -ucReadOutMode = 0x1 -ucBold3dPace = 0x1 -ucForcePositioningOnNDIS = 0x1 -ucInternalTablePosValid = 0x1 -sParametricMapping.ucParametricMap = 0x1 -sIR.lScanNumber = 1 -sAsl.ulMode = 0x1 -WaitForUserStart = 0x1 -ucAutoAlignInit = 0x1 -### ASCCONV END ### \ No newline at end of file diff --git a/nibabel/nicom/tests/data/csa2_b0.bin b/nibabel/nicom/tests/data/csa2_b0.bin deleted file mode 100644 index a23d352928..0000000000 Binary files a/nibabel/nicom/tests/data/csa2_b0.bin and /dev/null differ diff --git a/nibabel/nicom/tests/data/csa2_b1000.bin b/nibabel/nicom/tests/data/csa2_b1000.bin deleted file mode 100644 index 3de1528bbe..0000000000 Binary files a/nibabel/nicom/tests/data/csa2_b1000.bin and /dev/null differ diff --git a/nibabel/nicom/tests/data/csa2_zero_len.bin.gz b/nibabel/nicom/tests/data/csa2_zero_len.bin.gz deleted file mode 100644 index 74fbabd024..0000000000 Binary files a/nibabel/nicom/tests/data/csa2_zero_len.bin.gz and /dev/null differ diff --git a/nibabel/nicom/tests/data/csa_slice_norm.dcm b/nibabel/nicom/tests/data/csa_slice_norm.dcm deleted file mode 100644 index e480bcd260..0000000000 Binary files a/nibabel/nicom/tests/data/csa_slice_norm.dcm and /dev/null differ diff --git a/nibabel/nicom/tests/data/csa_str_1001n_items.bin b/nibabel/nicom/tests/data/csa_str_1001n_items.bin deleted file mode 100644 index ce572e9d72..0000000000 Binary files a/nibabel/nicom/tests/data/csa_str_1001n_items.bin and /dev/null differ diff --git a/nibabel/nicom/tests/data/csa_str_valid.bin b/nibabel/nicom/tests/data/csa_str_valid.bin deleted file mode 100644 index 6779d2c0f1..0000000000 Binary files a/nibabel/nicom/tests/data/csa_str_valid.bin and /dev/null differ diff --git a/nibabel/nicom/tests/data/decimal_rescale.dcm b/nibabel/nicom/tests/data/decimal_rescale.dcm deleted file mode 100644 index 148454a3ac..0000000000 Binary files a/nibabel/nicom/tests/data/decimal_rescale.dcm and /dev/null differ diff --git a/nibabel/nicom/tests/data/philips_mprage.dcm.gz b/nibabel/nicom/tests/data/philips_mprage.dcm.gz deleted file mode 100644 index eeb6adac43..0000000000 Binary files a/nibabel/nicom/tests/data/philips_mprage.dcm.gz and /dev/null differ diff --git a/nibabel/nicom/tests/data/siemens_dwi_0.dcm.gz b/nibabel/nicom/tests/data/siemens_dwi_0.dcm.gz deleted file mode 100644 index 011e38336e..0000000000 Binary files a/nibabel/nicom/tests/data/siemens_dwi_0.dcm.gz and /dev/null differ diff --git a/nibabel/nicom/tests/data/siemens_dwi_1000.dcm.gz b/nibabel/nicom/tests/data/siemens_dwi_1000.dcm.gz deleted file mode 100644 index 56731ff3b8..0000000000 Binary files a/nibabel/nicom/tests/data/siemens_dwi_1000.dcm.gz and /dev/null differ diff --git a/nibabel/nicom/tests/data/slicethickness_empty_string.dcm b/nibabel/nicom/tests/data/slicethickness_empty_string.dcm deleted file mode 100644 index c2718cfdeb..0000000000 Binary files a/nibabel/nicom/tests/data/slicethickness_empty_string.dcm and /dev/null differ diff --git a/nibabel/nicom/tests/data_pkgs.py b/nibabel/nicom/tests/data_pkgs.py deleted file mode 100644 index e95478ef90..0000000000 --- a/nibabel/nicom/tests/data_pkgs.py +++ /dev/null @@ -1,11 +0,0 @@ -"""Data packages for DICOM testing""" - -from ... import data as nibd - -PUBLIC_PKG_DEF = dict(relpath='nipy/dicom/public', name='nipy-dicom-public', version='0.1') - -PRIVATE_PKG_DEF = dict(relpath='nipy/dicom/private', name='nipy-dicom-private', version='0.1') - - -PUBLIC_DS = nibd.datasource_or_bomber(PUBLIC_PKG_DEF) -PRIVATE_DS = nibd.datasource_or_bomber(PRIVATE_PKG_DEF) diff --git a/nibabel/nicom/tests/test_ascconv.py b/nibabel/nicom/tests/test_ascconv.py deleted file mode 100644 index afe5f05e13..0000000000 --- a/nibabel/nicom/tests/test_ascconv.py +++ /dev/null @@ -1,67 +0,0 @@ -"""Testing Siemens "ASCCONV" parser""" - -from collections import OrderedDict -from os.path import dirname -from os.path import join as pjoin - -from numpy.testing import assert_array_almost_equal, assert_array_equal - -from .. import ascconv - -DATA_PATH = pjoin(dirname(__file__), 'data') -ASCCONV_INPUT = pjoin(DATA_PATH, 'ascconv_sample.txt') - - -def test_ascconv_parse(): - with open(ASCCONV_INPUT) as fobj: - contents = fobj.read() - ascconv_dict, attrs = ascconv.parse_ascconv(contents, str_delim='""') - assert attrs == OrderedDict() - assert len(ascconv_dict) == 72 - assert ascconv_dict['tProtocolName'] == 'CBU+AF8-DTI+AF8-64D+AF8-1A' - assert ascconv_dict['ucScanRegionPosValid'] == 1 - assert_array_almost_equal(ascconv_dict['sProtConsistencyInfo']['flNominalB0'], 2.89362) - assert ascconv_dict['sProtConsistencyInfo']['flGMax'] == 26 - assert list(ascconv_dict['sSliceArray'].keys()) == [ - 'asSlice', - 'anAsc', - 'anPos', - 'lSize', - 'lConc', - 'ucMode', - 'sTSat', - ] - slice_arr = ascconv_dict['sSliceArray'] - as_slice = slice_arr['asSlice'] - assert_array_equal([e['dPhaseFOV'] for e in as_slice], 230) - assert_array_equal([e['dReadoutFOV'] for e in as_slice], 230) - assert_array_equal([e['dThickness'] for e in as_slice], 2.5) - # Some lists defined starting at 1, so have None as first element - assert slice_arr['anAsc'] == [None] + list(range(1, 48)) - assert slice_arr['anPos'] == [None] + list(range(1, 48)) - # A top level list - assert len(ascconv_dict['asCoilSelectMeas']) == 1 - as_list = ascconv_dict['asCoilSelectMeas'][0]['asList'] - # This lower-level list does start indexing at 0 - assert len(as_list) == 12 - for i, el in enumerate(as_list): - assert list(el.keys()) == ['sCoilElementID', 'lElementSelected', 'lRxChannelConnected'] - assert el['lElementSelected'] == 1 - assert el['lRxChannelConnected'] == i + 1 - # Test negative number - assert_array_almost_equal(as_slice[0]['sPosition']['dCor'], -20.03015269) - - -def test_ascconv_w_attrs(): - in_str = ( - '### ASCCONV BEGIN object=MrProtDataImpl@MrProtocolData ' - 'version=41340006 ' - 'converter=%MEASCONST%/ConverterList/Prot_Converter.txt ###\n' - 'test = "hello"\n' - '### ASCCONV END ###' - ) - ascconv_dict, attrs = ascconv.parse_ascconv(in_str, '""') - assert attrs['object'] == 'MrProtDataImpl@MrProtocolData' - assert attrs['version'] == '41340006' - assert attrs['converter'] == '%MEASCONST%/ConverterList/Prot_Converter.txt' - assert ascconv_dict['test'] == 'hello' diff --git a/nibabel/nicom/tests/test_csareader.py b/nibabel/nicom/tests/test_csareader.py deleted file mode 100644 index f31f4a3935..0000000000 --- a/nibabel/nicom/tests/test_csareader.py +++ /dev/null @@ -1,132 +0,0 @@ -"""Testing Siemens CSA header reader""" - -import gzip -from copy import deepcopy -from os.path import join as pjoin - -import numpy as np -import pytest - -from .. import csareader as csa -from .. import dwiparams as dwp -from . import dicom_test, pydicom -from .test_dicomwrappers import DATA, IO_DATA_PATH - -CSA2_B0 = open(pjoin(IO_DATA_PATH, 'csa2_b0.bin'), 'rb').read() -CSA2_B1000 = open(pjoin(IO_DATA_PATH, 'csa2_b1000.bin'), 'rb').read() -CSA2_0len = gzip.open(pjoin(IO_DATA_PATH, 'csa2_zero_len.bin.gz'), 'rb').read() -CSA_STR_valid = open(pjoin(IO_DATA_PATH, 'csa_str_valid.bin'), 'rb').read() -CSA_STR_1001n_items = open(pjoin(IO_DATA_PATH, 'csa_str_1001n_items.bin'), 'rb').read() - - -@dicom_test -def test_csa_header_read(): - hdr = csa.get_csa_header(DATA, 'image') - assert hdr['n_tags'] == 83 - assert csa.get_csa_header(DATA, 'series')['n_tags'] == 65 - with pytest.raises(ValueError): - csa.get_csa_header(DATA, 'xxxx') - assert csa.is_mosaic(hdr) - # Get a shallow copy of the data, lacking the CSA marker - # Need to do it this way because del appears broken in pydicom 0.9.7 - data2 = pydicom.dataset.Dataset() - for element in DATA: - if (element.tag.group, element.tag.elem) != (0x29, 0x10): - 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)] - assert csa.is_mosaic(csa.get_csa_header(data2, 'image')) - - -def test_csas0(): - for csa_str in (CSA2_B0, CSA2_B1000): - csa_info = csa.read(csa_str) - assert csa_info['type'] == 2 - assert csa_info['n_tags'] == 83 - tags = csa_info['tags'] - assert len(tags) == 83 - n_o_m = tags['NumberOfImagesInMosaic'] - assert n_o_m['items'] == [48] - csa_info = csa.read(CSA2_B1000) - b_matrix = csa_info['tags']['B_matrix'] - assert len(b_matrix['items']) == 6 - b_value = csa_info['tags']['B_value'] - assert b_value['items'] == [1000] - - -def test_csa_len0(): - # We did get a failure for item with item_len of 0 - gh issue #92 - csa_info = csa.read(CSA2_0len) - assert csa_info['type'] == 2 - assert csa_info['n_tags'] == 44 - tags = csa_info['tags'] - assert len(tags) == 44 - - -def test_csa_nitem(): - # testing csa.read's ability to raise an error when n_items >= 200 - with pytest.raises(csa.CSAReadError): - csa.read(CSA_STR_1001n_items) - # OK when < 1000 - csa_info = csa.read(CSA_STR_valid) - assert len(csa_info['tags']) == 1 - # OK after changing module global - n_items_thresh = csa.MAX_CSA_ITEMS - try: - csa.MAX_CSA_ITEMS = 2000 - csa_info = csa.read(CSA_STR_1001n_items) - assert len(csa_info['tags']) == 1 - finally: - csa.MAX_CSA_ITEMS = n_items_thresh - - -def test_csa_params(): - for csa_str in (CSA2_B0, CSA2_B1000): - csa_info = csa.read(csa_str) - n_o_m = csa.get_n_mosaic(csa_info) - assert n_o_m == 48 - snv = csa.get_slice_normal(csa_info) - assert snv.shape == (3,) - assert np.allclose(1, np.sqrt((snv * snv).sum())) - amt = csa.get_acq_mat_txt(csa_info) - assert amt == '128p*128' - csa_info = csa.read(CSA2_B0) - b_matrix = csa.get_b_matrix(csa_info) - assert b_matrix is None - b_value = csa.get_b_value(csa_info) - assert b_value == 0 - g_vector = csa.get_g_vector(csa_info) - assert g_vector is None - csa_info = csa.read(CSA2_B1000) - b_matrix = csa.get_b_matrix(csa_info) - assert b_matrix.shape == (3, 3) - # check (by absence of error) that the B matrix is positive - # semi-definite. - dwp.B2q(b_matrix) # no error - b_value = csa.get_b_value(csa_info) - assert b_value == 1000 - g_vector = csa.get_g_vector(csa_info) - assert g_vector.shape == (3,) - assert np.allclose(1, np.sqrt((g_vector * g_vector).sum())) - - -def test_ice_dims(): - ex_dims0 = ['X', '1', '1', '1', '1', '1', '1', '48', '1', '1', '1', '1', '201'] - ex_dims1 = ['X', '1', '1', '1', '2', '1', '1', '48', '1', '1', '1', '1', '201'] - for csa_str, ex_dims in ((CSA2_B0, ex_dims0), (CSA2_B1000, ex_dims1)): - csa_info = csa.read(csa_str) - assert csa.get_ice_dims(csa_info) == ex_dims - assert csa.get_ice_dims({}) is None - - -@dicom_test -def test_missing_csa_elem(): - # Test that we get None instead of raising an Exception when the file has - # the PrivateCreator element for the CSA dict but not the element with the - # actual CSA header (perhaps due to anonymization) - dcm = deepcopy(DATA) - csa_tag = pydicom.dataset.Tag(0x29, 0x1010) - del dcm[csa_tag] - hdr = csa.get_csa_header(dcm, 'image') - assert hdr is None diff --git a/nibabel/nicom/tests/test_dicomreaders.py b/nibabel/nicom/tests/test_dicomreaders.py deleted file mode 100644 index d508343be1..0000000000 --- a/nibabel/nicom/tests/test_dicomreaders.py +++ /dev/null @@ -1,60 +0,0 @@ -"""Testing reading DICOM files""" - -from os.path import join as pjoin - -import numpy as np -import pytest -from numpy.testing import assert_array_almost_equal, assert_array_equal - -from nibabel.optpkg import optional_package - -from .. import dicomreaders as didr -from .test_dicomwrappers import DATA, EXPECTED_AFFINE, EXPECTED_PARAMS, IO_DATA_PATH - -pydicom, _, setup_module = optional_package('pydicom') - - -def test_read_dwi(): - img = didr.mosaic_to_nii(DATA) - arr = img.get_fdata() - assert arr.shape == (128, 128, 48) - assert_array_almost_equal(img.affine, EXPECTED_AFFINE) - - -def test_read_dwis(): - data, aff, bs, gs = didr.read_mosaic_dwi_dir(IO_DATA_PATH, 'siemens_dwi_*.dcm.gz') - assert data.ndim == 4 - assert_array_almost_equal(aff, EXPECTED_AFFINE) - assert_array_almost_equal(bs, (0, EXPECTED_PARAMS[0])) - assert_array_almost_equal(gs, (np.zeros((3,)), EXPECTED_PARAMS[1])) - with pytest.raises(OSError): - didr.read_mosaic_dwi_dir('improbable') - - -def test_passing_kwds(): - # Check that we correctly pass keywords to dicom - dwi_glob = 'siemens_dwi_*.dcm.gz' - csa_glob = 'csa*.bin' - for func in (didr.read_mosaic_dwi_dir, didr.read_mosaic_dir): - data, aff, bs, gs = func(IO_DATA_PATH, dwi_glob) - # This should not raise an error - data2, aff2, bs2, gs2 = func(IO_DATA_PATH, dwi_glob, dicom_kwargs=dict(force=True)) - assert_array_equal(data, data2) - # This should raise an error in pydicom.filereader.dcmread - with pytest.raises(TypeError): - func(IO_DATA_PATH, dwi_glob, dicom_kwargs=dict(not_a_parameter=True)) - # These are invalid dicoms, so will raise an error unless force=True - with pytest.raises(pydicom.filereader.InvalidDicomError): - func(IO_DATA_PATH, csa_glob) - # But here, we catch the error because the dicoms are in the wrong - # format - with pytest.raises(didr.DicomReadError): - func(IO_DATA_PATH, csa_glob, dicom_kwargs=dict(force=True)) - - -def test_slices_to_series(): - dicom_files = (pjoin(IO_DATA_PATH, f'{i}.dcm') for i in range(2)) - wrappers = [didr.wrapper_from_file(f) for f in dicom_files] - series = didr.slices_to_series(wrappers) - assert len(series) == 1 - assert len(series[0]) == 2 diff --git a/nibabel/nicom/tests/test_dicomwrappers.py b/nibabel/nicom/tests/test_dicomwrappers.py deleted file mode 100755 index 9f707b25e7..0000000000 --- a/nibabel/nicom/tests/test_dicomwrappers.py +++ /dev/null @@ -1,1122 +0,0 @@ -"""Testing DICOM wrappers""" - -import gzip -from copy import deepcopy -from decimal import Decimal -from hashlib import sha1 -from os.path import dirname -from os.path import join as pjoin -from unittest import TestCase - -import numpy as np -import pytest -from numpy.testing import assert_array_almost_equal, assert_array_equal - -from ...tests.nibabel_data import get_nibabel_data, needs_nibabel_data -from ...volumeutils import endian_codes -from .. import dicomreaders as didr -from .. import dicomwrappers as didw -from . import dicom_test, have_dicom, pydicom - -IO_DATA_PATH = pjoin(dirname(__file__), 'data') -DATA_FILE = pjoin(IO_DATA_PATH, 'siemens_dwi_1000.dcm.gz') -DATA_FILE_PHILIPS = pjoin(IO_DATA_PATH, 'philips_mprage.dcm.gz') -if have_dicom: - DATA = pydicom.dcmread(gzip.open(DATA_FILE)) - DATA_PHILIPS = pydicom.dcmread(gzip.open(DATA_FILE_PHILIPS)) -else: - DATA = None - DATA_PHILIPS = None -DATA_FILE_B0 = pjoin(IO_DATA_PATH, 'siemens_dwi_0.dcm.gz') -DATA_FILE_SLC_NORM = pjoin(IO_DATA_PATH, 'csa_slice_norm.dcm') -DATA_FILE_DEC_RSCL = pjoin(IO_DATA_PATH, 'decimal_rescale.dcm') -DATA_FILE_4D = pjoin(IO_DATA_PATH, '4d_multiframe_test.dcm') -DATA_FILE_EMPTY_ST = pjoin(IO_DATA_PATH, 'slicethickness_empty_string.dcm') -DATA_FILE_4D_DERIVED = pjoin(get_nibabel_data(), 'nitest-dicom', '4d_multiframe_with_derived.dcm') -DATA_FILE_CT = pjoin(get_nibabel_data(), 'nitest-dicom', 'siemens_ct_header_csa.dcm') -DATA_FILE_SIEMENS_TRACE = pjoin( - get_nibabel_data(), - 'dcm_qa_xa30', - 'In/20_DWI_dir80_AP/0001_1.3.12.2.1107.5.2.43.67093.2022071112140611403312307.dcm', -) - -# This affine from our converted image was shown to match our image spatially -# with an image from SPM DICOM conversion. We checked the matching with SPM -# check reg. We have flipped the first and second rows to allow for rows, cols -# transpose in current return compared to original case. -EXPECTED_AFFINE = np.array( # do this for philips? - [ - [-1.796875, 0, 0, 115], - [0, -1.79684984, -0.01570896, 135.028779], - [0, -0.00940843750, 2.99995887, -78.710481], - [0, 0, 0, 1], - ] -)[:, [1, 0, 2, 3]] - -# from Guys and Matthew's SPM code, undoing SPM's Y flip, and swapping first two -# values in vector, to account for data rows, cols difference. -EXPECTED_PARAMS = [992.05050247, (0.00507649, 0.99997450, -0.005023611)] - - -@dicom_test -def test_wrappers(): - # test direct wrapper calls - # first with empty or minimal data - multi_minimal = { - 'PerFrameFunctionalGroupsSequence': [pydicom.Dataset()], - 'SharedFunctionalGroupsSequence': [pydicom.Dataset()], - } - for maker, args in ( - (didw.Wrapper, ({},)), - (didw.SiemensWrapper, ({},)), - (didw.MosaicWrapper, ({}, None, 10)), - (didw.MultiframeWrapper, (multi_minimal,)), - ): - dw = maker(*args) - assert dw.get('InstanceNumber') is None - assert dw.get('AcquisitionNumber') is None - with pytest.raises(KeyError): - dw['not an item'] - with pytest.raises(didw.WrapperError): - dw.get_data() - with pytest.raises(didw.WrapperError): - dw.affine - with pytest.raises(TypeError): - maker() - # Check default attributes - if not maker is didw.MosaicWrapper: - assert not dw.is_mosaic - assert dw.b_matrix is None - assert dw.q_vector is None - for maker in (didw.wrapper_from_data, didw.Wrapper, didw.SiemensWrapper, didw.MosaicWrapper): - dw = maker(DATA) - assert dw.get('InstanceNumber') == 2 - assert dw.get('AcquisitionNumber') == 2 - with pytest.raises(KeyError): - dw['not an item'] - for maker in (didw.MosaicWrapper, didw.wrapper_from_data): - dw = maker(DATA) - assert dw.is_mosaic - # DATA is not a Multiframe DICOM file - with pytest.raises(didw.WrapperError): - didw.MultiframeWrapper(DATA) - - -def test_get_from_wrapper(): - # Test that 'get', and __getitem__ work as expected for underlying dicom - # data - dcm_data = {'some_key': 'some value'} - dw = didw.Wrapper(dcm_data) - assert dw.get('some_key') == 'some value' - assert dw.get('some_other_key') is None - # Getitem uses the same dictionary access - assert dw['some_key'] == 'some value' - # And raises a WrapperError for missing keys - with pytest.raises(KeyError): - dw['some_other_key'] - # Test we don't use attributes for get - - class FakeData(dict): - pass - - d = FakeData() - d.some_key = 'another bit of data' - dw = didw.Wrapper(d) - assert dw.get('some_key') is None - # Check get defers to dcm_data get - - class FakeData2: - def get(self, key, default): - return 1 - - d = FakeData2() - d.some_key = 'another bit of data' - dw = didw.Wrapper(d) - assert dw.get('some_key') == 1 - - -@dicom_test -def test_wrapper_from_data(): - # test wrapper from data, wrapper from file - for dw in (didw.wrapper_from_data(DATA), didw.wrapper_from_file(DATA_FILE)): - assert dw.get('InstanceNumber') == 2 - assert dw.get('AcquisitionNumber') == 2 - with pytest.raises(KeyError): - dw['not an item'] - assert dw.is_mosaic - assert_array_almost_equal(np.dot(didr.DPCS_TO_TAL, dw.affine), EXPECTED_AFFINE) - for dw in (didw.wrapper_from_data(DATA_PHILIPS), didw.wrapper_from_file(DATA_FILE_PHILIPS)): - assert dw.get('InstanceNumber') == 1 - assert dw.get('AcquisitionNumber') == 3 - with pytest.raises(KeyError): - dw['not an item'] - assert dw.is_multiframe - # Another CSA file - dw = didw.wrapper_from_file(DATA_FILE_SLC_NORM) - assert dw.is_mosaic - # Check that multiframe requires minimal set of DICOM tags - fake_data = dict() - fake_data['SOPClassUID'] = '1.2.840.10008.5.1.4.1.1.4.2' - dw = didw.wrapper_from_data(fake_data) - assert not dw.is_multiframe - # use the correct SOPClassUID - fake_data['SOPClassUID'] = '1.2.840.10008.5.1.4.1.1.4.1' - with pytest.raises(didw.WrapperError): - didw.wrapper_from_data(fake_data) - fake_data['PerFrameFunctionalGroupsSequence'] = [pydicom.Dataset()] - with pytest.raises(didw.WrapperError): - didw.wrapper_from_data(fake_data) - fake_data['SharedFunctionalGroupsSequence'] = [pydicom.Dataset()] - # minimal set should now be met - dw = didw.wrapper_from_data(fake_data) - assert dw.is_multiframe - - -@dicom_test -def test_wrapper_args_kwds(): - # Test we can pass args, kwargs to dcmread - dcm = didw.wrapper_from_file(DATA_FILE) - data = dcm.get_data() - # Passing in non-default arg for defer_size - dcm2 = didw.wrapper_from_file(DATA_FILE, np.inf) - assert_array_equal(data, dcm2.get_data()) - # Passing in non-default arg for defer_size with kwds - dcm2 = didw.wrapper_from_file(DATA_FILE, defer_size=np.inf) - assert_array_equal(data, dcm2.get_data()) - # Trying to read non-dicom file raises pydicom error, usually - csa_fname = pjoin(IO_DATA_PATH, 'csa2_b0.bin') - with pytest.raises(pydicom.filereader.InvalidDicomError): - didw.wrapper_from_file(csa_fname) - # We can force the read, in which case rubbish returns - dcm_malo = didw.wrapper_from_file(csa_fname, force=True) - assert not dcm_malo.is_mosaic - - -@dicom_test -def test_dwi_params(): - dw = didw.wrapper_from_data(DATA) - b_matrix = dw.b_matrix - assert b_matrix.shape == (3, 3) - q = dw.q_vector - b = np.sqrt(np.sum(q * q)) # vector norm - g = q / b - assert_array_almost_equal(b, EXPECTED_PARAMS[0]) - assert_array_almost_equal(g, EXPECTED_PARAMS[1]) - - -@dicom_test -def test_q_vector_etc(): - # Test diffusion params in wrapper classes - # Default is no q_vector, b_value, b_vector - dw = didw.Wrapper(DATA) - assert dw.q_vector is None - assert dw.b_value is None - assert dw.b_vector is None - for pos in range(3): - q_vec = np.zeros((3,)) - q_vec[pos] = 10.0 - # Reset wrapped dicom to refresh one_time property - dw = didw.Wrapper(DATA) - dw.q_vector = q_vec - assert_array_equal(dw.q_vector, q_vec) - assert dw.b_value == 10 - assert_array_equal(dw.b_vector, q_vec / 10.0) - # Reset wrapped dicom to refresh one_time property - dw = didw.Wrapper(DATA) - dw.q_vector = np.array([0, 0, 1e-6]) - assert dw.b_value == 0 - assert_array_equal(dw.b_vector, np.zeros((3,))) - # Test MosaicWrapper - sdw = didw.MosaicWrapper(DATA) - exp_b, exp_g = EXPECTED_PARAMS - assert_array_almost_equal(sdw.q_vector, exp_b * np.array(exp_g), 5) - assert_array_almost_equal(sdw.b_value, exp_b) - assert_array_almost_equal(sdw.b_vector, exp_g) - # Reset wrapped dicom to refresh one_time property - sdw = didw.MosaicWrapper(DATA) - sdw.q_vector = np.array([0, 0, 1e-6]) - assert sdw.b_value == 0 - assert_array_equal(sdw.b_vector, np.zeros((3,))) - - -@dicom_test -def test_vol_matching(): - # make the Siemens wrapper, check it compares True against itself - dw_siemens = didw.wrapper_from_data(DATA) - assert dw_siemens.is_mosaic - assert dw_siemens.is_csa - assert dw_siemens.is_same_series(dw_siemens) - # make plain wrapper, compare against itself - dw_plain = didw.Wrapper(DATA) - assert not dw_plain.is_mosaic - assert not dw_plain.is_csa - assert dw_plain.is_same_series(dw_plain) - # specific vs plain wrapper compares False, because the Siemens - # wrapper has more non-empty information - assert not dw_plain.is_same_series(dw_siemens) - # and this should be symmetric - assert not dw_siemens.is_same_series(dw_plain) - # we can even make an empty wrapper. This compares True against - # itself but False against the others - dw_empty = didw.Wrapper({}) - assert dw_empty.is_same_series(dw_empty) - assert not dw_empty.is_same_series(dw_plain) - assert not dw_plain.is_same_series(dw_empty) - # Just to check the interface, make a pretend signature-providing - # object. - - class C: - series_signature = {} - - assert dw_empty.is_same_series(C()) - - # make the Philips wrapper, check it compares True against itself - dw_philips = didw.wrapper_from_data(DATA_PHILIPS) - assert dw_philips.is_multiframe - assert dw_philips.is_same_series(dw_philips) - # make plain wrapper, compare against itself - dw_plain_philips = didw.Wrapper(DATA) - assert not dw_plain_philips.is_multiframe - assert dw_plain_philips.is_same_series(dw_plain_philips) - # specific vs plain wrapper compares False, because the Philips - # wrapper has more non-empty information - assert not dw_plain_philips.is_same_series(dw_philips) - # and this should be symmetric - assert not dw_philips.is_same_series(dw_plain_philips) - # we can even make an empty wrapper. This compares True against - # itself but False against the others - dw_empty = didw.Wrapper({}) - assert dw_empty.is_same_series(dw_empty) - assert not dw_empty.is_same_series(dw_plain_philips) - assert not dw_plain_philips.is_same_series(dw_empty) - - -@dicom_test -def test_slice_indicator(): - dw_0 = didw.wrapper_from_file(DATA_FILE_B0) - dw_1000 = didw.wrapper_from_data(DATA) - z = dw_0.slice_indicator - assert not z is None - assert z == dw_1000.slice_indicator - dw_empty = didw.Wrapper({}) - assert dw_empty.slice_indicator is None - - -@dicom_test -def test_orthogonal(): - # Test that the slice normal is sufficiently orthogonal - dw = didw.wrapper_from_file(DATA_FILE_SLC_NORM) - R = dw.rotation_matrix - assert np.allclose(np.eye(3), np.dot(R, R.T), atol=1e-6) - - # Test the threshold for rotation matrix orthogonality - d = {} - d['ImageOrientationPatient'] = [0, 1, 0, 1, 0, 0] - dw = didw.wrapper_from_data(d) - assert_array_equal(dw.rotation_matrix, np.eye(3)) - d['ImageOrientationPatient'] = [1e-5, 1, 0, 1, 0, 0] - dw = didw.wrapper_from_data(d) - assert_array_almost_equal(dw.rotation_matrix, np.eye(3), 5) - d['ImageOrientationPatient'] = [1e-4, 1, 0, 1, 0, 0] - dw = didw.wrapper_from_data(d) - with pytest.raises(didw.WrapperPrecisionError): - dw.rotation_matrix - - -@dicom_test -def test_rotation_matrix(): - # Test rotation matrix and slice normal - d = {} - d['ImageOrientationPatient'] = [0, 1, 0, 1, 0, 0] - dw = didw.wrapper_from_data(d) - assert_array_equal(dw.rotation_matrix, np.eye(3)) - d['ImageOrientationPatient'] = [1, 0, 0, 0, 1, 0] - dw = didw.wrapper_from_data(d) - assert_array_equal(dw.rotation_matrix, [[0, 1, 0], [1, 0, 0], [0, 0, -1]]) - - -@dicom_test -def test_use_csa_sign(): - # Test that we get the same slice normal, even after swapping the iop - # directions - dw = didw.wrapper_from_file(DATA_FILE_SLC_NORM) - iop = dw.image_orient_patient - dw.image_orient_patient = np.c_[iop[:, 1], iop[:, 0]] - dw2 = didw.wrapper_from_file(DATA_FILE_SLC_NORM) - assert np.allclose(dw.slice_normal, dw2.slice_normal) - - -@dicom_test -def test_assert_parallel(): - # Test that we get an AssertionError if the cross product and the CSA - # slice normal are not parallel - dw = didw.wrapper_from_file(DATA_FILE_SLC_NORM) - dw.image_orient_patient = np.c_[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]] - with pytest.raises(AssertionError): - dw.slice_normal - - -@dicom_test -def test_decimal_rescale(): - # Test that we don't get back a data array with dtype object when our - # rescale slope is a decimal - dw = didw.wrapper_from_file(DATA_FILE_DEC_RSCL) - assert dw.get_data().dtype != np.dtype(object) - - -def fake_frames(seq_name, field_name, value_seq, frame_seq=None): - """Make fake frames for multiframe testing - - Parameters - ---------- - seq_name : str - name of sequence - field_name : str - name of field within sequence - value_seq : length N sequence - sequence of values - frame_seq : length N list - previous result from this function to update - - Returns - ------- - frame_seq : length N list - each element in list is obj.[0]. = - value_seq[n] for n in range(N) - """ - if frame_seq is None: - frame_seq = [pydicom.Dataset() for _ in range(len(value_seq))] - for value, fake_frame in zip(value_seq, frame_seq): - if value is None: - continue - if hasattr(fake_frame, seq_name): - fake_element = getattr(fake_frame, seq_name)[0] - else: - fake_element = pydicom.Dataset() - setattr(fake_frame, seq_name, [fake_element]) - setattr(fake_element, field_name, value) - return frame_seq - - -def fake_shape_dependents( - div_seq, - sid_seq=None, - sid_dim=None, - ipp_seq=None, - slice_dim=None, - flip_ipp_idx_corr=False, -): - """Make a fake dictionary of data that ``image_shape`` is dependent on. - - If you are providing the ``ipp_seq`` argument, they should be generated using - a slice normal aligned with the z-axis (i.e. iop == (0, 1, 0, 1, 0, 0)). - - Parameters - ---------- - div_seq : list of tuples - list of values to use for the `DimensionIndexValues` of each frame. - sid_seq : list of int - list of values to use for the `StackID` of each frame. - sid_dim : int - the index of the column in 'div_seq' to use as 'sid_seq' - ipp_seq : list of tuples - list of values to use for `ImagePositionPatient` for each frame - slice_dim : int - the index of the column in 'div_seq' corresponding to slices - flip_ipp_idx_corr : bool - generate ipp values so slice location is negatively correlated with slice index - """ - - class DimIdxSeqElem(pydicom.Dataset): - def __init__(self, dip=(0, 0), fgp=None): - super().__init__() - self.DimensionIndexPointer = dip - if fgp is not None: - self.FunctionalGroupPointer = fgp - - class FrmContSeqElem(pydicom.Dataset): - def __init__(self, div, sid): - super().__init__() - self.DimensionIndexValues = list(div) - self.StackID = str(sid) - - class PlnPosSeqElem(pydicom.Dataset): - def __init__(self, ipp): - super().__init__() - self.ImagePositionPatient = ipp - - class PlnOrientSeqElem(pydicom.Dataset): - def __init__(self, iop): - super().__init__() - self.ImageOrientationPatient = iop - - class PerFrmFuncGrpSeqElem(pydicom.Dataset): - def __init__(self, div, sid, ipp, iop): - super().__init__() - self.FrameContentSequence = [FrmContSeqElem(div, sid)] - self.PlanePositionSequence = [PlnPosSeqElem(ipp)] - self.PlaneOrientationSequence = [PlnOrientSeqElem(iop)] - - # if no StackID values passed in then use the values at index 'sid_dim' in - # the value for DimensionIndexValues for it - n_indices = len(div_seq[0]) - if sid_seq is None: - if sid_dim is None: - sid_dim = 0 - sid_seq = [div[sid_dim] for div in div_seq] - # Determine slice_dim and create per-slice ipp information - if slice_dim is None: - slice_dim = 1 if sid_dim == 0 else 0 - num_of_frames = len(div_seq) - frame_slc_indices = np.array(div_seq)[:, slice_dim] - uniq_slc_indices = np.unique(frame_slc_indices) - n_slices = len(uniq_slc_indices) - iop_seq = [[0.0, 1.0, 0.0, 1.0, 0.0, 0.0] for _ in range(num_of_frames)] - if ipp_seq is None: - slc_locs = np.linspace(-1.0, 1.0, n_slices) - if flip_ipp_idx_corr: - slc_locs = slc_locs[::-1] - slc_idx_loc = { - div_idx: slc_locs[arr_idx] for arr_idx, div_idx in enumerate(np.sort(uniq_slc_indices)) - } - ipp_seq = [[-1.0, -1.0, slc_idx_loc[idx]] for idx in frame_slc_indices] - else: - assert flip_ipp_idx_corr is False # caller can flip it themselves - assert len(ipp_seq) == num_of_frames - # create the DimensionIndexSequence - dim_idx_seq = [DimIdxSeqElem()] * n_indices - # Add entry for InStackPositionNumber to DimensionIndexSequence - fcs_tag = pydicom.datadict.tag_for_keyword('FrameContentSequence') - isp_tag = pydicom.datadict.tag_for_keyword('InStackPositionNumber') - dim_idx_seq[slice_dim] = DimIdxSeqElem(isp_tag, fcs_tag) - # add an entry for StackID into the DimensionIndexSequence - if sid_dim is not None: - sid_tag = pydicom.datadict.tag_for_keyword('StackID') - dim_idx_seq[sid_dim] = DimIdxSeqElem(sid_tag, fcs_tag) - # create the PerFrameFunctionalGroupsSequence - frames = [ - PerFrmFuncGrpSeqElem(div, sid, ipp, iop) - for div, sid, ipp, iop in zip(div_seq, sid_seq, ipp_seq, iop_seq) - ] - return { - 'NumberOfFrames': num_of_frames, - 'DimensionIndexSequence': dim_idx_seq, - 'PerFrameFunctionalGroupsSequence': frames, - } - - -if have_dicom: - - class FakeDataset(pydicom.Dataset): - pixel_array = None - - -class TestMultiFrameWrapper(TestCase): - # Test MultiframeWrapper - - if have_dicom: - # Minimal contents of dcm_data for this wrapper - MINIMAL_MF = FakeDataset() - MINIMAL_MF.PerFrameFunctionalGroupsSequence = [pydicom.Dataset()] - MINIMAL_MF.SharedFunctionalGroupsSequence = [pydicom.Dataset()] - WRAPCLASS = didw.MultiframeWrapper - - @dicom_test - def test_shape(self): - # Check the shape algorithm - fake_mf = deepcopy(self.MINIMAL_MF) - MFW = self.WRAPCLASS - dw = MFW(fake_mf) - # No rows, cols, raise WrapperError - with pytest.raises(didw.WrapperError): - dw.image_shape - fake_mf.Rows = 64 - with pytest.raises(didw.WrapperError): - dw.image_shape - fake_mf.pop('Rows') - fake_mf.Columns = 64 - with pytest.raises(didw.WrapperError): - dw.image_shape - fake_mf.Rows = 32 - # Single frame doesn't need dimension index values - assert dw.image_shape == (32, 64) - assert len(dw.frame_order) == 1 - assert dw.frame_order[0] == 0 - # Multiple frames do require dimension index values - fake_mf.PerFrameFunctionalGroupsSequence = [pydicom.Dataset(), pydicom.Dataset()] - with pytest.raises(didw.WrapperError): - MFW(fake_mf).image_shape - # check 2D shape with StackID index is 0 - div_seq = ((1, 1),) - fake_mf.update(fake_shape_dependents(div_seq, sid_dim=0)) - dw = MFW(fake_mf) - assert dw.image_shape == (32, 64) - assert len(dw.frame_order) == 1 - assert dw.frame_order[0] == 0 - # Check 2D shape with extraneous extra indices - div_seq = ((1, 1, 2),) - fake_mf.update(fake_shape_dependents(div_seq, sid_dim=0)) - dw = MFW(fake_mf) - assert dw.image_shape == (32, 64) - assert len(dw.frame_order) == 1 - assert dw.frame_order[0] == 0 - # Check 2D plus time - div_seq = ((1, 1, 1), (1, 1, 2), (1, 1, 3)) - fake_mf.update(fake_shape_dependents(div_seq, sid_dim=0)) - assert MFW(fake_mf).image_shape == (32, 64, 1, 3) - # Check 3D shape when StackID index is 0 - div_seq = ((1, 1), (1, 2), (1, 3), (1, 4)) - fake_mf.update(fake_shape_dependents(div_seq, sid_dim=0)) - assert MFW(fake_mf).image_shape == (32, 64, 4) - # Check fow warning when implicitly dropping stacks - div_seq = ((1, 1), (1, 2), (1, 3), (2, 4)) - fake_mf.update(fake_shape_dependents(div_seq, sid_dim=0)) - with pytest.warns( - UserWarning, - match='A multi-stack file was passed without an explicit filter,', - ): - assert MFW(fake_mf).image_shape == (32, 64, 3) - # No warning if we expclitly select that StackID to keep - assert MFW(fake_mf, frame_filters=(didw.FilterMultiStack(1),)).image_shape == (32, 64, 3) - assert MFW(fake_mf, frame_filters=(didw.FilterMultiStack(2),)).image_shape == (32, 64) - # Stack filtering is the same when StackID is not an index - div_seq = ((1,), (2,), (3,), (4,)) - sid_seq = (1, 1, 1, 2) - fake_mf.update(fake_shape_dependents(div_seq, sid_seq=sid_seq)) - with pytest.warns( - UserWarning, - match='A multi-stack file was passed without an explicit filter,', - ): - assert MFW(fake_mf).image_shape == (32, 64, 3) - # No warning if we expclitly select that StackID to keep - assert MFW(fake_mf, frame_filters=(didw.FilterMultiStack(1),)).image_shape == (32, 64, 3) - assert MFW(fake_mf, frame_filters=(didw.FilterMultiStack(2),)).image_shape == (32, 64) - # Check for error when explicitly requested StackID is missing - with pytest.raises(didw.WrapperError): - MFW(fake_mf, frame_filters=(didw.FilterMultiStack(3),)) - # StackID can be a string - div_seq = ((1,), (2,), (3,), (4,)) - sid_seq = ('a', 'a', 'a', 'b') - fake_mf.update(fake_shape_dependents(div_seq, sid_seq=sid_seq)) - with pytest.warns( - UserWarning, - match='A multi-stack file was passed without an explicit filter,', - ): - assert MFW(fake_mf).image_shape == (32, 64, 3) - assert MFW(fake_mf, frame_filters=(didw.FilterMultiStack('a'),)).image_shape == (32, 64, 3) - assert MFW(fake_mf, frame_filters=(didw.FilterMultiStack('b'),)).image_shape == (32, 64) - # Make some fake frame data for 4D when StackID index is 0 - div_seq = ((1, 1, 1), (1, 2, 1), (1, 1, 2), (1, 2, 2), (1, 1, 3), (1, 2, 3)) - fake_mf.update(fake_shape_dependents(div_seq, sid_dim=0)) - assert MFW(fake_mf).image_shape == (32, 64, 2, 3) - # Check stack number matching for 4D when StackID index is 0 - div_seq = ((1, 1, 1), (1, 2, 1), (1, 1, 2), (1, 2, 2), (1, 1, 3), (2, 2, 3)) - fake_mf.update(fake_shape_dependents(div_seq, sid_dim=0)) - with pytest.warns( - UserWarning, - match='A multi-stack file was passed without an explicit filter,', - ): - with pytest.raises(didw.WrapperError): - MFW(fake_mf).image_shape - # Check indices can be non-contiguous when StackID index is 0 - div_seq = ((1, 1, 1), (1, 2, 1), (1, 1, 3), (1, 2, 3)) - fake_mf.update(fake_shape_dependents(div_seq, sid_dim=0)) - assert MFW(fake_mf).image_shape == (32, 64, 2, 2) - # Check indices can include zero when StackID index is 0 - div_seq = ((1, 1, 0), (1, 2, 0), (1, 1, 3), (1, 2, 3)) - fake_mf.update(fake_shape_dependents(div_seq, sid_dim=0)) - assert MFW(fake_mf).image_shape == (32, 64, 2, 2) - # Check number of IPP vals match the number of slices or we raise - frames = fake_mf.PerFrameFunctionalGroupsSequence - for frame in frames[1:]: - frame.PlanePositionSequence = frames[0].PlanePositionSequence[:] - with pytest.raises(didw.WrapperError): - MFW(fake_mf).image_shape - # Check we raise on missing slices - div_seq = ((1, 1, 0), (1, 2, 0), (1, 1, 1)) - fake_mf.update(fake_shape_dependents(div_seq, sid_dim=0)) - with pytest.raises(didw.WrapperError): - MFW(fake_mf).image_shape - # check 3D shape when there is no StackID index - div_seq = ((1,), (2,), (3,), (4,)) - sid_seq = (1, 1, 1, 1) - fake_mf.update(fake_shape_dependents(div_seq, sid_seq=sid_seq)) - assert MFW(fake_mf).image_shape == (32, 64, 4) - # check 4D shape when there is no StackID index - div_seq = ((1, 1), (2, 1), (1, 2), (2, 2), (1, 3), (2, 3)) - sid_seq = (1, 1, 1, 1, 1, 1) - fake_mf.update(fake_shape_dependents(div_seq, sid_seq=sid_seq)) - assert MFW(fake_mf).image_shape == (32, 64, 2, 3) - # check 4D stack number matching when there is no StackID index - div_seq = ((1, 1), (2, 1), (1, 2), (2, 2), (1, 3), (2, 3)) - sid_seq = (1, 1, 1, 1, 1, 2) - fake_mf.update(fake_shape_dependents(div_seq, sid_seq=sid_seq)) - with pytest.warns( - UserWarning, - match='A multi-stack file was passed without an explicit filter,', - ): - with pytest.raises(didw.WrapperError): - MFW(fake_mf).image_shape - # check 3D shape when StackID index is 1 - div_seq = ((1, 1), (2, 1), (3, 1), (4, 1)) - fake_mf.update(fake_shape_dependents(div_seq, sid_dim=1)) - assert MFW(fake_mf).image_shape == (32, 64, 4) - # Check stack number matching when StackID index is 1 - div_seq = ((1, 1), (2, 1), (3, 2), (4, 1)) - fake_mf.update(fake_shape_dependents(div_seq, sid_dim=1)) - with pytest.warns( - UserWarning, - match='A multi-stack file was passed without an explicit filter,', - ): - assert MFW(fake_mf).image_shape == (32, 64, 3) - # Make some fake frame data for 4D when StackID index is 1 - div_seq = ((1, 1, 1), (2, 1, 1), (1, 1, 2), (2, 1, 2), (1, 1, 3), (2, 1, 3)) - fake_mf.update(fake_shape_dependents(div_seq, sid_dim=1)) - assert MFW(fake_mf).image_shape == (32, 64, 2, 3) - # Check non-singular dimension preceding slice dim raises - div_seq = ((1, 1, 1), (1, 2, 1), (1, 1, 2), (1, 2, 2), (1, 1, 3), (1, 2, 3)) - fake_mf.update(fake_shape_dependents(div_seq, sid_dim=0, slice_dim=2)) - with pytest.raises(didw.WrapperError): - MFW(fake_mf).image_shape - # Test with combo indices, here with the last two needing to be combined into - # a single index corresponding to [(1, 1), (1, 1), (2, 1), (2, 1), (2, 2), (2, 2)] - div_seq = ( - (1, 1, 1, 1), - (1, 2, 1, 1), - (1, 1, 2, 1), - (1, 2, 2, 1), - (1, 1, 2, 2), - (1, 2, 2, 2), - ) - fake_mf.update(fake_shape_dependents(div_seq, sid_dim=0)) - assert MFW(fake_mf).image_shape == (32, 64, 2, 3) - # Test invalid 4D indices - div_seq = ((1, 1, 1), (1, 2, 1), (1, 1, 2), (1, 2, 2), (1, 1, 3), (1, 2, 4)) - fake_mf.update(fake_shape_dependents(div_seq, sid_dim=0)) - with pytest.raises(didw.WrapperError): - MFW(fake_mf).image_shape - div_seq = ((1, 1, 1), (1, 2, 1), (1, 1, 2), (1, 2, 2), (1, 1, 3), (1, 2, 2)) - fake_mf.update(fake_shape_dependents(div_seq, sid_dim=0)) - with pytest.raises(didw.WrapperError): - MFW(fake_mf).image_shape - # Time index that is unique to each frame - div_seq = ((1, 1, 1), (1, 2, 2), (1, 1, 3), (1, 2, 4), (1, 1, 5), (1, 2, 6)) - fake_mf.update(fake_shape_dependents(div_seq, sid_dim=0)) - assert MFW(fake_mf).image_shape == (32, 64, 2, 3) - div_seq = ( - (1, 1, 1, 1), - (1, 2, 2, 1), - (1, 1, 3, 1), - (1, 2, 4, 1), - (1, 1, 5, 1), - (1, 2, 6, 1), - (1, 1, 7, 2), - (1, 2, 8, 2), - (1, 1, 9, 2), - (1, 2, 10, 2), - (1, 1, 11, 2), - (1, 2, 12, 2), - ) - fake_mf.update(fake_shape_dependents(div_seq, sid_dim=0)) - assert MFW(fake_mf).image_shape == (32, 64, 2, 3, 2) - # Check we only allow one extra spatial dimension with unique val per frame - div_seq = ( - (1, 1, 1, 6), - (1, 2, 2, 5), - (1, 1, 3, 4), - (1, 2, 4, 3), - (1, 1, 5, 2), - (1, 2, 6, 1), - ) - fake_mf.update(fake_shape_dependents(div_seq, sid_dim=0)) - with pytest.raises(didw.WrapperError): - MFW(fake_mf).image_shape - # Check that having unique value per frame works with single volume - div_seq = ((1, 1, 1), (1, 2, 2), (1, 3, 3)) - fake_mf.update(fake_shape_dependents(div_seq, sid_dim=0)) - assert MFW(fake_mf).image_shape == (32, 64, 3) - - @dicom_test - def test_iop(self): - # Test Image orient patient for multiframe - fake_mf = deepcopy(self.MINIMAL_MF) - MFW = self.WRAPCLASS - dw = MFW(fake_mf) - with pytest.raises(didw.WrapperError): - dw.image_orient_patient - # Make a fake frame - fake_frame = fake_frames( - 'PlaneOrientationSequence', 'ImageOrientationPatient', [[0, 1, 0, 1, 0, 0]] - )[0] - fake_mf.SharedFunctionalGroupsSequence = [fake_frame] - assert_array_equal(MFW(fake_mf).image_orient_patient, [[0, 1], [1, 0], [0, 0]]) - fake_mf.SharedFunctionalGroupsSequence = [pydicom.Dataset()] - with pytest.raises(didw.WrapperError): - MFW(fake_mf).image_orient_patient - fake_mf.PerFrameFunctionalGroupsSequence = [fake_frame] - assert_array_equal(MFW(fake_mf).image_orient_patient, [[0, 1], [1, 0], [0, 0]]) - - @dicom_test - def test_voxel_sizes(self): - # Test voxel size calculation - fake_mf = deepcopy(self.MINIMAL_MF) - MFW = self.WRAPCLASS - dw = MFW(fake_mf) - with pytest.raises(didw.WrapperError): - dw.voxel_sizes - # Make a fake frame - fake_frame = fake_frames('PixelMeasuresSequence', 'PixelSpacing', [[2.1, 3.2]])[0] - fake_mf.SharedFunctionalGroupsSequence = [fake_frame] - # Still not enough, we lack information for slice distances - with pytest.raises(didw.WrapperError): - MFW(fake_mf).voxel_sizes - # This can come from SpacingBetweenSlices or frame SliceThickness - fake_mf.SpacingBetweenSlices = 4.3 - assert_array_equal(MFW(fake_mf).voxel_sizes, [2.1, 3.2, 4.3]) - # If both, prefer SliceThickness - fake_frame.PixelMeasuresSequence[0].SliceThickness = 5.4 - assert_array_equal(MFW(fake_mf).voxel_sizes, [2.1, 3.2, 5.4]) - # Just SliceThickness is OK - del fake_mf.SpacingBetweenSlices - assert_array_equal(MFW(fake_mf).voxel_sizes, [2.1, 3.2, 5.4]) - # Removing shared leads to error again - fake_mf.SharedFunctionalGroupsSequence = [pydicom.Dataset()] - with pytest.raises(didw.WrapperError): - MFW(fake_mf).voxel_sizes - # Restoring to frames makes it work again - fake_mf.PerFrameFunctionalGroupsSequence = [fake_frame] - assert_array_equal(MFW(fake_mf).voxel_sizes, [2.1, 3.2, 5.4]) - # Decimals in any field are OK - fake_frame = fake_frames( - 'PixelMeasuresSequence', 'PixelSpacing', [[Decimal('2.1'), Decimal('3.2')]] - )[0] - fake_mf.SharedFunctionalGroupsSequence = [fake_frame] - fake_mf.SpacingBetweenSlices = Decimal('4.3') - assert_array_equal(MFW(fake_mf).voxel_sizes, [2.1, 3.2, 4.3]) - fake_frame.PixelMeasuresSequence[0].SliceThickness = Decimal('5.4') - assert_array_equal(MFW(fake_mf).voxel_sizes, [2.1, 3.2, 5.4]) - - @dicom_test - def test_image_position(self): - # Test image_position property for multiframe - fake_mf = deepcopy(self.MINIMAL_MF) - MFW = self.WRAPCLASS - dw = MFW(fake_mf) - with pytest.raises(didw.WrapperError): - dw.image_position - # Make a fake frame - iop = [0, 1, 0, 1, 0, 0] - frames = fake_frames('PlaneOrientationSequence', 'ImageOrientationPatient', [iop]) - frames = fake_frames( - 'PlanePositionSequence', 'ImagePositionPatient', [[-2.0, 3.0, 7]], frames - ) - fake_mf.SharedFunctionalGroupsSequence = frames - assert_array_equal(MFW(fake_mf).image_position, [-2, 3, 7]) - fake_mf.SharedFunctionalGroupsSequence = [pydicom.Dataset()] - with pytest.raises(didw.WrapperError): - MFW(fake_mf).image_position - fake_mf.PerFrameFunctionalGroupsSequence = frames - assert_array_equal(MFW(fake_mf).image_position, [-2, 3, 7]) - # Check lists of Decimals work - frames[0].PlanePositionSequence[0].ImagePositionPatient = [ - Decimal(str(v)) for v in [-2, 3, 7] - ] - assert_array_equal(MFW(fake_mf).image_position, [-2, 3, 7]) - assert MFW(fake_mf).image_position.dtype == float - # We should get minimum along slice normal with multiple frames - frames = fake_frames('PlaneOrientationSequence', 'ImageOrientationPatient', [iop] * 2) - ipps = [[-2.0, 3.0, 7], [-2.0, 3.0, 6]] - frames = fake_frames('PlanePositionSequence', 'ImagePositionPatient', ipps, frames) - fake_mf.PerFrameFunctionalGroupsSequence = frames - assert_array_equal(MFW(fake_mf).image_position, [-2, 3, 6]) - - @dicom_test - @pytest.mark.xfail(reason='Not packaged in install', raises=FileNotFoundError) - def test_affine(self): - # Make sure we find orientation/position/spacing info - dw = didw.wrapper_from_file(DATA_FILE_4D) - dw.affine - - @dicom_test - @pytest.mark.xfail(reason='Not packaged in install', raises=FileNotFoundError) - def test_data_real(self): - # The data in this file is (initially) a 1D gradient so it compresses - # well. This just tests that the data ordering produces a consistent - # result. - dw = didw.wrapper_from_file(DATA_FILE_4D) - data = dw.get_data() - # data hash depends on the endianness - if endian_codes[data.dtype.byteorder] == '>': - data = data.byteswap() - dat_str = data.tobytes() - assert sha1(dat_str).hexdigest() == 'dc011bb49682fb78f3cebacf965cb65cc9daba7d' - - @dicom_test - def test_slicethickness_fallback(self): - dw = didw.wrapper_from_file(DATA_FILE_EMPTY_ST) - assert dw.voxel_sizes[2] == 1.0 - - @dicom_test - @needs_nibabel_data('nitest-dicom') - def test_data_derived_shape(self): - # Test 4D diffusion data with an additional trace volume included - # Excludes the trace volume and generates the correct shape - with pytest.warns(UserWarning, match='Derived images found and removed'): - dw = didw.wrapper_from_file(DATA_FILE_4D_DERIVED) - assert dw.image_shape == (96, 96, 60, 33) - - @dicom_test - @needs_nibabel_data('dcm_qa_xa30') - def test_data_trace(self): - # Test that a standalone trace volume is found and not dropped - dw = didw.wrapper_from_file(DATA_FILE_SIEMENS_TRACE) - assert dw.image_shape == (72, 72, 39) - - @dicom_test - @needs_nibabel_data('nitest-dicom') - def test_data_unreadable_private_headers(self): - # Test CT image with unreadable CSA tags - with pytest.warns(UserWarning, match='Error while attempting to read CSA header'): - dw = didw.wrapper_from_file(DATA_FILE_CT) - assert dw.image_shape == (512, 571) - - @dicom_test - def test_data_fake(self): - # Test algorithm for get_data - fake_mf = deepcopy(self.MINIMAL_MF) - MFW = self.WRAPCLASS - dw = MFW(fake_mf) - # Fails - no shape - with pytest.raises(didw.WrapperError): - dw.get_data() - # Set shape by cheating - dw.image_shape = (2, 3, 4) - # Still fails - no data - with pytest.raises(didw.WrapperError): - dw.get_data() - # Make shape and indices - fake_mf.Rows = 2 - fake_mf.Columns = 3 - dim_idxs = ((1, 1), (1, 2), (1, 3), (1, 4)) - fake_mf.update(fake_shape_dependents(dim_idxs, sid_dim=0)) - assert MFW(fake_mf).image_shape == (2, 3, 4) - # Still fails - no data - with pytest.raises(didw.WrapperError): - dw.get_data() - # Add data - 3D - data = np.arange(24).reshape((2, 3, 4)) - # Frames dim is first for some reason - object.__setattr__(fake_mf, 'pixel_array', np.rollaxis(data, 2)) - # Now it should work - dw = MFW(fake_mf) - assert_array_equal(dw.get_data(), data) - # Test scaling works - fake_mf.RescaleSlope = 2.0 - fake_mf.RescaleIntercept = -1 - assert_array_equal(MFW(fake_mf).get_data(), data * 2.0 - 1) - # Check slice sorting - dim_idxs = ((1, 4), (1, 2), (1, 3), (1, 1)) - fake_mf.update(fake_shape_dependents(dim_idxs, sid_dim=0)) - sorted_data = data[..., [3, 1, 2, 0]] - fake_mf.pixel_array = np.rollaxis(sorted_data, 2) - assert_array_equal(MFW(fake_mf).get_data(), data * 2.0 - 1) - # Check slice sorting with negative index / IPP correlation - fake_mf.update(fake_shape_dependents(dim_idxs, sid_dim=0, flip_ipp_idx_corr=True)) - sorted_data = data[..., [0, 2, 1, 3]] - fake_mf.pixel_array = np.rollaxis(sorted_data, 2) - assert_array_equal(MFW(fake_mf).get_data(), data * 2.0 - 1) - # 5D! - dim_idxs = [ - [1, 4, 2, 1], - [1, 2, 2, 1], - [1, 3, 2, 1], - [1, 1, 2, 1], - [1, 4, 2, 2], - [1, 2, 2, 2], - [1, 3, 2, 2], - [1, 1, 2, 2], - [1, 4, 1, 1], - [1, 2, 1, 1], - [1, 3, 1, 1], - [1, 1, 1, 1], - [1, 4, 1, 2], - [1, 2, 1, 2], - [1, 3, 1, 2], - [1, 1, 1, 2], - ] - fake_mf.update(fake_shape_dependents(dim_idxs, sid_dim=0)) - shape = (2, 3, 4, 2, 2) - data = np.arange(np.prod(shape)).reshape(shape) - sorted_data = data.reshape(shape[:2] + (-1,), order='F') - order = [11, 9, 10, 8, 3, 1, 2, 0, 15, 13, 14, 12, 7, 5, 6, 4] - sorted_data = sorted_data[..., np.argsort(order)] - fake_mf.pixel_array = np.rollaxis(sorted_data, 2) - assert_array_equal(MFW(fake_mf).get_data(), data * 2.0 - 1) - - @dicom_test - def test_scale_data(self): - # Test data scaling - fake_mf = deepcopy(self.MINIMAL_MF) - fake_mf.Rows = 2 - fake_mf.Columns = 3 - fake_mf.PerFrameFunctionalGroupsSequence = [pydicom.Dataset() for _ in range(4)] - MFW = self.WRAPCLASS - data = np.arange(24).reshape((2, 3, 4), order='F') - assert_array_equal(data, MFW(fake_mf)._scale_data(data)) - # Test legacy top-level slope/intercept - fake_mf.RescaleSlope = 2.0 - fake_mf.RescaleIntercept = -1.0 - assert_array_equal(data * 2 - 1, MFW(fake_mf)._scale_data(data)) - # RealWorldValueMapping takes precedence, but only with defined units - fake_mf.RealWorldValueMappingSequence = [pydicom.Dataset()] - fake_mf.RealWorldValueMappingSequence[0].RealWorldValueSlope = 10.0 - fake_mf.RealWorldValueMappingSequence[0].RealWorldValueIntercept = -5.0 - assert_array_equal(data * 2 - 1, MFW(fake_mf)._scale_data(data)) - fake_mf.RealWorldValueMappingSequence[0].MeasurementUnitsCodeSequence = [pydicom.Dataset()] - fake_mf.RealWorldValueMappingSequence[0].MeasurementUnitsCodeSequence[0].CodeMeaning = '%' - assert_array_equal(data * 10 - 5, MFW(fake_mf)._scale_data(data)) - fake_mf.RealWorldValueMappingSequence[0].MeasurementUnitsCodeSequence[ - 0 - ].CodeMeaning = 'no units' - assert_array_equal(data * 2 - 1, MFW(fake_mf)._scale_data(data)) - # Possible to have more than one RealWorldValueMapping, use first one with defined units - fake_mf.RealWorldValueMappingSequence.append(pydicom.Dataset()) - fake_mf.RealWorldValueMappingSequence[-1].RealWorldValueSlope = 15.0 - fake_mf.RealWorldValueMappingSequence[-1].RealWorldValueIntercept = -3.0 - fake_mf.RealWorldValueMappingSequence[-1].MeasurementUnitsCodeSequence = [ - pydicom.Dataset() - ] - fake_mf.RealWorldValueMappingSequence[-1].MeasurementUnitsCodeSequence[0].CodeMeaning = '%' - assert_array_equal(data * 15 - 3, MFW(fake_mf)._scale_data(data)) - # A global RWV scale takes precedence over per-frame PixelValueTransformation - div_seq = ((1, 1, 1), (1, 2, 1), (1, 1, 2), (1, 2, 2)) - fake_mf.update(fake_shape_dependents(div_seq, sid_dim=0)) - frames = fake_frames( - 'PixelValueTransformationSequence', - 'RescaleSlope', - [3.0, 3.0, 3.0, 3.0], - fake_mf.PerFrameFunctionalGroupsSequence, - ) - assert_array_equal(data * 15 - 3, MFW(fake_mf)._scale_data(data)) - # The per-frame PixelValueTransformation takes precedence over plain top-level slope / inter - delattr(fake_mf, 'RealWorldValueMappingSequence') - assert_array_equal(data * 3, MFW(fake_mf)._scale_data(data)) - for frame in frames: - frame.PixelValueTransformationSequence[0].RescaleIntercept = -2 - assert_array_equal(data * 3 - 2, MFW(fake_mf)._scale_data(data)) - # Decimals are OK - for frame in frames: - frame.PixelValueTransformationSequence[0].RescaleSlope = Decimal(3) - frame.PixelValueTransformationSequence[0].RescaleIntercept = Decimal(-2) - assert_array_equal(data * 3 - 2, MFW(fake_mf)._scale_data(data)) - # A per-frame RWV scaling takes precedence over per-frame PixelValueTransformation - for frame in frames: - frame.RealWorldValueMappingSequence = [pydicom.Dataset()] - frame.RealWorldValueMappingSequence[0].RealWorldValueSlope = 10.0 - frame.RealWorldValueMappingSequence[0].RealWorldValueIntercept = -5.0 - frame.RealWorldValueMappingSequence[0].MeasurementUnitsCodeSequence = [ - pydicom.Dataset() - ] - frame.RealWorldValueMappingSequence[0].MeasurementUnitsCodeSequence[ - 0 - ].CodeMeaning = '%' - assert_array_equal(data * 10 - 5, MFW(fake_mf)._scale_data(data)) - # Test varying per-frame scale factors - for frame_idx, frame in enumerate(frames): - frame.RealWorldValueMappingSequence[0].RealWorldValueSlope = 2 * (frame_idx + 1) - frame.RealWorldValueMappingSequence[0].RealWorldValueIntercept = -1 * (frame_idx + 1) - assert_array_equal( - data * np.array([2, 4, 6, 8]) + np.array([-1, -2, -3, -4]), - MFW(fake_mf)._scale_data(data), - ) - - @dicom_test - def test_philips_scale_data(self): - fake_mf = deepcopy(self.MINIMAL_MF) - fake_mf.Manufacturer = 'Philips' - fake_mf.Rows = 2 - fake_mf.Columns = 3 - fake_mf.PerFrameFunctionalGroupsSequence = [pydicom.Dataset() for _ in range(4)] - MFW = self.WRAPCLASS - data = np.arange(24).reshape((2, 3, 4), order='F') - # Unlike other manufacturers, public scale factors from Philips without defined - # units should not be used. In lieu of this the private scale factor should be - # used, which should always be available (modulo deidentification). If we can't - # find any of these scale factors a warning is issued. - with pytest.warns( - UserWarning, - match='Unable to find Philips private scale factor, cross-series comparisons may be invalid', - ): - assert_array_equal(data, MFW(fake_mf)._scale_data(data)) - fake_mf.RescaleSlope = 2.0 - fake_mf.RescaleIntercept = -1.0 - for rescale_type in (None, '', 'US', 'normalized'): - if rescale_type is not None: - fake_mf.RescaleType = rescale_type - with pytest.warns( - UserWarning, - match='Unable to find Philips private scale factor, cross-series comparisons may be invalid', - ): - assert_array_equal(data, MFW(fake_mf)._scale_data(data)) - # Falling back to private scaling doesn't generate error - priv_block = fake_mf.private_block(0x2005, 'Philips MR Imaging DD 001', create=True) - priv_block.add_new(0xE, 'FL', 3.0) - assert_array_equal(data * 3.0, MFW(fake_mf)._scale_data(data)) - # If the units are defined they take precedence over private scaling - fake_mf.RescaleType = 'mrad' - assert_array_equal(data * 2 - 1, MFW(fake_mf)._scale_data(data)) - # A RWV scale factor with defined units takes precdence - shared = pydicom.Dataset() - fake_mf.SharedFunctionalGroupsSequence = [shared] - rwv_map = pydicom.Dataset() - rwv_map.RealWorldValueSlope = 10.0 - rwv_map.RealWorldValueIntercept = -5.0 - rwv_map.MeasurementUnitsCodeSequence = [pydicom.Dataset()] - rwv_map.MeasurementUnitsCodeSequence[0].CodeMeaning = '%' - shared.RealWorldValueMappingSequence = [rwv_map] - assert_array_equal(data * 10 - 5, MFW(fake_mf)._scale_data(data)) - # Get rid of valid top-level scale factors, test per-frame scale factors - delattr(shared, 'RealWorldValueMappingSequence') - delattr(fake_mf, 'RescaleType') - del fake_mf[priv_block.get_tag(0xE)] - div_seq = ((1, 1, 1), (1, 2, 1), (1, 1, 2), (1, 2, 2)) - fake_mf.update(fake_shape_dependents(div_seq, sid_dim=0)) - # Simplest case is all frames have same (valid) scale factor - for frame in fake_mf.PerFrameFunctionalGroupsSequence: - pix_trans = pydicom.Dataset() - pix_trans.RescaleSlope = 2.5 - pix_trans.RescaleIntercept = -4 - pix_trans.RescaleType = 'mrad' - frame.PixelValueTransformationSequence = [pix_trans] - assert_array_equal(data * 2.5 - 4, MFW(fake_mf)._scale_data(data)) - # If some frames are missing valid scale factors we should get a warning - for frame in fake_mf.PerFrameFunctionalGroupsSequence[2:]: - delattr(frame.PixelValueTransformationSequence[0], 'RescaleType') - with pytest.warns( - UserWarning, - match='Unable to find Philips private scale factor, cross-series comparisons may be invalid', - ): - assert_array_equal( - data * np.array([2.5, 2.5, 1, 1]) + np.array([-4, -4, 0, 0]), - MFW(fake_mf)._scale_data(data), - ) - # We can fall back to private scale factor on frame-by-frame basis - for frame in fake_mf.PerFrameFunctionalGroupsSequence: - priv_block = frame.private_block(0x2005, 'Philips MR Imaging DD 001', create=True) - priv_block.add_new(0xE, 'FL', 7.0) - assert_array_equal( - data * np.array([2.5, 2.5, 7, 7]) + np.array([-4, -4, 0, 0]), - MFW(fake_mf)._scale_data(data), - ) - # Again RWV scale factors take precedence - for frame_idx, frame in enumerate(fake_mf.PerFrameFunctionalGroupsSequence): - rwv_map = pydicom.Dataset() - rwv_map.RealWorldValueSlope = 14.0 - frame_idx - rwv_map.RealWorldValueIntercept = 5.0 - rwv_map.MeasurementUnitsCodeSequence = [pydicom.Dataset()] - rwv_map.MeasurementUnitsCodeSequence[0].CodeMeaning = '%' - frame.RealWorldValueMappingSequence = [rwv_map] - assert_array_equal( - data * np.array([14, 13, 12, 11]) + np.array([5, 5, 5, 5]), - MFW(fake_mf)._scale_data(data), - ) diff --git a/nibabel/nicom/tests/test_dwiparams.py b/nibabel/nicom/tests/test_dwiparams.py deleted file mode 100644 index 559c0a2143..0000000000 --- a/nibabel/nicom/tests/test_dwiparams.py +++ /dev/null @@ -1,55 +0,0 @@ -"""Testing diffusion parameter processing""" - -import numpy as np -import pytest -from numpy.testing import assert_array_almost_equal -from numpy.testing import assert_equal as np_assert_equal - -from ..dwiparams import B2q, q2bg - - -def test_b2q(): - # conversion of b matrix to q - q = np.array([1, 2, 3]) - s = np.sqrt(np.sum(q * q)) # vector norm - B = np.outer(q, q) - assert_array_almost_equal(q * s, B2q(B)) - q = np.array([1, 2, 3]) - # check that the sign of the vector as positive x convention - B = np.outer(-q, -q) - assert_array_almost_equal(q * s, B2q(B)) - q = np.array([-1, 2, 3]) - B = np.outer(q, q) - assert_array_almost_equal(-q * s, B2q(B)) - # Massive negative eigs - B = np.eye(3) * -1 - with pytest.raises(ValueError): - B2q(B) - # no error if we up the tolerance - q = B2q(B, tol=1) - # Less massive negativity, dropping tol - B = np.diag([-1e-14, 10.0, 1]) - with pytest.raises(ValueError): - B2q(B) - assert_array_almost_equal(B2q(B, tol=5e-13), [0, 10, 0]) - # Confirm that we assume symmetric - B = np.eye(3) - B[0, 1] = 1e-5 - with pytest.raises(ValueError): - B2q(B) - - -def test_q2bg(): - # Conversion of q vector to b value and unit vector - for pos in range(3): - q_vec = np.zeros((3,)) - q_vec[pos] = 10.0 - np_assert_equal(q2bg(q_vec), (10, q_vec / 10.0)) - # Also - check array-like - q_vec = [0, 1e-6, 0] - np_assert_equal(q2bg(q_vec), (0, 0)) - q_vec = [0, 1e-4, 0] - b, g = q2bg(q_vec) - assert_array_almost_equal(b, 1e-4) - assert_array_almost_equal(g, [0, 1, 0]) - np_assert_equal(q2bg(q_vec, tol=5e-4), (0, 0)) diff --git a/nibabel/nicom/tests/test_structreader.py b/nibabel/nicom/tests/test_structreader.py deleted file mode 100644 index ccd2dd4f85..0000000000 --- a/nibabel/nicom/tests/test_structreader.py +++ /dev/null @@ -1,48 +0,0 @@ -"""Testing Siemens CSA header reader""" - -import struct -import sys - -from ..structreader import Unpacker - - -def test_unpacker(): - s = b'1234\x00\x01' - (le_int,) = struct.unpack('h', b'\x00\x01') - if sys.byteorder == 'little': - native_int = le_int - swapped_int = be_int - swapped_code = '>' - else: - native_int = be_int - swapped_int = le_int - swapped_code = '<' - up_str = Unpacker(s, endian='<') - assert up_str.read(4) == b'1234' - up_str.ptr = 0 - assert up_str.unpack('4s') == (b'1234',) - assert up_str.unpack('h') == (le_int,) - up_str = Unpacker(s, endian='>') - assert up_str.unpack('4s') == (b'1234',) - assert up_str.unpack('h') == (be_int,) - # now test conflict of endian - up_str = Unpacker(s, ptr=4, endian='>') - assert up_str.unpack('h') == (be_int,) - up_str.ptr = 4 - assert up_str.unpack('@h') == (native_int,) - # test -1 for read - up_str.ptr = 2 - assert up_str.read() == b'34\x00\x01' - # past end - assert up_str.read() == b'' - # with n_bytes - up_str.ptr = 2 - assert up_str.read(2) == b'34' - assert up_str.read(2) == b'\x00\x01' diff --git a/nibabel/nicom/tests/test_utils.py b/nibabel/nicom/tests/test_utils.py deleted file mode 100644 index bdf95bbbe2..0000000000 --- a/nibabel/nicom/tests/test_utils.py +++ /dev/null @@ -1,65 +0,0 @@ -"""Testing nicom.utils module""" - -import re - -from nibabel.optpkg import optional_package - -from ..utils import find_private_section as fps -from .test_dicomwrappers import DATA, DATA_PHILIPS - -pydicom, _, setup_module = optional_package('pydicom') - - -def test_find_private_section_real(): - # Find section containing named private creator information - # On real data first - assert fps(DATA, 0x29, 'SIEMENS CSA HEADER') == 0x1000 - assert fps(DATA, 0x29, b'SIEMENS CSA HEADER') == 0x1000 - assert fps(DATA, 0x29, re.compile(r'SIEMENS CSA HEADER')) == 0x1000 - assert fps(DATA, 0x29, 'NOT A HEADER') is None - assert fps(DATA, 0x29, 'SIEMENS MEDCOM HEADER2') == 0x1100 - assert fps(DATA_PHILIPS, 0x29, 'SIEMENS CSA HEADER') == None - - -def test_find_private_section_fake(): - # Make and test fake datasets - ds = pydicom.dataset.Dataset({}) - assert fps(ds, 0x11, 'some section') is None - ds.add_new((0x11, 0x10), 'LO', b'some section') - assert fps(ds, 0x11, 'some section') == 0x1000 - ds.add_new((0x11, 0x11), 'LO', b'another section') - ds.add_new((0x11, 0x12), 'LO', b'third section') - assert fps(ds, 0x11, 'third section') == 0x1200 - # Technically incorrect 'OB' is acceptable for VM (should be 'LO') - ds.add_new((0x11, 0x12), 'OB', b'third section') - assert fps(ds, 0x11, 'third section') == 0x1200 - # Anything else not acceptable - ds.add_new((0x11, 0x12), 'PN', b'third section') - assert fps(ds, 0x11, 'third section') is None - # The input (DICOM value) can be a string insteal of bytes - ds.add_new((0x11, 0x12), 'LO', 'third section') - assert fps(ds, 0x11, 'third section') == 0x1200 - # Search can be bytes as well as string - ds.add_new((0x11, 0x12), 'LO', b'third section') - assert fps(ds, 0x11, b'third section') == 0x1200 - # Search with string or bytes must be exact - assert fps(ds, 0x11, b'third sectio') is None - assert fps(ds, 0x11, 'hird sectio') is None - # The search can be a regexp - assert fps(ds, 0x11, re.compile(r'third\Wsectio[nN]')) == 0x1200 - # No match -> None - assert fps(ds, 0x11, re.compile(r'not third\Wsectio[nN]')) is None - # If there are gaps in the sequence before the one we want, that is OK - ds.add_new((0x11, 0x13), 'LO', b'near section') - assert fps(ds, 0x11, 'near section') == 0x1300 - ds.add_new((0x11, 0x15), 'LO', b'far section') - assert fps(ds, 0x11, 'far section') == 0x1500 - # More than one match - find the first. - assert fps(ds, 0x11, re.compile(r'(another|third) section')) == 0x1100 - # The signalling element number must be <= 0xFF - ds = pydicom.dataset.Dataset({}) - ds.add_new((0x11, 0xFF), 'LO', b'some section') - assert fps(ds, 0x11, 'some section') == 0xFF00 - ds = pydicom.dataset.Dataset({}) - ds.add_new((0x11, 0x100), 'LO', b'some section') - assert fps(ds, 0x11, 'some section') is None diff --git a/nibabel/nicom/utils.py b/nibabel/nicom/utils.py deleted file mode 100644 index 2c01c9d161..0000000000 --- a/nibabel/nicom/utils.py +++ /dev/null @@ -1,101 +0,0 @@ -"""Utilities for working with DICOM datasets""" - -from enum import Enum - - -def find_private_section(dcm_data, group_no, creator): - """Return start element in group `group_no` given creator name `creator` - - Private attribute tags need to announce where they will go by putting a tag - in the private group (here `group_no`) between elements 1 and 0xFF. The - element number of these tags give the start of matching information, in the - higher tag numbers. - - Parameters - ---------- - dcm_data : dicom ``dataset`` - Iterating over `dcm_data` produces ``elements`` with attributes - ``tag``, ``VR``, ``value`` - group_no : int - Group number in which to search - creator : str or bytes or regex - Name of section - e.g. 'SIEMENS CSA HEADER' - or regex to search for - section name. Regex used via ``creator.search(element_value)`` where - ``element_value`` is the value of the data element. - - Returns - ------- - element_start : int - Element number at which named section starts. - """ - if hasattr(creator, 'search'): - match_func = creator.search - else: - if isinstance(creator, bytes): - creator = creator.decode('latin-1') - match_func = creator.__eq__ - # Group elements assumed ordered by tag (groupno, elno) - for element in dcm_data.group_dataset(group_no): - elno = element.tag.elem - if elno > 0xFF: - break - if element.VR not in ('LO', 'OB'): - continue - val = element.value - if isinstance(val, bytes): - val = val.decode('latin-1') - if match_func(val): - return elno * 0x100 - return None - - -class Vendor(Enum): - SIEMENS = 1 - GE = 2 - PHILIPS = 3 - - -vendor_priv_sections = { - Vendor.SIEMENS: [ - (0x9, 'SIEMENS SYNGO INDEX SERVICE'), - (0x19, 'SIEMENS MR HEADER'), - (0x21, 'SIEMENS MR SDR 01'), - (0x21, 'SIEMENS MR SDS 01'), - (0x21, 'SIEMENS MR SDI 02'), - (0x29, 'SIEMENS CSA HEADER'), - (0x29, 'SIEMENS MEDCOM HEADER2'), - (0x51, 'SIEMENS MR HEADER'), - ], - Vendor.PHILIPS: [ - (0x2001, 'Philips Imaging DD 001'), - (0x2001, 'Philips Imaging DD 002'), - (0x2001, 'Philips Imaging DD 129'), - (0x2005, 'Philips MR Imaging DD 001'), - (0x2005, 'Philips MR Imaging DD 002'), - (0x2005, 'Philips MR Imaging DD 003'), - (0x2005, 'Philips MR Imaging DD 004'), - (0x2005, 'Philips MR Imaging DD 005'), - (0x2005, 'Philips MR Imaging DD 006'), - (0x2005, 'Philips MR Imaging DD 007'), - (0x2005, 'Philips MR Imaging DD 005'), - (0x2005, 'Philips MR Imaging DD 006'), - ], - Vendor.GE: [ - (0x9, 'GEMS_IDEN_01'), - (0x19, 'GEMS_ACQU_01'), - (0x21, 'GEMS_RELA_01'), - (0x23, 'GEMS_STDY_01'), - (0x25, 'GEMS_SERS_01'), - (0x27, 'GEMS_IMAG_01'), - (0x29, 'GEMS_IMPS_01'), - (0x43, 'GEMS_PARM_01'), - ], -} - - -def vendor_from_private(dcm_data): - """Try to determine the vendor by looking for specific private tags""" - for vendor, priv_sections in vendor_priv_sections.items(): - for priv_group, priv_creator in priv_sections: - if find_private_section(dcm_data, priv_group, priv_creator) != None: - return vendor diff --git a/nibabel/nifti1.py b/nibabel/nifti1.py deleted file mode 100644 index e39f9f9042..0000000000 --- a/nibabel/nifti1.py +++ /dev/null @@ -1,2611 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Read / write access to NIfTI1 image format - -NIfTI1 format defined at http://nifti.nimh.nih.gov/nifti-1/ -""" - -from __future__ import annotations - -import json -import typing as ty -import warnings -from io import BytesIO - -import numpy as np -import numpy.linalg as npl - -from . import analyze # module import -from ._typing import Self, TypeVar -from .arrayproxy import get_obj_dtype -from .batteryrunners import Report -from .casting import have_binary128 -from .deprecated import alert_future_error -from .filebasedimages import ImageFileError, SerializableImage -from .optpkg import optional_package -from .quaternions import fillpositive, mat2quat, quat2mat -from .spatialimages import HeaderDataError -from .spm99analyze import SpmAnalyzeHeader -from .volumeutils import Recoder, endian_codes, make_dt_codes - -if ty.TYPE_CHECKING: - import pydicom as pdcm - - have_dicom = True - DicomDataset = pdcm.Dataset -else: - pdcm, have_dicom, _ = optional_package('pydicom') - if have_dicom: - DicomDataset = pdcm.Dataset - else: - DicomDataset = ty.Any - -T = TypeVar('T', default=bytes) - -# nifti1 flat header definition for Analyze-like first 348 bytes -# first number in comments indicates offset in file header in bytes -# fmt: off -header_dtd = [ - ('sizeof_hdr', 'i4'), # 0; must be 348 - ('data_type', 'S10'), # 4; unused - ('db_name', 'S18'), # 14; unused - ('extents', 'i4'), # 32; unused - ('session_error', 'i2'), # 36; unused - ('regular', 'S1'), # 38; unused - ('dim_info', 'u1'), # 39; MRI slice ordering code - ('dim', 'i2', (8,)), # 40; data array dimensions - ('intent_p1', 'f4'), # 56; first intent parameter - ('intent_p2', 'f4'), # 60; second intent parameter - ('intent_p3', 'f4'), # 64; third intent parameter - ('intent_code', 'i2'), # 68; NIFTI intent code - ('datatype', 'i2'), # 70; it's the datatype - ('bitpix', 'i2'), # 72; number of bits per voxel - ('slice_start', 'i2'), # 74; first slice index - ('pixdim', 'f4', (8,)), # 76; grid spacings (units below) - ('vox_offset', 'f4'), # 108; offset to data in image file - ('scl_slope', 'f4'), # 112; data scaling slope - ('scl_inter', 'f4'), # 116; data scaling intercept - ('slice_end', 'i2'), # 120; last slice index - ('slice_code', 'u1'), # 122; slice timing order - ('xyzt_units', 'u1'), # 123; units of pixdim[1..4] - ('cal_max', 'f4'), # 124; max display intensity - ('cal_min', 'f4'), # 128; min display intensity - ('slice_duration', 'f4'), # 132; time for 1 slice - ('toffset', 'f4'), # 136; time axis shift - ('glmax', 'i4'), # 140; unused - ('glmin', 'i4'), # 144; unused - ('descrip', 'S80'), # 148; any text - ('aux_file', 'S24'), # 228; auxiliary filename - ('qform_code', 'i2'), # 252; xform code - ('sform_code', 'i2'), # 254; xform code - ('quatern_b', 'f4'), # 256; quaternion b param - ('quatern_c', 'f4'), # 260; quaternion c param - ('quatern_d', 'f4'), # 264; quaternion d param - ('qoffset_x', 'f4'), # 268; quaternion x shift - ('qoffset_y', 'f4'), # 272; quaternion y shift - ('qoffset_z', 'f4'), # 276; quaternion z shift - ('srow_x', 'f4', (4,)), # 280; 1st row affine transform - ('srow_y', 'f4', (4,)), # 296; 2nd row affine transform - ('srow_z', 'f4', (4,)), # 312; 3rd row affine transform - ('intent_name', 'S16'), # 328; name or meaning of data - ('magic', 'S4'), # 344; must be 'ni1\0' or 'n+1\0' -] -# fmt: on - -# Full header numpy dtype -header_dtype = np.dtype(header_dtd) - -# datatypes not in analyze format, with codes -if have_binary128(): - # Only enable 128 bit floats if we really have IEEE binary 128 longdoubles - _float128t: type[np.generic] = np.longdouble - _complex256t: type[np.generic] = np.clongdouble -else: - _float128t = np.void - _complex256t = np.void - -_dtdefs = ( # code, label, dtype definition, niistring - (0, 'none', np.void, ''), - (1, 'binary', np.void, ''), - (2, 'uint8', np.uint8, 'NIFTI_TYPE_UINT8'), - (4, 'int16', np.int16, 'NIFTI_TYPE_INT16'), - (8, 'int32', np.int32, 'NIFTI_TYPE_INT32'), - (16, 'float32', np.float32, 'NIFTI_TYPE_FLOAT32'), - (32, 'complex64', np.complex64, 'NIFTI_TYPE_COMPLEX64'), - (64, 'float64', np.float64, 'NIFTI_TYPE_FLOAT64'), - (128, 'RGB', np.dtype([('R', 'u1'), ('G', 'u1'), ('B', 'u1')]), 'NIFTI_TYPE_RGB24'), - (255, 'all', np.void, ''), - (256, 'int8', np.int8, 'NIFTI_TYPE_INT8'), - (512, 'uint16', np.uint16, 'NIFTI_TYPE_UINT16'), - (768, 'uint32', np.uint32, 'NIFTI_TYPE_UINT32'), - (1024, 'int64', np.int64, 'NIFTI_TYPE_INT64'), - (1280, 'uint64', np.uint64, 'NIFTI_TYPE_UINT64'), - (1536, 'float128', _float128t, 'NIFTI_TYPE_FLOAT128'), - (1792, 'complex128', np.complex128, 'NIFTI_TYPE_COMPLEX128'), - (2048, 'complex256', _complex256t, 'NIFTI_TYPE_COMPLEX256'), - ( - 2304, - 'RGBA', - np.dtype([('R', 'u1'), ('G', 'u1'), ('B', 'u1'), ('A', 'u1')]), - 'NIFTI_TYPE_RGBA32', - ), -) - -# Make full code alias bank, including dtype column -data_type_codes = make_dt_codes(_dtdefs) - -# Transform (qform, sform) codes -xform_codes = Recoder( - ( # code, label, niistring - (0, 'unknown', 'NIFTI_XFORM_UNKNOWN'), - (1, 'scanner', 'NIFTI_XFORM_SCANNER_ANAT'), - (2, 'aligned', 'NIFTI_XFORM_ALIGNED_ANAT'), - (3, 'talairach', 'NIFTI_XFORM_TALAIRACH'), - (4, 'mni', 'NIFTI_XFORM_MNI_152'), - (5, 'template', 'NIFTI_XFORM_TEMPLATE_OTHER'), - ), - fields=('code', 'label', 'niistring'), -) - -# unit codes -unit_codes = Recoder( - ( # code, label - (0, 'unknown'), - (1, 'meter'), - (2, 'mm'), - (3, 'micron'), - (8, 'sec'), - (16, 'msec'), - (24, 'usec'), - (32, 'hz'), - (40, 'ppm'), - (48, 'rads'), - ), - fields=('code', 'label'), -) - -slice_order_codes = Recoder( - ( # code, label - (0, 'unknown'), - (1, 'sequential increasing', 'seq inc'), - (2, 'sequential decreasing', 'seq dec'), - (3, 'alternating increasing', 'alt inc'), - (4, 'alternating decreasing', 'alt dec'), - (5, 'alternating increasing 2', 'alt inc 2'), - (6, 'alternating decreasing 2', 'alt dec 2'), - ), - fields=('code', 'label'), -) - -intent_codes = Recoder( - ( - # code, label, parameters description tuple - (0, 'none', (), 'NIFTI_INTENT_NONE'), - (2, 'correlation', ('p1 = DOF',), 'NIFTI_INTENT_CORREL'), - (3, 't test', ('p1 = DOF',), 'NIFTI_INTENT_TTEST'), - (4, 'f test', ('p1 = numerator DOF', 'p2 = denominator DOF'), 'NIFTI_INTENT_FTEST'), - (5, 'z score', (), 'NIFTI_INTENT_ZSCORE'), - (6, 'chi2', ('p1 = DOF',), 'NIFTI_INTENT_CHISQ'), - # two parameter beta distribution - (7, 'beta', ('p1=a', 'p2=b'), 'NIFTI_INTENT_BETA'), - # Prob(x) = (p1 choose x) * p2^x * (1-p2)^(p1-x), for x=0,1,...,p1 - ( - 8, - 'binomial', - ('p1 = number of trials', 'p2 = probability per trial'), - 'NIFTI_INTENT_BINOM', - ), - # 2 parameter gamma - # Density(x) proportional to # x^(p1-1) * exp(-p2*x) - (9, 'gamma', ('p1 = shape, p2 = scale', 2), 'NIFTI_INTENT_GAMMA'), - (10, 'poisson', ('p1 = mean',), 'NIFTI_INTENT_POISSON'), - ( - 11, - 'normal', - ( - 'p1 = mean', - 'p2 = standard deviation', - ), - 'NIFTI_INTENT_NORMAL', - ), - ( - 12, - 'non central f test', - ( - 'p1 = numerator DOF', - 'p2 = denominator DOF', - 'p3 = numerator noncentrality parameter', - ), - 'NIFTI_INTENT_FTEST_NONC', - ), - ( - 13, - 'non central chi2', - ( - 'p1 = DOF', - 'p2 = noncentrality parameter', - ), - 'NIFTI_INTENT_CHISQ_NONC', - ), - ( - 14, - 'logistic', - ( - 'p1 = location', - 'p2 = scale', - ), - 'NIFTI_INTENT_LOGISTIC', - ), - (15, 'laplace', ('p1 = location', 'p2 = scale'), 'NIFTI_INTENT_LAPLACE'), - (16, 'uniform', ('p1 = lower end', 'p2 = upper end'), 'NIFTI_INTENT_UNIFORM'), - ( - 17, - 'non central t test', - ('p1 = DOF', 'p2 = noncentrality parameter'), - 'NIFTI_INTENT_TTEST_NONC', - ), - (18, 'weibull', ('p1 = location', 'p2 = scale, p3 = power'), 'NIFTI_INTENT_WEIBULL'), - # p1 = 1 = 'half normal' distribution - # p1 = 2 = Rayleigh distribution - # p1 = 3 = Maxwell-Boltzmann distribution. - (19, 'chi', ('p1 = DOF',), 'NIFTI_INTENT_CHI'), - (20, 'inverse gaussian', ('pi = mu', 'p2 = lambda'), 'NIFTI_INTENT_INVGAUSS'), - (21, 'extreme value 1', ('p1 = location', 'p2 = scale'), 'NIFTI_INTENT_EXTVAL'), - (22, 'p value', (), 'NIFTI_INTENT_PVAL'), - (23, 'log p value', (), 'NIFTI_INTENT_LOGPVAL'), - (24, 'log10 p value', (), 'NIFTI_INTENT_LOG10PVAL'), - (1001, 'estimate', (), 'NIFTI_INTENT_ESTIMATE'), - (1002, 'label', (), 'NIFTI_INTENT_LABEL'), - (1003, 'neuroname', (), 'NIFTI_INTENT_NEURONAME'), - (1004, 'general matrix', ('p1 = M', 'p2 = N'), 'NIFTI_INTENT_GENMATRIX'), - (1005, 'symmetric matrix', ('p1 = M',), 'NIFTI_INTENT_SYMMATRIX'), - (1006, 'displacement vector', (), 'NIFTI_INTENT_DISPVECT'), - (1007, 'vector', (), 'NIFTI_INTENT_VECTOR'), - (1008, 'pointset', (), 'NIFTI_INTENT_POINTSET'), - (1009, 'triangle', (), 'NIFTI_INTENT_TRIANGLE'), - (1010, 'quaternion', (), 'NIFTI_INTENT_QUATERNION'), - (1011, 'dimensionless', (), 'NIFTI_INTENT_DIMLESS'), - ( - 2001, - 'time series', - (), - 'NIFTI_INTENT_TIME_SERIES', - 'NIFTI_INTENT_TIMESERIES', - ), # this mis-spell occurs in the wild - (2002, 'node index', (), 'NIFTI_INTENT_NODE_INDEX'), - (2003, 'rgb vector', (), 'NIFTI_INTENT_RGB_VECTOR'), - (2004, 'rgba vector', (), 'NIFTI_INTENT_RGBA_VECTOR'), - (2005, 'shape', (), 'NIFTI_INTENT_SHAPE'), - # FSL-specific intent codes - codes used by FNIRT - # ($FSLDIR/warpfns/fnirt_file_reader.h:104) - (2006, 'fnirt disp field', (), 'FSL_FNIRT_DISPLACEMENT_FIELD'), - (2007, 'fnirt cubic spline coef', (), 'FSL_CUBIC_SPLINE_COEFFICIENTS'), - (2008, 'fnirt dct coef', (), 'FSL_DCT_COEFFICIENTS'), - (2009, 'fnirt quad spline coef', (), 'FSL_QUADRATIC_SPLINE_COEFFICIENTS'), - # FSL-specific intent codes - codes used by TOPUP - # ($FSLDIR/topup/topup_file_io.h:104) - (2016, 'topup cubic spline coef ', (), 'FSL_TOPUP_CUBIC_SPLINE_COEFFICIENTS'), - (2017, 'topup quad spline coef', (), 'FSL_TOPUP_QUADRATIC_SPLINE_COEFFICIENTS'), - (2018, 'topup field', (), 'FSL_TOPUP_FIELD'), - ), - fields=('code', 'label', 'parameters', 'niistring'), -) - - -class NiftiExtension(ty.Generic[T]): - """Base class for NIfTI header extensions. - - This class provides access to the extension content in various forms. - For simple extensions that expose data as bytes, text or JSON, this class - is sufficient. More complex extensions should be implemented as subclasses - that provide custom serialization/deserialization methods. - - Efficiency note: - - This class assumes that the runtime representation of the extension content - is mutable. Once a runtime representation is set, it is cached and will be - serialized on any attempt to access the extension content as bytes, including - determining the size of the extension in the NIfTI file. - - If the runtime representation is never accessed, the raw bytes will be used - without modification. While avoiding unnecessary deserialization, if there - are bytestrings that do not produce a valid runtime representation, they will - be written as-is, and may cause errors downstream. - """ - - code: int - encoding: str | None = None - _raw: bytes - _object: T | None = None - - def __init__( - self, - code: int | str, - content: bytes = b'', - object: T | None = None, - ) -> None: - """ - Parameters - ---------- - code : int or str - Canonical extension code as defined in the NIfTI standard, given - either as integer or corresponding label - (see :data:`~nibabel.nifti1.extension_codes`) - content : bytes, optional - Extension content as read from the NIfTI file header. - object : optional - Extension content in runtime form. - """ - try: - self.code = extension_codes.code[code] # type: ignore[assignment] - except KeyError: - self.code = code # type: ignore[assignment] - self._raw = content - if object is not None: - self._object = object - - @property - def _content(self): - return self.get_object() - - @classmethod - def from_bytes(cls, content: bytes) -> Self: - """Create an extension from raw bytes. - - This constructor may only be used in extension classes with a class - attribute `code` to indicate the extension type. - """ - if not hasattr(cls, 'code'): - raise NotImplementedError('from_bytes() requires a class attribute `code`') - return cls(cls.code, content=content) - - @classmethod - def from_object(cls, obj: T) -> Self: - """Create an extension from a runtime object. - - This constructor may only be used in extension classes with a class - attribute `code` to indicate the extension type. - """ - if not hasattr(cls, 'code'): - raise NotImplementedError('from_object() requires a class attribute `code`') - return cls(cls.code, object=obj) - - # Handle (de)serialization of extension content - # Subclasses may implement these methods to provide an alternative - # view of the extension content. If left unimplemented, the content - # must be bytes and is not modified. - def _mangle(self, obj: T) -> bytes: - raise NotImplementedError - - def _unmangle(self, content: bytes) -> T: - raise NotImplementedError - - def _sync(self) -> None: - """Synchronize content with object. - - This permits the runtime representation to be modified in-place - and updates the bytes representation accordingly. - """ - if self._object is not None: - self._raw = self._mangle(self._object) - - def __repr__(self) -> str: - try: - code = extension_codes.label[self.code] - except KeyError: - # deal with unknown codes - code = self.code - return f'{self.__class__.__name__}({code}, {self._raw!r})' - - def __eq__(self, other: object) -> bool: - return ( - isinstance(other, self.__class__) - and self.code == other.code - and self.content == other.content - ) - - def __ne__(self, other): - return not self == other - - def get_code(self): - """Return the canonical extension type code.""" - return self.code - - # Canonical access to extension content - # Follows the lead of httpx.Response .content, .text and .json() - # properties/methods - @property - def content(self) -> bytes: - """Return the extension content as raw bytes.""" - self._sync() - return self._raw - - @property - def text(self) -> str: - """Attempt to decode the extension content as text. - - The encoding is determined by the `encoding` attribute, which may be - set by the user or subclass. If not set, the default encoding is 'utf-8'. - """ - return self.content.decode(self.encoding or 'utf-8') - - def json(self) -> ty.Any: - """Attempt to decode the extension content as JSON. - - If the content is not valid JSON, a JSONDecodeError or UnicodeDecodeError - will be raised. - """ - return json.loads(self.content) - - def get_object(self) -> T: - """Return the extension content in its runtime representation. - - This method may return a different type for each extension type. - For simple use cases, consider using ``.content``, ``.text`` or ``.json()`` - instead. - """ - if self._object is None: - self._object = self._unmangle(self._raw) - return self._object - - # Backwards compatibility - get_content = get_object - - def get_sizeondisk(self) -> int: - """Return the size of the extension in the NIfTI file.""" - # need raw value size plus 8 bytes for esize and ecode, rounded up to next 16 bytes - # Rounding C+8 up to M is done by (C+8 + (M-1)) // M * M - return (len(self.content) + 23) // 16 * 16 - - def write_to(self, fileobj: ty.BinaryIO, byteswap: bool = False) -> None: - """Write header extensions to fileobj - - Write starts at fileobj current file position. - - Parameters - ---------- - fileobj : file-like object - Should implement ``write`` method - byteswap : boolean - Flag if byteswapping the data is required. - - Returns - ------- - None - """ - extstart = fileobj.tell() - rawsize = self.get_sizeondisk() # Calls _sync() - # write esize and ecode first - extinfo = np.array((rawsize, self.code), dtype=np.int32) - if byteswap: - extinfo = extinfo.byteswap() - fileobj.write(extinfo.tobytes()) - # followed by the actual extension content, synced above - fileobj.write(self._raw) - # be nice and zero out remaining part of the extension till the - # next 16 byte border - pad = extstart + rawsize - fileobj.tell() - if pad: - fileobj.write(bytes(pad)) - - -class Nifti1Extension(NiftiExtension[T]): - """Baseclass for NIfTI1 header extensions. - - This class is sufficient to handle very simple text-based extensions, such - as `comment`. More sophisticated extensions should/will be supported by - dedicated subclasses. - """ - - code = 0 # Default to unknown extension - - def _unmangle(self, value: bytes) -> T: - """Convert the extension content into its runtime representation. - - The default implementation does nothing at all. - - Parameters - ---------- - value : str - Extension content as read from file. - - Returns - ------- - The same object that was passed as `value`. - - Notes - ----- - Subclasses should reimplement this method to provide the desired - unmangling procedure and may return any type of object. - """ - return value # type: ignore[return-value] - - def _mangle(self, value: T) -> bytes: - """Convert the extension content into NIfTI file header representation. - - The default implementation does nothing at all. - - Parameters - ---------- - value : str - Extension content in runtime form. - - Returns - ------- - str - - Notes - ----- - Subclasses should reimplement this method to provide the desired - mangling procedure. - """ - return value # type: ignore[return-value] - - -class Nifti1DicomExtension(Nifti1Extension[DicomDataset]): - """NIfTI1 DICOM header extension - - This class is a thin wrapper around pydicom to read a binary DICOM - byte string. If pydicom is available, content is exposed as a Dicom Dataset. - Otherwise, this silently falls back to the standard NiftiExtension class - and content is the raw bytestring loaded directly from the nifti file - header. - """ - - code = 2 - _is_implicit_VR: bool = False - _is_little_endian: bool = True - - def __init__( - self, - code: int | str, - content: bytes | DicomDataset | None = None, - parent_hdr: Nifti1Header | None = None, - ) -> None: - """ - Parameters - ---------- - code : int or str - Canonical extension code as defined in the NIfTI standard, given - either as integer or corresponding label - (see :data:`~nibabel.nifti1.extension_codes`) - content : bytes or pydicom Dataset or None - Extension content - either a bytestring as read from the NIfTI file - header or an existing pydicom Dataset. If a bystestring, the content - is converted into a Dataset on initialization. If None, a new empty - Dataset is created. - parent_hdr : :class:`~nibabel.nifti1.Nifti1Header`, optional - If a dicom extension belongs to an existing - :class:`~nibabel.nifti1.Nifti1Header`, it may be provided here to - ensure that the DICOM dataset is written with correctly corresponding - endianness; otherwise it is assumed the dataset is little endian. - - Notes - ----- - - code should always be 2 for DICOM. - """ - - if code != 2: - raise ValueError(f'code must be 2 for DICOM. Got {code}.') - - if content is None: - content = pdcm.Dataset() - - if parent_hdr is not None: - self._is_little_endian = parent_hdr.endianness == '<' - - if isinstance(content, pdcm.dataset.Dataset): - super().__init__(code, object=content) - elif isinstance(content, bytes): # Got a byte string - unmangle it - self._is_implicit_VR = self._guess_implicit_VR(content) - super().__init__(code, content=content) - else: - raise TypeError( - f'content must be either a bytestring or a pydicom Dataset. ' - f'Got {content.__class__}' - ) - - @staticmethod - def _guess_implicit_VR(content) -> bool: - """Try to guess DICOM syntax by checking for valid VRs. - - Without a DICOM Transfer Syntax, it's difficult to tell if Value - Representations (VRs) are included in the DICOM encoding or not. - This reads where the first VR would be and checks it against a list of - valid VRs - """ - potential_vr = content[4:6].decode() - return potential_vr not in pdcm.values.converters.keys() - - def _unmangle(self, obj: bytes) -> DicomDataset: - return pdcm.filereader.read_dataset( - BytesIO(obj), - self._is_implicit_VR, - self._is_little_endian, - ) - - def _mangle(self, dataset: DicomDataset) -> bytes: - bio = BytesIO() - dio = pdcm.filebase.DicomFileLike(bio) - dio.is_implicit_VR = self._is_implicit_VR - dio.is_little_endian = self._is_little_endian - ds_len = pdcm.filewriter.write_dataset(dio, dataset) - dio.seek(0) - return dio.read(ds_len) - - -# NIfTI header extension type codes (ECODE) -# see nifti1_io.h for a complete list of all known extensions and -# references to their description or contacts of the respective -# initiators -extension_codes = Recoder( - ( - (0, 'ignore', Nifti1Extension), - (2, 'dicom', Nifti1DicomExtension if have_dicom else Nifti1Extension), - (4, 'afni', Nifti1Extension), - (6, 'comment', Nifti1Extension), - (8, 'xcede', Nifti1Extension), - (10, 'jimdiminfo', Nifti1Extension), - (12, 'workflow_fwds', Nifti1Extension), - (14, 'freesurfer', Nifti1Extension), - (16, 'pypickle', Nifti1Extension), - (18, 'mind_ident', NiftiExtension), - (20, 'b_value', NiftiExtension), - (22, 'spherical_direction', NiftiExtension), - (24, 'dt_component', NiftiExtension), - (26, 'shc_degreeorder', NiftiExtension), - (28, 'voxbo', NiftiExtension), - (30, 'caret', NiftiExtension), - ## Defined in nibabel.cifti2.parse_cifti2 - # (32, 'cifti', Cifti2Extension), - (34, 'variable_frame_timing', NiftiExtension), - (36, 'unassigned', NiftiExtension), - (38, 'eval', NiftiExtension), - (40, 'matlab', NiftiExtension), - (42, 'quantiphyse', NiftiExtension), - (44, 'mrs', Nifti1Extension), - ), - fields=('code', 'label', 'handler'), -) - - -class Nifti1Extensions(list): - """Simple extension collection, implemented as a list-subclass.""" - - def count(self, ecode): - """Returns the number of extensions matching a given *ecode*. - - Parameters - ---------- - code : int | str - The ecode can be specified either literal or as numerical value. - """ - count = 0 - code = extension_codes.code[ecode] - for e in self: - if e.get_code() == code: - count += 1 - return count - - def get_codes(self): - """Return a list of the extension code of all available extensions""" - return [e.get_code() for e in self] - - def get_sizeondisk(self): - """Return the size of the complete header extensions in the NIfTI file.""" - return np.sum([e.get_sizeondisk() for e in self]) - - def __repr__(self): - return 'Nifti1Extensions({})'.format(', '.join(str(e) for e in self)) - - def write_to(self, fileobj, byteswap): - """Write header extensions to fileobj - - Write starts at fileobj current file position. - - Parameters - ---------- - fileobj : file-like object - Should implement ``write`` method - byteswap : boolean - Flag if byteswapping the data is required. - - Returns - ------- - None - """ - for e in self: - e.write_to(fileobj, byteswap) - - @classmethod - def from_fileobj(klass, fileobj, size, byteswap): - """Read header extensions from a fileobj - - Parameters - ---------- - fileobj : file-like object - We begin reading the extensions at the current file position - size : int - Number of bytes to read. If negative, fileobj will be read till its - end. - byteswap : boolean - Flag if byteswapping the read data is required. - - Returns - ------- - An extension list. This list might be empty in case not extensions - were present in fileobj. - """ - # make empty extension list - extensions = klass() - # assume the file pointer is at the beginning of any extensions. - # read until the whole header is parsed (each extension is a multiple - # of 16 bytes) or in case of a separate header file till the end - # (break inside the body) - while size >= 16 or size < 0: - # the next 8 bytes should have esize and ecode - ext_def = fileobj.read(8) - # nothing was read and instructed to read till the end - # -> assume all extensions where parsed and break - if not len(ext_def) and size < 0: - break - # otherwise there should be a full extension header - if not len(ext_def) == 8: - raise HeaderDataError('failed to read extension header') - ext_def = np.frombuffer(ext_def, dtype=np.int32) - if byteswap: - ext_def = ext_def.byteswap() - # be extra verbose - ecode = ext_def[1] - esize = ext_def[0] - if esize % 16: - warnings.warn( - 'Extension size is not a multiple of 16 bytes; ' - 'Assuming size is correct and hoping for the best', - UserWarning, - ) - # read extension itself; esize includes the 8 bytes already read - evalue = fileobj.read(int(esize - 8)) - if not len(evalue) == esize - 8: - raise HeaderDataError('failed to read extension content') - # note that we read a full extension - size -= esize - # store raw extension content, but strip trailing NULL chars - evalue = evalue.rstrip(b'\x00') - # 'extension_codes' also knows the best implementation to handle - # a particular extension type - try: - ext = extension_codes.handler[ecode](ecode, evalue) - except KeyError: - # unknown extension type - # XXX complain or fail or go with a generic extension - ext = Nifti1Extension(ecode, evalue) - extensions.append(ext) - return extensions - - -class Nifti1Header(SpmAnalyzeHeader): - """Class for NIfTI1 header - - The NIfTI1 header has many more coded fields than the simpler Analyze - variants. NIfTI1 headers also have extensions. - - Nifti allows the header to be a separate file, as part of a nifti image / - header pair, or to precede the data in a single file. The object needs to - know which type it is, in order to manage the voxel offset pointing to the - data, extension reading, and writing the correct magic string. - - This class handles the header-preceding-data case. - """ - - # Copies of module level definitions - template_dtype = header_dtype - _data_type_codes = data_type_codes - - # fields with recoders for their values - _field_recoders = { - 'datatype': data_type_codes, - 'qform_code': xform_codes, - 'sform_code': xform_codes, - 'intent_code': intent_codes, - 'slice_code': slice_order_codes, - } - - # data scaling capabilities - has_data_slope = True - has_data_intercept = True - - # Extension class; should implement __call__ for construction, and - # ``from_fileobj`` for reading from file - exts_klass = Nifti1Extensions - - # Signal whether this is single (header + data) file - is_single = True - - # Default voxel data offsets for single and pair - pair_vox_offset = 0 - single_vox_offset = 352 - - # Magics for single and pair - pair_magic = b'ni1' - single_magic = b'n+1' - - # Quaternion threshold near 0, based on float32 precision - quaternion_threshold: np.floating = np.finfo(np.float32).eps * 3 - - def __init__(self, binaryblock=None, endianness=None, check=True, extensions=()): - """Initialize header from binary data block and extensions""" - super().__init__(binaryblock, endianness, check) - self.extensions = self.exts_klass(extensions) - - def copy(self): - """Return copy of header - - Take reference to extensions as well as copy of header contents - """ - return self.__class__(self.binaryblock, self.endianness, False, self.extensions) - - @classmethod - def from_fileobj(klass, fileobj, endianness=None, check=True): - raw_str = fileobj.read(klass.template_dtype.itemsize) - hdr = klass(raw_str, endianness, check) - # Read next 4 bytes to see if we have extensions. The nifti standard - # has this as a 4 byte string; if the first value is not zero, then we - # have extensions. - extension_status = fileobj.read(4) - # Need to test *slice* of extension_status to preserve byte string type - # on Python 3 - if len(extension_status) < 4 or extension_status[0:1] == b'\x00': - return hdr - # If this is a detached header file read to end - if not klass.is_single: - extsize = -1 - else: # otherwise read until the beginning of the data - extsize = hdr._structarr['vox_offset'] - fileobj.tell() - byteswap = endian_codes['native'] != hdr.endianness - hdr.extensions = klass.exts_klass.from_fileobj(fileobj, extsize, byteswap) - return hdr - - def write_to(self, fileobj): - # First check that vox offset is large enough; set if necessary - if self.is_single: - vox_offset = self._structarr['vox_offset'] - min_vox_offset = self.single_vox_offset + self.extensions.get_sizeondisk() - if vox_offset == 0: # vox offset unset; set as necessary - self._structarr['vox_offset'] = min_vox_offset - elif vox_offset < min_vox_offset: - raise HeaderDataError( - f'vox offset set to {vox_offset}, but need at least {min_vox_offset}' - ) - super().write_to(fileobj) - # Write extensions - if len(self.extensions) == 0: - # If single file, write required 0 stream to signal no extensions - if self.is_single: - fileobj.write(b'\x00' * 4) - return - # Signal there are extensions that follow - fileobj.write(b'\x01\x00\x00\x00') - byteswap = endian_codes['native'] != self.endianness - self.extensions.write_to(fileobj, byteswap) - - def get_best_affine(self): - """Select best of available transforms""" - hdr = self._structarr - if hdr['sform_code'] != 0: - return self.get_sform() - if hdr['qform_code'] != 0: - return self.get_qform() - return self.get_base_affine() - - @classmethod - def default_structarr(klass, endianness=None): - """Create empty header binary block with given endianness""" - hdr_data = super().default_structarr(endianness) - if klass.is_single: - hdr_data['magic'] = klass.single_magic - else: - hdr_data['magic'] = klass.pair_magic - return hdr_data - - @classmethod - def from_header(klass, header=None, check=True): - """Class method to create header from another header - - Extend Analyze header copy by copying extensions from other Nifti - types. - - Parameters - ---------- - header : ``Header`` instance or mapping - a header of this class, or another class of header for - conversion to this type - check : {True, False} - whether to check header for integrity - - Returns - ------- - hdr : header instance - fresh header instance of our own class - """ - new_hdr = super().from_header(header, check) - if isinstance(header, Nifti1Header): - new_hdr.extensions[:] = header.extensions[:] - return new_hdr - - def get_data_shape(self): - """Get shape of data - - Examples - -------- - >>> hdr = Nifti1Header() - >>> hdr.get_data_shape() - (0,) - >>> hdr.set_data_shape((1,2,3)) - >>> hdr.get_data_shape() - (1, 2, 3) - - Expanding number of dimensions gets default zooms - - >>> hdr.get_zooms() - (1.0, 1.0, 1.0) - - Notes - ----- - Applies freesurfer hack for large vectors described in `issue 100`_ and - `save_nifti.m `_. - - Allows for freesurfer hack for 7th order icosahedron surface described - in `issue 309`_, load_nifti.m_, and `save_nifti.m `_. - """ - shape = super().get_data_shape() - # Apply freesurfer hack for large vectors - if shape[:3] == (-1, 1, 1): - vec_len = int(self._structarr['glmin']) - if vec_len == 0: - raise HeaderDataError( - '-1 in dim[1] but 0 in glmin; inconsistent freesurfer type header?' - ) - return (vec_len, 1, 1) + shape[3:] - # Apply freesurfer hack for ico7 surface - elif shape[:3] == (27307, 1, 6): - return (163842, 1, 1) + shape[3:] - else: # Normal case - return shape - - def set_data_shape(self, shape): - """Set shape of data # noqa - - If ``ndims == len(shape)`` then we set zooms for dimensions higher than - ``ndims`` to 1.0 - - Nifti1 images can have up to seven dimensions. For FreeSurfer-variant - Nifti surface files, the first dimension is assumed to correspond to - vertices/nodes on a surface, and dimensions two and three are - constrained to have depth of 1. Dimensions 4-7 are constrained only by - type bounds. - - Parameters - ---------- - shape : sequence - sequence of integers specifying data array shape - - Notes - ----- - Applies freesurfer hack for large vectors described in `issue 100`_ and - `save_nifti.m `_. - - Allows for freesurfer hack for 7th order icosahedron surface described - in `issue 309`_, load_nifti.m_, and `save_nifti.m `_. - - The Nifti1 `standard header`_ allows for the following "point set" - definition of a surface, not currently implemented in nibabel. - - :: - - To signify that the vector value at each voxel is really a - spatial coordinate (e.g., the vertices or nodes of a surface mesh): - - dataset must have a 5th dimension - - intent_code must be NIFTI_INTENT_POINTSET - - dim[0] = 5 - - dim[1] = number of points - - dim[2] = dim[3] = dim[4] = 1 - - dim[5] must be the dimensionality of space (e.g., 3 => 3D space). - - intent_name may describe the object these points come from - (e.g., "pial", "gray/white" , "EEG", "MEG"). - - .. _issue 100: https://github.com/nipy/nibabel/issues/100 - .. _issue 309: https://github.com/nipy/nibabel/issues/309 - .. _save77: - https://github.com/fieldtrip/fieldtrip/blob/428798b/external/freesurfer/save_nifti.m#L77-L82 - .. _save50: - https://github.com/fieldtrip/fieldtrip/blob/428798b/external/freesurfer/save_nifti.m#L50-L56 - .. _load_nifti.m: - https://github.com/fieldtrip/fieldtrip/blob/428798b/external/freesurfer/load_nifti.m#L86-L89 - .. _standard header: http://nifti.nimh.nih.gov/pub/dist/src/niftilib/nifti1.h - """ - hdr = self._structarr - shape = tuple(shape) - - # Apply freesurfer hack for ico7 surface - if shape[:3] == (163842, 1, 1): - shape = (27307, 1, 6) + shape[3:] - # Apply freesurfer hack for large vectors - elif ( - len(shape) >= 3 - and shape[1:3] == (1, 1) - and shape[0] > np.iinfo(hdr['dim'].dtype.base).max - ): - try: - hdr['glmin'] = shape[0] - except OverflowError: - overflow = True - else: - overflow = hdr['glmin'] != shape[0] - if overflow: - raise HeaderDataError(f'shape[0] {shape[0]} does not fit in glmax datatype') - warnings.warn( - 'Using large vector Freesurfer hack; header will ' - 'not be compatible with SPM or FSL', - stacklevel=2, - ) - shape = (-1, 1, 1) + shape[3:] - super().set_data_shape(shape) - - def set_data_dtype(self, datatype): - """Set numpy dtype for data from code or dtype or type - - Using :py:class:`int` or ``"int"`` is disallowed, as these types - will be interpreted as ``np.int64``, which is almost never desired. - ``np.int64`` is permitted for those intent on making poor choices. - - Examples - -------- - >>> hdr = Nifti1Header() - >>> hdr.set_data_dtype(np.uint8) - >>> hdr.get_data_dtype() - dtype('uint8') - >>> hdr.set_data_dtype(np.dtype(np.uint8)) - >>> hdr.get_data_dtype() - dtype('uint8') - >>> hdr.set_data_dtype('implausible') - Traceback (most recent call last): - ... - nibabel.spatialimages.HeaderDataError: data dtype "implausible" not recognized - >>> hdr.set_data_dtype('none') - Traceback (most recent call last): - ... - nibabel.spatialimages.HeaderDataError: data dtype "none" known but not supported - >>> hdr.set_data_dtype(np.void) - Traceback (most recent call last): - ... - nibabel.spatialimages.HeaderDataError: data dtype "" known - but not supported - >>> hdr.set_data_dtype('int') - Traceback (most recent call last): - ... - ValueError: Invalid data type 'int'. Specify a sized integer, e.g., 'uint8' or numpy.int16. - >>> hdr.set_data_dtype(int) - Traceback (most recent call last): - ... - ValueError: Invalid data type . Specify a sized integer, e.g., 'uint8' or - numpy.int16. - >>> hdr.set_data_dtype('int64') - >>> hdr.get_data_dtype() == np.dtype('int64') - True - """ - if not isinstance(datatype, np.dtype) and datatype in (int, 'int'): - raise ValueError( - f'Invalid data type {datatype!r}. Specify a sized integer, ' - "e.g., 'uint8' or numpy.int16." - ) - super().set_data_dtype(datatype) - - def get_qform_quaternion(self): - """Compute quaternion from b, c, d of quaternion - - Fills a value by assuming this is a unit quaternion - """ - hdr = self._structarr - bcd = [hdr['quatern_b'], hdr['quatern_c'], hdr['quatern_d']] - # Adjust threshold to precision of stored values in header - return fillpositive(bcd, self.quaternion_threshold) - - def get_qform(self, coded=False): - """Return 4x4 affine matrix from qform parameters in header - - Parameters - ---------- - coded : bool, optional - If True, return {affine or None}, and qform code. If False, just - return affine. {affine or None} means, return None if qform code - == 0, and affine otherwise. - - Returns - ------- - affine : None or (4,4) ndarray - If `coded` is False, always return affine reconstructed from qform - quaternion. If `coded` is True, return None if qform code is 0, - else return the affine. - code : int - Qform code. Only returned if `coded` is True. - """ - hdr = self._structarr - code = int(hdr['qform_code']) - if code == 0 and coded: - return None, 0 - quat = self.get_qform_quaternion() - R = quat2mat(quat) - vox = hdr['pixdim'][1:4].copy() - if np.any(vox < 0): - raise HeaderDataError('pixdims[1,2,3] should be positive') - qfac = hdr['pixdim'][0] - if qfac not in (-1, 1): - raise HeaderDataError('qfac (pixdim[0]) should be 1 or -1') - vox[-1] *= qfac - S = np.diag(vox) - M = np.dot(R, S) - out = np.eye(4) - out[0:3, 0:3] = M - out[0:3, 3] = [hdr['qoffset_x'], hdr['qoffset_y'], hdr['qoffset_z']] - if coded: - return out, code - return out - - def set_qform(self, affine, code=None, strip_shears=True): - """Set qform header values from 4x4 affine - - Parameters - ---------- - affine : None or 4x4 array - affine transform to write into sform. If None, only set code. - code : None, string or integer, optional - String or integer giving meaning of transform in *affine*. - The default is None. If code is None, then: - - * If affine is None, `code`-> 0 - * If affine not None and existing qform code in header == 0, - `code`-> 2 (aligned) - * If affine not None and existing qform code in header != 0, - `code`-> existing qform code in header - - strip_shears : bool, optional - Whether to strip shears in `affine`. If True, shears will be - silently stripped. If False, the presence of shears will raise a - ``HeaderDataError`` - - Notes - ----- - The qform transform only encodes translations, rotations and - zooms. If there are shear components to the `affine` transform, and - `strip_shears` is True (the default), the written qform gives the - closest approximation where the rotation matrix is orthogonal. This is - to allow quaternion representation. The orthogonal representation - enforces orthogonal axes. - - Examples - -------- - >>> hdr = Nifti1Header() - >>> int(hdr['qform_code']) # gives 0 - unknown - 0 - >>> affine = np.diag([1,2,3,1]) - >>> np.all(hdr.get_qform() == affine) - False - >>> hdr.set_qform(affine) - >>> np.all(hdr.get_qform() == affine) - True - >>> int(hdr['qform_code']) # gives 2 - aligned - 2 - >>> hdr.set_qform(affine, code='talairach') - >>> int(hdr['qform_code']) - 3 - >>> hdr.set_qform(affine, code=None) - >>> int(hdr['qform_code']) - 3 - >>> hdr.set_qform(affine, code='scanner') - >>> int(hdr['qform_code']) - 1 - >>> hdr.set_qform(None) - >>> int(hdr['qform_code']) - 0 - """ - hdr = self._structarr - old_code = hdr['qform_code'] - if code is None: - if affine is None: - code = 0 - elif old_code == 0: - code = 2 # aligned - else: - code = old_code - else: # code set - code = self._field_recoders['qform_code'][code] - hdr['qform_code'] = code - if affine is None: - return - affine = np.asarray(affine) - if not affine.shape == (4, 4): - raise TypeError('Need 4x4 affine as input') - trans = affine[:3, 3] - RZS = affine[:3, :3] - zooms = np.sqrt(np.sum(RZS * RZS, axis=0)) - R = RZS / zooms - # Set qfac to make R determinant positive - if npl.det(R) > 0: - qfac = 1 - else: - qfac = -1 - R[:, -1] *= -1 - # Make R orthogonal (to allow quaternion representation) - # The orthogonal representation enforces orthogonal axes - # (a subtle requirement of the NIFTI format qform transform) - # Transform below is polar decomposition, returning the closest - # orthogonal matrix PR, to input R - try: - P, S, Qs = npl.svd(R) - except np.linalg.LinAlgError as e: - raise HeaderDataError(f'Could not decompose affine:\n{affine}') from e - PR = np.dot(P, Qs) - if not strip_shears and not np.allclose(PR, R): - raise HeaderDataError('Shears in affine and `strip_shears` is False') - # Convert to quaternion - quat = mat2quat(PR) - # Set into header - hdr['qoffset_x'], hdr['qoffset_y'], hdr['qoffset_z'] = trans - hdr['pixdim'][0] = qfac - hdr['pixdim'][1:4] = zooms - hdr['quatern_b'], hdr['quatern_c'], hdr['quatern_d'] = quat[1:] - - def get_sform(self, coded=False): - """Return 4x4 affine matrix from sform parameters in header - - Parameters - ---------- - coded : bool, optional - If True, return {affine or None}, and sform code. If False, just - return affine. {affine or None} means, return None if sform code - == 0, and affine otherwise. - - Returns - ------- - affine : None or (4,4) ndarray - If `coded` is False, always return affine from sform fields. If - `coded` is True, return None if sform code is 0, else return the - affine. - code : int - Sform code. Only returned if `coded` is True. - """ - hdr = self._structarr - code = int(hdr['sform_code']) - if code == 0 and coded: - return None, 0 - out = np.eye(4) - out[0, :] = hdr['srow_x'][:] - out[1, :] = hdr['srow_y'][:] - out[2, :] = hdr['srow_z'][:] - if coded: - return out, code - return out - - def set_sform(self, affine, code=None): - """Set sform transform from 4x4 affine - - Parameters - ---------- - affine : None or 4x4 array - affine transform to write into sform. If None, only set `code` - code : None, string or integer, optional - String or integer giving meaning of transform in *affine*. - The default is None. If code is None, then: - - * If affine is None, `code`-> 0 - * If affine not None and existing sform code in header == 0, - `code`-> 2 (aligned) - * If affine not None and existing sform code in header != 0, - `code`-> existing sform code in header - - Examples - -------- - >>> hdr = Nifti1Header() - >>> int(hdr['sform_code']) # gives 0 - unknown - 0 - >>> affine = np.diag([1,2,3,1]) - >>> np.all(hdr.get_sform() == affine) - False - >>> hdr.set_sform(affine) - >>> np.all(hdr.get_sform() == affine) - True - >>> int(hdr['sform_code']) # gives 2 - aligned - 2 - >>> hdr.set_sform(affine, code='talairach') - >>> int(hdr['sform_code']) - 3 - >>> hdr.set_sform(affine, code=None) - >>> int(hdr['sform_code']) - 3 - >>> hdr.set_sform(affine, code='scanner') - >>> int(hdr['sform_code']) - 1 - >>> hdr.set_sform(None) - >>> int(hdr['sform_code']) - 0 - """ - hdr = self._structarr - old_code = hdr['sform_code'] - if code is None: - if affine is None: - code = 0 - elif old_code == 0: - code = 2 # aligned - else: - code = old_code - else: # code set - code = self._field_recoders['sform_code'][code] - hdr['sform_code'] = code - if affine is None: - return - affine = np.asarray(affine) - hdr['srow_x'][:] = affine[0, :] - hdr['srow_y'][:] = affine[1, :] - hdr['srow_z'][:] = affine[2, :] - - def get_slope_inter(self): - """Get data scaling (slope) and DC offset (intercept) from header data - - Returns - ------- - slope : None or float - scaling (slope). None if there is no valid scaling from these - fields - inter : None or float - offset (intercept). None if there is no valid scaling or if offset - is not finite. - - Examples - -------- - >>> hdr = Nifti1Header() - >>> hdr.get_slope_inter() - (1.0, 0.0) - >>> hdr['scl_slope'] = 0 - >>> hdr.get_slope_inter() - (None, None) - >>> hdr['scl_slope'] = np.nan - >>> hdr.get_slope_inter() - (None, None) - >>> hdr['scl_slope'] = 1 - >>> hdr['scl_inter'] = 1 - >>> hdr.get_slope_inter() - (1.0, 1.0) - >>> hdr['scl_inter'] = np.inf - >>> hdr.get_slope_inter() #doctest: +IGNORE_EXCEPTION_DETAIL - Traceback (most recent call last): - ... - HeaderDataError: Valid slope but invalid intercept inf - """ - # Note that we are returning float (float64) scalefactors and - # intercepts, although they are stored as in nifti1 as float32. - slope = float(self['scl_slope']) - inter = float(self['scl_inter']) - if slope == 0 or not np.isfinite(slope): - return None, None - if not np.isfinite(inter): - raise HeaderDataError(f'Valid slope but invalid intercept {inter}') - return slope, inter - - def set_slope_inter(self, slope, inter=None): - """Set slope and / or intercept into header - - Set slope and intercept for image data, such that, if the image - data is ``arr``, then the scaled image data will be ``(arr * - slope) + inter`` - - (`slope`, `inter`) of (NaN, NaN) is a signal to a containing image to - set `slope`, `inter` automatically on write. - - Parameters - ---------- - slope : None or float - If None, implies `slope` of NaN. If `slope` is None or NaN then - `inter` should be None or NaN. Values of 0, Inf or -Inf raise - HeaderDataError - inter : None or float, optional - Intercept. If None, implies `inter` of NaN. If `slope` is None or - NaN then `inter` should be None or NaN. Values of Inf or -Inf raise - HeaderDataError - """ - if slope is None: - slope = np.nan - if inter is None: - inter = np.nan - if slope in (0, np.inf, -np.inf): - raise HeaderDataError('Slope cannot be 0 or infinite') - if inter in (np.inf, -np.inf): - raise HeaderDataError('Intercept cannot be infinite') - if np.isnan(slope) ^ np.isnan(inter): - raise HeaderDataError('None or both of slope, inter should be nan') - self._structarr['scl_slope'] = slope - self._structarr['scl_inter'] = inter - - def get_dim_info(self): - """Gets NIfTI MRI slice etc dimension information - - Returns - ------- - freq : {None,0,1,2} - Which data array axis is frequency encode direction - phase : {None,0,1,2} - Which data array axis is phase encode direction - slice : {None,0,1,2} - Which data array axis is slice encode direction - - where ``data array`` is the array returned by ``get_data`` - - Because NIfTI1 files are natively Fortran indexed: - 0 is fastest changing in file - 1 is medium changing in file - 2 is slowest changing in file - - ``None`` means the axis appears not to be specified. - - Examples - -------- - See set_dim_info function - - """ - hdr = self._structarr - info = int(hdr['dim_info']) - freq = info & 3 - phase = (info >> 2) & 3 - slice = (info >> 4) & 3 - return ( - freq - 1 if freq else None, - phase - 1 if phase else None, - slice - 1 if slice else None, - ) - - def set_dim_info(self, freq=None, phase=None, slice=None): - """Sets nifti MRI slice etc dimension information - - Parameters - ---------- - freq : {None, 0, 1, 2} - axis of data array referring to frequency encoding - phase : {None, 0, 1, 2} - axis of data array referring to phase encoding - slice : {None, 0, 1, 2} - axis of data array referring to slice encoding - - ``None`` means the axis is not specified. - - Examples - -------- - >>> hdr = Nifti1Header() - >>> hdr.set_dim_info(1, 2, 0) - >>> hdr.get_dim_info() - (1, 2, 0) - >>> hdr.set_dim_info(freq=1, phase=2, slice=0) - >>> hdr.get_dim_info() - (1, 2, 0) - >>> hdr.set_dim_info() - >>> hdr.get_dim_info() - (None, None, None) - >>> hdr.set_dim_info(freq=1, phase=None, slice=0) - >>> hdr.get_dim_info() - (1, None, 0) - - Notes - ----- - This is stored in one byte in the header - """ - for inp in (freq, phase, slice): - # Don't use == on None to avoid a FutureWarning in python3 - if inp is not None and inp not in (0, 1, 2): - raise HeaderDataError('Inputs must be in [None, 0, 1, 2]') - info = 0 - if freq is not None: - info = info | ((freq + 1) & 3) - if phase is not None: - info = info | (((phase + 1) & 3) << 2) - if slice is not None: - info = info | (((slice + 1) & 3) << 4) - self._structarr['dim_info'] = info - - def get_intent(self, code_repr='label'): - """Get intent code, parameters and name - - Parameters - ---------- - code_repr : string - string giving output form of intent code representation. - Default is 'label'; use 'code' for integer representation. - - Returns - ------- - code : string or integer - intent code, or string describing code - parameters : tuple - parameters for the intent - name : string - intent name - - Examples - -------- - >>> hdr = Nifti1Header() - >>> hdr.set_intent('t test', (10,), name='some score') - >>> hdr.get_intent() - ('t test', (10.0,), 'some score') - >>> hdr.get_intent('code') - (3, (10.0,), 'some score') - """ - hdr = self._structarr - recoder = self._field_recoders['intent_code'] - code = int(hdr['intent_code']) - known_intent = code in recoder - if code_repr == 'code': - label = code - elif code_repr == 'label': - if known_intent: - label = recoder.label[code] - else: - label = 'unknown code ' + str(code) - else: - raise TypeError('repr can be "label" or "code"') - n_params = len(recoder.parameters[code]) if known_intent else 0 - params = (float(hdr[f'intent_p{i}']) for i in range(1, n_params + 1)) - name = hdr['intent_name'].item().decode('latin-1') - return label, tuple(params), name - - def set_intent(self, code, params=(), name='', allow_unknown=False): - """Set the intent code, parameters and name - - If parameters are not specified, assumed to be all zero. Each - intent code has a set number of parameters associated. If you - specify any parameters, then it will need to be the correct number - (e.g the "f test" intent requires 2). However, parameters can - also be set in the file data, so we also allow not setting any - parameters (empty parameter tuple). - - Parameters - ---------- - code : integer or string - code specifying nifti intent - params : list, tuple of scalars - parameters relating to intent (see intent_codes) - defaults to (). Unspecified parameters are set to 0.0 - name : string - intent name (description). Defaults to '' - allow_unknown : {False, True}, optional - Allow unknown integer intent codes. If False (the default), - a KeyError is raised on attempts to set the intent - to an unknown code. - - Returns - ------- - None - - Examples - -------- - >>> hdr = Nifti1Header() - >>> hdr.set_intent(0) # no intent - >>> hdr.set_intent('z score') - >>> hdr.get_intent() - ('z score', (), '') - >>> hdr.get_intent('code') - (5, (), '') - >>> hdr.set_intent('t test', (10,), name='some score') - >>> hdr.get_intent() - ('t test', (10.0,), 'some score') - >>> hdr.set_intent('f test', (2, 10), name='another score') - >>> hdr.get_intent() - ('f test', (2.0, 10.0), 'another score') - >>> hdr.set_intent('f test') - >>> hdr.get_intent() - ('f test', (0.0, 0.0), '') - >>> hdr.set_intent(9999, allow_unknown=True) # unknown code - >>> hdr.get_intent() - ('unknown code 9999', (), '') - """ - hdr = self._structarr - known_intent = code in intent_codes - if not known_intent: - # We can set intent via an unknown integer code, but can't via an - # unknown string label - if not allow_unknown or isinstance(code, str): - raise KeyError('Unknown intent code: ' + str(code)) - if known_intent: - icode = intent_codes.code[code] - p_descr = intent_codes.parameters[code] - else: - icode = code - p_descr = ('p1', 'p2', 'p3') - if len(params) and len(params) != len(p_descr): - raise HeaderDataError(f'Need params of form {p_descr}, or empty') - hdr['intent_code'] = icode - hdr['intent_name'] = name - all_params = [0] * 3 - all_params[: len(params)] = params[:] - for i, param in enumerate(all_params, start=1): - hdr[f'intent_p{i}'] = param - - def get_slice_duration(self): - """Get slice duration - - Returns - ------- - slice_duration : float - time to acquire one slice - - Examples - -------- - >>> hdr = Nifti1Header() - >>> hdr.set_dim_info(slice=2) - >>> hdr.set_slice_duration(0.3) - >>> print("%0.1f" % hdr.get_slice_duration()) - 0.3 - - Notes - ----- - The NIfTI1 spec appears to require the slice dimension to be - defined for slice_duration to have meaning. - """ - _, _, slice_dim = self.get_dim_info() - if slice_dim is None: - raise HeaderDataError('Slice dimension must be set for duration to be valid') - return float(self._structarr['slice_duration']) - - def set_slice_duration(self, duration): - """Set slice duration - - Parameters - ---------- - duration : scalar - time to acquire one slice - - Examples - -------- - See ``get_slice_duration`` - """ - _, _, slice_dim = self.get_dim_info() - if slice_dim is None: - raise HeaderDataError('Slice dimension must be set for duration to be valid') - self._structarr['slice_duration'] = duration - - def get_n_slices(self): - """Return the number of slices""" - _, _, slice_dim = self.get_dim_info() - if slice_dim is None: - raise HeaderDataError('Slice dimension not set in header dim_info') - shape = self.get_data_shape() - try: - slice_len = shape[slice_dim] - except IndexError: - raise HeaderDataError( - f'Slice dimension index ({slice_dim}) outside shape tuple ({shape})' - ) - return slice_len - - def get_slice_times(self): - """Get slice times from slice timing information - - Returns - ------- - slice_times : tuple - Times of acquisition of slices, where 0 is the beginning of - the acquisition, ordered by position in file. nifti allows - slices at the top and bottom of the volume to be excluded from - the standard slice timing specification, and calls these - "padding slices". We give padding slices ``None`` as a time - of acquisition - - Examples - -------- - >>> hdr = Nifti1Header() - >>> hdr.set_dim_info(slice=2) - >>> hdr.set_data_shape((1, 1, 7)) - >>> hdr.set_slice_duration(0.1) - >>> hdr['slice_code'] = slice_order_codes['sequential increasing'] - >>> slice_times = hdr.get_slice_times() - >>> np.allclose(slice_times, [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6]) - True - """ - hdr = self._structarr - slice_len = self.get_n_slices() - duration = self.get_slice_duration() - slabel = self.get_value_label('slice_code') - if slabel == 'unknown': - raise HeaderDataError('Cannot get slice times when slice code is "unknown"') - slice_start, slice_end = (int(hdr['slice_start']), int(hdr['slice_end'])) - if slice_start < 0: - raise HeaderDataError('slice_start should be >= 0') - if slice_end == 0: - slice_end = slice_len - 1 - n_timed = slice_end - slice_start + 1 - if n_timed < 1: - raise HeaderDataError('slice_end should be > slice_start') - st_order = self._slice_time_order(slabel, n_timed) - times = st_order * duration - return (None,) * slice_start + tuple(times) + (None,) * (slice_len - slice_end - 1) - - def set_slice_times(self, slice_times): - """Set slice times into *hdr* - - Parameters - ---------- - slice_times : tuple - tuple of slice times, one value per slice - tuple can include None to indicate no slice time for that slice - - Examples - -------- - >>> hdr = Nifti1Header() - >>> hdr.set_dim_info(slice=2) - >>> hdr.set_data_shape([1, 1, 7]) - >>> hdr.set_slice_duration(0.1) - >>> times = [None, 0.2, 0.4, 0.1, 0.3, 0.0, None] - >>> hdr.set_slice_times(times) - >>> hdr.get_value_label('slice_code') - 'alternating decreasing' - >>> int(hdr['slice_start']) - 1 - >>> int(hdr['slice_end']) - 5 - """ - # Check if number of slices matches header - hdr = self._structarr - slice_len = self.get_n_slices() - if slice_len != len(slice_times): - raise HeaderDataError('Number of slice times does not match number of slices') - # Extract Nones at beginning and end. Check for others - for ind, time in enumerate(slice_times): - if time is not None: - slice_start = ind - break - else: - raise HeaderDataError('Not all slice times can be None') - for ind, time in enumerate(slice_times[::-1]): - if time is not None: - slice_end = slice_len - ind - 1 - break - timed = slice_times[slice_start : slice_end + 1] - for time in timed: - if time is None: - raise HeaderDataError('Cannot have None in middle of slice time vector') - # Find slice duration, check times are compatible with single - # duration - tdiffs = np.diff(np.sort(timed)) - if not np.allclose(np.diff(tdiffs), 0): - raise HeaderDataError('Slice times not compatible with single slice duration') - duration = np.mean(tdiffs) - # To slice time order - st_order = np.round(np.array(timed) / duration) - # Check if slice times fit known schemes - n_timed = len(timed) - so_recoder = self._field_recoders['slice_code'] - labels = so_recoder.value_set('label') - labels.remove('unknown') - - matching_labels = [ - label for label in labels if np.all(st_order == self._slice_time_order(label, n_timed)) - ] - - if not matching_labels: - raise HeaderDataError(f'slice ordering of {st_order} fits with no known scheme') - if len(matching_labels) > 1: - warnings.warn( - f'Multiple slice orders satisfy: {", ".join(matching_labels)}. ' - 'Choosing the first one' - ) - label = matching_labels[0] - # Set values into header - hdr['slice_start'] = slice_start - hdr['slice_end'] = slice_end - hdr['slice_duration'] = duration - hdr['slice_code'] = slice_order_codes.code[label] - - def _slice_time_order(self, slabel, n_slices): - """Supporting function to give time order of slices from label""" - if slabel == 'sequential increasing': - sp_ind_time_order = list(range(n_slices)) - elif slabel == 'sequential decreasing': - sp_ind_time_order = list(range(n_slices)[::-1]) - elif slabel == 'alternating increasing': - sp_ind_time_order = list(range(0, n_slices, 2)) + list(range(1, n_slices, 2)) - elif slabel == 'alternating decreasing': - sp_ind_time_order = list(range(n_slices - 1, -1, -2)) + list( - range(n_slices - 2, -1, -2) - ) - elif slabel == 'alternating increasing 2': - sp_ind_time_order = list(range(1, n_slices, 2)) + list(range(0, n_slices, 2)) - elif slabel == 'alternating decreasing 2': - sp_ind_time_order = list(range(n_slices - 2, -1, -2)) + list( - range(n_slices - 1, -1, -2) - ) - else: - raise HeaderDataError(f'We do not handle slice ordering "{slabel}"') - return np.argsort(sp_ind_time_order) - - def get_xyzt_units(self): - xyz_code = self.structarr['xyzt_units'] % 8 - t_code = self.structarr['xyzt_units'] - xyz_code - return (unit_codes.label[xyz_code], unit_codes.label[t_code]) - - def set_xyzt_units(self, xyz=None, t=None): - if xyz is None: - xyz = 0 - if t is None: - t = 0 - xyz_code = self.structarr['xyzt_units'] % 8 - t_code = self.structarr['xyzt_units'] - xyz_code - xyz_code = unit_codes[xyz] - t_code = unit_codes[t] - self.structarr['xyzt_units'] = xyz_code + t_code - - def _clean_after_mapping(self): - """Set format-specific stuff after converting header from mapping - - Clean up header after it has been initialized from an - ``as_analyze_map`` method of another header type - - See :meth:`nibabel.analyze.AnalyzeHeader._clean_after_mapping` for a - more detailed description. - """ - self._structarr['magic'] = self.single_magic if self.is_single else self.pair_magic - - """ Checks only below here """ - - @classmethod - def _get_checks(klass): - # We need to return our own versions of - e.g. chk_datatype, to - # pick up the Nifti datatypes from our class - return ( - klass._chk_sizeof_hdr, - klass._chk_datatype, - klass._chk_bitpix, - klass._chk_pixdims, - klass._chk_qfac, - klass._chk_magic, - klass._chk_offset, - klass._chk_qform_code, - klass._chk_sform_code, - ) - - @staticmethod - def _chk_qfac(hdr, fix=False): - rep = Report(HeaderDataError) - if hdr['pixdim'][0] in (-1, 1): - return hdr, rep - rep.problem_level = 20 - rep.problem_msg = 'pixdim[0] (qfac) should be 1 (default) or -1' - if fix: - hdr['pixdim'][0] = 1 - rep.fix_msg = 'setting qfac to 1' - return hdr, rep - - @staticmethod - def _chk_magic(hdr, fix=False): - rep = Report(HeaderDataError) - magic = hdr['magic'].item() - if magic in (hdr.pair_magic, hdr.single_magic): - return hdr, rep - rep.problem_msg = f'magic string {magic.decode("latin1")!r} is not valid' - rep.problem_level = 45 - if fix: - rep.fix_msg = 'leaving as is, but future errors are likely' - return hdr, rep - - @staticmethod - def _chk_offset(hdr, fix=False): - rep = Report(HeaderDataError) - # for ease of later string formatting, use scalar of byte string - magic = hdr['magic'].item() - offset = hdr['vox_offset'].item() - if offset == 0: - return hdr, rep - if magic == hdr.single_magic and offset < hdr.single_vox_offset: - rep.problem_level = 40 - rep.problem_msg = f'vox offset {int(offset)} too low for single file nifti1' - if fix: - hdr['vox_offset'] = hdr.single_vox_offset - rep.fix_msg = f'setting to minimum value of {hdr.single_vox_offset}' - return hdr, rep - if not offset % 16: - return hdr, rep - # SPM uses memory mapping to read the data, and - # apparently this has to start on 16 byte boundaries - rep.problem_msg = f'vox offset (={offset:g}) not divisible by 16, not SPM compatible' - rep.problem_level = 30 - if fix: - rep.fix_msg = 'leaving at current value' - return hdr, rep - - @classmethod - def _chk_qform_code(klass, hdr, fix=False): - return klass._chk_xform_code('qform_code', hdr, fix) - - @classmethod - def _chk_sform_code(klass, hdr, fix=False): - return klass._chk_xform_code('sform_code', hdr, fix) - - @classmethod - def _chk_xform_code(klass, code_type, hdr, fix): - # utility method for sform and qform codes - rep = Report(HeaderDataError) - code = int(hdr[code_type]) - recoder = klass._field_recoders[code_type] - if code in recoder.value_set(): - return hdr, rep - rep.problem_level = 30 - rep.problem_msg = f'{code_type} {code} not valid' - if fix: - hdr[code_type] = 0 - rep.fix_msg = 'setting to 0' - return hdr, rep - - @classmethod - def may_contain_header(klass, binaryblock): - if len(binaryblock) < klass.sizeof_hdr: - return False - - hdr_struct = np.ndarray( - shape=(), dtype=header_dtype, buffer=binaryblock[: klass.sizeof_hdr] - ) - return hdr_struct['magic'] in (b'ni1', b'n+1') - - -class Nifti1PairHeader(Nifti1Header): - """Class for NIfTI1 pair header""" - - # Signal whether this is single (header + data) file - is_single = False - - -class Nifti1Pair(analyze.AnalyzeImage): - """Class for NIfTI1 format image, header pair""" - - header_class: type[Nifti1Header] = Nifti1PairHeader - header: Nifti1Header - _meta_sniff_len = header_class.sizeof_hdr - rw = True - - # If a _dtype_alias has been set, it can only be resolved by inspecting - # the data at serialization time - _dtype_alias = None - - def __init__(self, dataobj, affine, header=None, extra=None, file_map=None, dtype=None): - # Special carve-out for 64 bit integers - # See GitHub issues - # * https://github.com/nipy/nibabel/issues/1046 - # * https://github.com/nipy/nibabel/issues/1089 - # This only applies to NIfTI because the parent Analyze formats did - # not support 64-bit integer data, so `set_data_dtype(int64)` would - # already fail. - danger_dts = (np.dtype('int64'), np.dtype('uint64')) - if header is None and dtype is None and get_obj_dtype(dataobj) in danger_dts: - alert_future_error( - f'Image data has type {dataobj.dtype}, which may cause ' - 'incompatibilities with other tools.', - '5.0', - warning_rec='This warning can be silenced by passing the dtype argument' - f' to {self.__class__.__name__}().', - error_rec='To use this type, pass an explicit header or dtype argument' - f' to {self.__class__.__name__}().', - error_class=ValueError, - ) - super().__init__(dataobj, affine, header, extra, file_map, dtype) - # Force set of s/q form when header is None unless affine is also None - if header is None and affine is not None: - self._affine2header() - - # Copy docstring - __init__.__doc__ = f"""{analyze.AnalyzeImage.__init__.__doc__} - Notes - ----- - - If both a `header` and an `affine` are specified, and the `affine` does - not match the affine that is in the `header`, the `affine` will be used, - but the ``sform_code`` and ``qform_code`` fields in the header will be - re-initialised to their default values. This is performed on the basis - that, if you are changing the affine, you are likely to be changing the - space to which the affine is pointing. The :meth:`set_sform` and - :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 - - See AnalyzeImage.update_header for more examples - - Examples - -------- - >>> data = np.zeros((2,3,4)) - >>> affine = np.diag([1.0,2.0,3.0,1.0]) - >>> img = Nifti1Image(data, affine) - >>> hdr = img.header - >>> np.all(hdr.get_qform() == affine) - True - >>> np.all(hdr.get_sform() == affine) - True - """ - super().update_header() - hdr = self._header - hdr['magic'] = hdr.pair_magic - - def _affine2header(self): - """Unconditionally set affine into the header""" - hdr = self._header - # Set affine into sform with default code - hdr.set_sform(self._affine, code='aligned') - # Make qform 'unknown' - hdr.set_qform(self._affine, code='unknown') - - def get_qform(self, coded=False): - """Return 4x4 affine matrix from qform parameters in header - - Parameters - ---------- - coded : bool, optional - If True, return {affine or None}, and qform code. If False, just - return affine. {affine or None} means, return None if qform code - == 0, and affine otherwise. - - Returns - ------- - affine : None or (4,4) ndarray - If `coded` is False, always return affine reconstructed from qform - quaternion. If `coded` is True, return None if qform code is 0, - else return the affine. - code : int - Qform code. Only returned if `coded` is True. - - See also - -------- - set_qform - get_sform - """ - return self._header.get_qform(coded) - - def set_qform(self, affine, code=None, strip_shears=True, **kwargs): - """Set qform header values from 4x4 affine - - Parameters - ---------- - affine : None or 4x4 array - affine transform to write into sform. If None, only set code. - code : None, string or integer - String or integer giving meaning of transform in *affine*. - The default is None. If code is None, then: - - * If affine is None, `code`-> 0 - * If affine not None and existing qform code in header == 0, - `code`-> 2 (aligned) - * If affine not None and existing qform code in header != 0, - `code`-> existing qform code in header - - strip_shears : bool, optional - Whether to strip shears in `affine`. If True, shears will be - silently stripped. If False, the presence of shears will raise a - ``HeaderDataError`` - update_affine : bool, optional - Whether to update the image affine from the header best affine - after setting the qform. Must be keyword argument (because of - different position in `set_qform`). Default is True - - See also - -------- - get_qform - set_sform - - Examples - -------- - >>> data = np.arange(24, dtype='f4').reshape((2,3,4)) - >>> aff = np.diag([2, 3, 4, 1]) - >>> img = Nifti1Pair(data, aff) - >>> img.get_qform() - array([[2., 0., 0., 0.], - [0., 3., 0., 0.], - [0., 0., 4., 0.], - [0., 0., 0., 1.]]) - >>> img.get_qform(coded=True) - (None, 0) - >>> aff2 = np.diag([3, 4, 5, 1]) - >>> img.set_qform(aff2, 'talairach') - >>> qaff, code = img.get_qform(coded=True) - >>> np.all(qaff == aff2) - True - >>> int(code) - 3 - """ - update_affine = kwargs.pop('update_affine', True) - if kwargs: - raise TypeError(f'Unexpected keyword argument(s) {kwargs}') - self._header.set_qform(affine, code, strip_shears) - if update_affine: - if self._affine is None: - self._affine = self._header.get_best_affine() - else: - self._affine[:] = self._header.get_best_affine() - - def get_sform(self, coded=False): - """Return 4x4 affine matrix from sform parameters in header - - Parameters - ---------- - coded : bool, optional - If True, return {affine or None}, and sform code. If False, just - return affine. {affine or None} means, return None if sform code - == 0, and affine otherwise. - - Returns - ------- - affine : None or (4,4) ndarray - If `coded` is False, always return affine from sform fields. If - `coded` is True, return None if sform code is 0, else return the - affine. - code : int - Sform code. Only returned if `coded` is True. - - See also - -------- - set_sform - get_qform - """ - return self._header.get_sform(coded) - - def set_sform(self, affine, code=None, **kwargs): - """Set sform transform from 4x4 affine - - Parameters - ---------- - affine : None or 4x4 array - affine transform to write into sform. If None, only set `code` - code : None, string or integer - String or integer giving meaning of transform in *affine*. - The default is None. If code is None, then: - - * If affine is None, `code`-> 0 - * If affine not None and existing sform code in header == 0, - `code`-> 2 (aligned) - * If affine not None and existing sform code in header != 0, - `code`-> existing sform code in header - - update_affine : bool, optional - Whether to update the image affine from the header best affine - after setting the qform. Must be keyword argument (because of - different position in `set_qform`). Default is True - - See also - -------- - get_sform - set_qform - - Examples - -------- - >>> data = np.arange(24, dtype='f4').reshape((2,3,4)) - >>> aff = np.diag([2, 3, 4, 1]) - >>> img = Nifti1Pair(data, aff) - >>> img.get_sform() - array([[2., 0., 0., 0.], - [0., 3., 0., 0.], - [0., 0., 4., 0.], - [0., 0., 0., 1.]]) - >>> saff, code = img.get_sform(coded=True) - >>> saff - array([[2., 0., 0., 0.], - [0., 3., 0., 0.], - [0., 0., 4., 0.], - [0., 0., 0., 1.]]) - >>> int(code) - 2 - >>> aff2 = np.diag([3, 4, 5, 1]) - >>> img.set_sform(aff2, 'talairach') - >>> saff, code = img.get_sform(coded=True) - >>> np.all(saff == aff2) - True - >>> int(code) - 3 - """ - update_affine = kwargs.pop('update_affine', True) - if kwargs: - raise TypeError(f'Unexpected keyword argument(s) {kwargs}') - self._header.set_sform(affine, code) - if update_affine: - if self._affine is None: - self._affine = self._header.get_best_affine() - else: - self._affine[:] = self._header.get_best_affine() - - def set_data_dtype(self, datatype): - """Set numpy dtype for data from code, dtype, type or alias - - Using :py:class:`int` or ``"int"`` is disallowed, as these types - will be interpreted as ``np.int64``, which is almost never desired. - ``np.int64`` is permitted for those intent on making poor choices. - - The following aliases are defined to allow for flexible specification: - - * ``'mask'`` - Alias for ``uint8`` - * ``'compat'`` - The nearest Analyze-compatible datatype - (``uint8``, ``int16``, ``int32``, ``float32``) - * ``'smallest'`` - The smallest Analyze-compatible integer - (``uint8``, ``int16``, ``int32``) - - Dynamic aliases are resolved when ``get_data_dtype()`` is called - with a ``finalize=True`` flag. Until then, these aliases are not - written to the header and will not persist to new images. - - Examples - -------- - >>> ints = np.arange(24, dtype='i4').reshape((2,3,4)) - - >>> img = Nifti1Image(ints, np.eye(4)) - >>> img.set_data_dtype(np.uint8) - >>> img.get_data_dtype() - dtype('uint8') - >>> img.set_data_dtype('mask') - >>> img.get_data_dtype() - dtype('uint8') - >>> img.set_data_dtype('compat') - >>> img.get_data_dtype() - 'compat' - >>> img.get_data_dtype(finalize=True) - dtype('>> img.get_data_dtype() - dtype('>> img.set_data_dtype('smallest') - >>> img.get_data_dtype() - 'smallest' - >>> img.get_data_dtype(finalize=True) - dtype('uint8') - >>> img.get_data_dtype() - dtype('uint8') - - Note that floating point values will not be coerced to ``int`` - - >>> floats = np.arange(24, dtype='f4').reshape((2,3,4)) - >>> img = Nifti1Image(floats, np.eye(4)) - >>> img.set_data_dtype('smallest') - >>> img.get_data_dtype(finalize=True) - Traceback (most recent call last): - ... - ValueError: Cannot automatically cast array (of type float32) to an integer - type with fewer than 64 bits. Please set_data_dtype() to an explicit data type. - - >>> arr = np.arange(1000, 1024, dtype='i4').reshape((2,3,4)) - >>> img = Nifti1Image(arr, np.eye(4)) - >>> img.set_data_dtype('smallest') - >>> img.set_data_dtype('implausible') - Traceback (most recent call last): - ... - nibabel.spatialimages.HeaderDataError: data dtype "implausible" not recognized - >>> img.set_data_dtype('none') - Traceback (most recent call last): - ... - nibabel.spatialimages.HeaderDataError: data dtype "none" known but not supported - >>> img.set_data_dtype(np.void) - Traceback (most recent call last): - ... - nibabel.spatialimages.HeaderDataError: data dtype "" known - but not supported - >>> img.set_data_dtype('int') - Traceback (most recent call last): - ... - ValueError: Invalid data type 'int'. Specify a sized integer, e.g., 'uint8' or numpy.int16. - >>> img.set_data_dtype(int) - Traceback (most recent call last): - ... - ValueError: Invalid data type . Specify a sized integer, e.g., 'uint8' or - numpy.int16. - >>> img.set_data_dtype('int64') - >>> img.get_data_dtype() == np.dtype('int64') - True - """ - # Comparing dtypes to strings, numpy will attempt to call, e.g., dtype('mask'), - # so only check for aliases if the type is a string - # See https://github.com/numpy/numpy/issues/7242 - if isinstance(datatype, str): - # Static aliases - if datatype == 'mask': - datatype = 'u1' - # Dynamic aliases - elif datatype in ('compat', 'smallest'): - self._dtype_alias = datatype - return - - self._dtype_alias = None - super().set_data_dtype(datatype) - - def get_data_dtype(self, finalize=False): - """Get numpy dtype for data - - If ``set_data_dtype()`` has been called with an alias - and ``finalize`` is ``False``, return the alias. - If ``finalize`` is ``True``, determine the appropriate dtype - from the image data object and set the final dtype in the - header before returning it. - """ - if self._dtype_alias is None: - return super().get_data_dtype() - if not finalize: - return self._dtype_alias - - datatype = None - if self._dtype_alias == 'compat': - datatype = _get_analyze_compat_dtype(self._dataobj) - descrip = 'an Analyze-compatible dtype' - elif self._dtype_alias == 'smallest': - datatype = _get_smallest_dtype(self._dataobj) - descrip = 'an integer type with fewer than 64 bits' - else: - raise ValueError(f'Unknown dtype alias {self._dtype_alias}.') - if datatype is None: - dt = get_obj_dtype(self._dataobj) - raise ValueError( - f'Cannot automatically cast array (of type {dt}) to {descrip}.' - ' Please set_data_dtype() to an explicit data type.' - ) - - self.set_data_dtype(datatype) # Clears the alias - return super().get_data_dtype() - - def to_file_map(self, file_map=None, dtype=None): - """Write image to `file_map` or contained ``self.file_map`` - - Parameters - ---------- - file_map : None or mapping, optional - files mapping. If None (default) use object's ``file_map`` - attribute instead - dtype : dtype-like, optional - The on-disk data type to coerce the data array. - """ - img_dtype = self.get_data_dtype() - self.get_data_dtype(finalize=True) - try: - super().to_file_map(file_map, dtype) - finally: - self.set_data_dtype(img_dtype) - - def as_reoriented(self, ornt): - """Apply an orientation change and return a new image - - If ornt is identity transform, return the original image, unchanged - - Parameters - ---------- - ornt : (n,2) orientation array - orientation transform. ``ornt[N,1]` is flip of axis N of the - array implied by `shape`, where 1 means no flip and -1 means - flip. For example, if ``N==0`` and ``ornt[0,1] == -1``, and - there's an array ``arr`` of shape `shape`, the flip would - correspond to the effect of ``np.flipud(arr)``. ``ornt[:,0]`` is - the transpose that needs to be done to the implied array, as in - ``arr.transpose(ornt[:,0])`` - """ - img = super().as_reoriented(ornt) - - if img is self: - return img - - # Also apply the transform to the dim_info fields - new_dim = [ - None if orig_dim is None else int(ornt[orig_dim, 0]) - for orig_dim in img.header.get_dim_info() - ] - - img.header.set_dim_info(*new_dim) - - return img - - -class Nifti1Image(Nifti1Pair, SerializableImage): - """Class for single file NIfTI1 format image""" - - header_class = Nifti1Header - valid_exts = ('.nii',) - files_types = (('image', '.nii'),) - - @staticmethod - def _get_fileholders(file_map): - """Return fileholder for header and image - - For single-file niftis, the fileholder for the header and the image - will be the same - """ - return file_map['image'], file_map['image'] - - def update_header(self): - """Harmonize header with image data and affine""" - super().update_header() - hdr = self._header - hdr['magic'] = hdr.single_magic - - -def load(filename): - """Load NIfTI1 single or pair from `filename` - - Parameters - ---------- - filename : str - filename of image to be loaded - - Returns - ------- - img : Nifti1Image or Nifti1Pair - NIfTI1 single or pair image instance - - Raises - ------ - ImageFileError - if `filename` doesn't look like NIfTI1; - OSError - if `filename` does not exist. - """ - try: - img = Nifti1Image.load(filename) - except ImageFileError: - return Nifti1Pair.load(filename) - return img - - -def save(img, filename): - """Save NIfTI1 single or pair to `filename` - - Parameters - ---------- - filename : str - filename to which to save image - """ - try: - Nifti1Image.instance_to_filename(img, filename) - except ImageFileError: - Nifti1Pair.instance_to_filename(img, filename) - - -def _get_smallest_dtype( - arr, - itypes=(np.uint8, np.int16, np.int32), - ftypes=(), -): - """Return the smallest "sensible" dtype that will hold the array data - - The purpose of this function is to support automatic type selection - for serialization, so "sensible" here means well-supported in the NIfTI-1 world. - - For floating point data, select between single- and double-precision. - For integer data, select among uint8, int16 and int32. - - The test is for min/max range, so float64 is pretty unlikely to be hit. - - Returns ``None`` if these dtypes do not suffice. - - >>> _get_smallest_dtype(np.array([0, 1])) - dtype('uint8') - >>> _get_smallest_dtype(np.array([-1, 1])) - dtype('int16') - >>> _get_smallest_dtype(np.array([0, 256])) - dtype('int16') - >>> _get_smallest_dtype(np.array([-65536, 65536])) - dtype('int32') - >>> _get_smallest_dtype(np.array([-2147483648, 2147483648])) - - By default floating point types are not searched: - - >>> _get_smallest_dtype(np.array([1.])) - >>> _get_smallest_dtype(np.array([2. ** 1000])) - >>> _get_smallest_dtype(np.longdouble(2) ** 2000) - >>> _get_smallest_dtype(np.array([1+0j])) - - However, this function can be passed "legal" floating point types, and - the logic works the same. - - >>> _get_smallest_dtype(np.array([1.]), ftypes=('float32',)) - dtype('float32') - >>> _get_smallest_dtype(np.array([2. ** 1000]), ftypes=('float32',)) - >>> _get_smallest_dtype(np.longdouble(2) ** 2000, ftypes=('float32',)) - >>> _get_smallest_dtype(np.array([1+0j]), ftypes=('float32',)) - """ - arr = np.asanyarray(arr) - if np.issubdtype(arr.dtype, np.floating): - test_dts = ftypes - info = np.finfo - elif np.issubdtype(arr.dtype, np.integer): - test_dts = itypes - info = np.iinfo - else: - return None - - mn, mx = np.min(arr), np.max(arr) - for dt in test_dts: - dtinfo = info(dt) - if dtinfo.min <= mn and mx <= dtinfo.max: - return np.dtype(dt) - - -def _get_analyze_compat_dtype(arr): - """Return an Analyze-compatible dtype that ``arr`` can be safely cast to - - Analyze-compatible types are returned without inspection: - - >>> _get_analyze_compat_dtype(np.uint8([0, 1])) - dtype('uint8') - >>> _get_analyze_compat_dtype(np.int16([0, 1])) - dtype('int16') - >>> _get_analyze_compat_dtype(np.int32([0, 1])) - dtype('int32') - >>> _get_analyze_compat_dtype(np.float32([0, 1])) - dtype('float32') - - Signed ``int8`` are cast to ``uint8`` or ``int16`` based on value ranges: - - >>> _get_analyze_compat_dtype(np.int8([0, 1])) - dtype('uint8') - >>> _get_analyze_compat_dtype(np.int8([-1, 1])) - dtype('int16') - - Unsigned ``uint16`` are cast to ``int16`` or ``int32`` based on value ranges: - - >>> _get_analyze_compat_dtype(np.uint16([32767])) - dtype('int16') - >>> _get_analyze_compat_dtype(np.uint16([65535])) - dtype('int32') - - ``int32`` is returned for integer types and ``float32`` for floating point types: - - >>> _get_analyze_compat_dtype(np.array([-1, 1])) - dtype('int32') - >>> _get_analyze_compat_dtype(np.array([-1., 1.])) - dtype('float32') - - If the value ranges exceed 4 bytes or cannot be cast, then a ``ValueError`` is raised: - - >>> _get_analyze_compat_dtype(np.array([0, 4294967295])) - Traceback (most recent call last): - ... - ValueError: Cannot find analyze-compatible dtype for array with dtype=int64 - (min=0, max=4294967295) - - >>> _get_analyze_compat_dtype([0., 2.e40]) - Traceback (most recent call last): - ... - ValueError: Cannot find analyze-compatible dtype for array with dtype=float64 - (min=0.0, max=2e+40) - - Note that real-valued complex arrays cannot be safely cast. - - >>> _get_analyze_compat_dtype(np.array([1+0j])) - Traceback (most recent call last): - ... - ValueError: Cannot find analyze-compatible dtype for array with dtype=complex128 - (min=(1+0j), max=(1+0j)) - """ - arr = np.asanyarray(arr) - dtype = arr.dtype - if dtype in (np.uint8, np.int16, np.int32, np.float32): - return dtype - - if dtype == np.int8: - return np.dtype('uint8' if arr.min() >= 0 else 'int16') - elif dtype == np.uint16: - return np.dtype('int16' if arr.max() <= np.iinfo(np.int16).max else 'int32') - - mn, mx = arr.min(), arr.max() - if arr.dtype.kind in 'iu': - info = np.iinfo('int32') - if mn >= info.min and mx <= info.max: - return np.dtype('int32') - elif arr.dtype.kind == 'f': - info = np.finfo('float32') - if mn >= info.min and mx <= info.max: - return np.dtype('float32') - - raise ValueError( - f'Cannot find analyze-compatible dtype for array with dtype={dtype} (min={mn}, max={mx})' - ) diff --git a/nibabel/nifti2.py b/nibabel/nifti2.py deleted file mode 100644 index 9c898b47ba..0000000000 --- a/nibabel/nifti2.py +++ /dev/null @@ -1,295 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Read / write access to NIfTI2 image format - -Format described here: - - https://www.nitrc.org/forum/message.php?msg_id=3738 -""" - -import numpy as np - -from .analyze import AnalyzeHeader -from .batteryrunners import Report -from .filebasedimages import ImageFileError -from .nifti1 import Nifti1Header, Nifti1Image, Nifti1Pair -from .spatialimages import HeaderDataError - -r""" -Header struct from : https://www.nitrc.org/forum/message.php?msg_id=3738 - -/*! \struct nifti_2_header - \brief Data structure defining the fields in the nifti2 header. - This binary header should be found at the beginning of a valid - NIFTI-2 header file. - */ - /*************************/ /************/ -struct nifti_2_header { /* NIFTI-2 usage */ /* offset - /*************************/ /************/ -int sizeof_hdr; /*!< MUST be 540 */ /* 0 */ -char magic[8] ; /*!< MUST be valid signature. /* 4 */ -short datatype; /*!< Defines data type! */ /* 12 */ -short bitpix; /*!< Number bits/voxel. */ /* 14 */ -int64_t dim[8]; /*!< Data array dimensions.*/ /* 16 */ -double intent_p1 ; /*!< 1st intent parameter. */ /* 80 */ -double intent_p2 ; /*!< 2nd intent parameter. */ /* 88 */ -double intent_p3 ; /*!< 3rd intent parameter. */ /* 96 */ -double pixdim[8]; /*!< Grid spacings. */ /* 104 */ -int64_t vox_offset; /*!< Offset into .nii file */ /* 168 */ -double scl_slope ; /*!< Data scaling: slope. */ /* 176 */ -double scl_inter ; /*!< Data scaling: offset. */ /* 184 */ -double cal_max; /*!< Max display intensity */ /* 192 */ -double cal_min; /*!< Min display intensity */ /* 200 */ -double slice_duration;/*!< Time for 1 slice. */ /* 208 */ -double toffset; /*!< Time axis shift. */ /* 216 */ -int64_t slice_start; /*!< First slice index. */ /* 224 */ -int64_t slice_end; /*!< Last slice index. */ /* 232 */ -char descrip[80]; /*!< any text you like. */ /* 240 */ -char aux_file[24]; /*!< auxiliary filename. */ /* 320 */ -int qform_code ; /*!< NIFTI_XFORM_* code. */ /* 344 */ -int sform_code ; /*!< NIFTI_XFORM_* code. */ /* 348 */ -double quatern_b ; /*!< Quaternion b param. */ /* 352 */ -double quatern_c ; /*!< Quaternion c param. */ /* 360 */ -double quatern_d ; /*!< Quaternion d param. */ /* 368 */ -double qoffset_x ; /*!< Quaternion x shift. */ /* 376 */ -double qoffset_y ; /*!< Quaternion y shift. */ /* 384 */ -double qoffset_z ; /*!< Quaternion z shift. */ /* 392 */ -double srow_x[4] ; /*!< 1st row affine transform. */ /* 400 */ -double srow_y[4] ; /*!< 2nd row affine transform. */ /* 432 */ -double srow_z[4] ; /*!< 3rd row affine transform. */ /* 464 */ -int slice_code ; /*!< Slice timing order. */ /* 496 */ -int xyzt_units ; /*!< Units of pixdim[1..4] */ /* 500 */ -int intent_code ; /*!< NIFTI_INTENT_* code. */ /* 504 */ -char intent_name[16]; /*!< 'name' or meaning of data. */ /* 508 */ -char dim_info; /*!< MRI slice ordering. */ /* 524 */ -char unused_str[15]; /*!< unused, filled with \0 */ /* 525 */ -} ; /**** 540 bytes total ****/ -typedef struct nifti_2_header nifti_2_header ; -""" - -# nifti2 flat header definition for first 540 bytes -# First number in comments indicates offset in file header in bytes -# fmt: off -header_dtd = [ - ('sizeof_hdr', 'i4'), # 0; must be 540 - ('magic', 'S4'), # 4; must be 'ni2\0' or 'n+2\0' - ('eol_check', 'i1', (4,)), # 8; must be 0D 0A 1A 0A - ('datatype', 'i2'), # 12; it's the datatype - ('bitpix', 'i2'), # 14; number of bits per voxel - ('dim', 'i8', (8,)), # 16; data array dimensions - ('intent_p1', 'f8'), # 80; first intent parameter - ('intent_p2', 'f8'), # 88; second intent parameter - ('intent_p3', 'f8'), # 96; third intent parameter - ('pixdim', 'f8', (8,)), # 104; grid spacings (units below) - ('vox_offset', 'i8'), # 168; offset to data in image file - ('scl_slope', 'f8'), # 176; data scaling slope - ('scl_inter', 'f8'), # 184; data scaling intercept - ('cal_max', 'f8'), # 192; max display intensity - ('cal_min', 'f8'), # 200; min display intensity - ('slice_duration', 'f8'), # 208; time for 1 slice - ('toffset', 'f8'), # 216; time axis shift - ('slice_start', 'i8'), # 224; first slice index - ('slice_end', 'i8'), # 232; last slice index - ('descrip', 'S80'), # 240; any text - ('aux_file', 'S24'), # 320; auxiliary filename - ('qform_code', 'i4'), # 344; xform code - ('sform_code', 'i4'), # 348; xform code - ('quatern_b', 'f8'), # 352; quaternion b param - ('quatern_c', 'f8'), # 360; quaternion c param - ('quatern_d', 'f8'), # 368; quaternion d param - ('qoffset_x', 'f8'), # 376; quaternion x shift - ('qoffset_y', 'f8'), # 384; quaternion y shift - ('qoffset_z', 'f8'), # 392; quaternion z shift - ('srow_x', 'f8', (4,)), # 400; 1st row affine transform - ('srow_y', 'f8', (4,)), # 432; 2nd row affine transform - ('srow_z', 'f8', (4,)), # 464; 3rd row affine transform - ('slice_code', 'i4'), # 496; slice timing order - ('xyzt_units', 'i4'), # 500; inits of pixdim[1..4] - ('intent_code', 'i4'), # 504; NIFTI intent code - ('intent_name', 'S16'), # 508; name or meaning of data - ('dim_info', 'u1'), # 524; MRI slice ordering code - ('unused_str', 'S15'), # 525; unused, filled with \0 -] # total 540 -# fmt: on - -# Full header numpy dtype -header_dtype = np.dtype(header_dtd) - - -class Nifti2Header(Nifti1Header): - """Class for NIfTI2 header - - NIfTI2 is a slightly simplified variant of NIfTI1 which replaces 32-bit - floats with 64-bit floats, and increases some integer widths to 32 or 64 - bits. - """ - - template_dtype = header_dtype - pair_vox_offset = 0 - single_vox_offset = 544 - - # Magics for single and pair - pair_magic = b'ni2' - single_magic = b'n+2' - - # Size of header in sizeof_hdr field - sizeof_hdr = 540 - - # Quaternion threshold near 0, based on float64 preicision - quaternion_threshold = -np.finfo(np.float64).eps * 3 - - def get_data_shape(self): - """Get shape of data - - Examples - -------- - >>> hdr = Nifti2Header() - >>> hdr.get_data_shape() - (0,) - >>> hdr.set_data_shape((1,2,3)) - >>> hdr.get_data_shape() - (1, 2, 3) - - Expanding number of dimensions gets default zooms - - >>> hdr.get_zooms() - (1.0, 1.0, 1.0) - - Notes - ----- - Does not use Nifti1 freesurfer hack for large vectors described in - :meth:`Nifti1Header.set_data_shape` - """ - return AnalyzeHeader.get_data_shape(self) - - def set_data_shape(self, shape): - """Set shape of data - - If ``ndims == len(shape)`` then we set zooms for dimensions higher than - ``ndims`` to 1.0 - - Parameters - ---------- - shape : sequence - sequence of integers specifying data array shape - - Notes - ----- - Does not apply nifti1 Freesurfer hack for long vectors (see - :meth:`Nifti1Header.set_data_shape`) - """ - AnalyzeHeader.set_data_shape(self, shape) - - @classmethod - def default_structarr(klass, endianness=None): - """Create empty header binary block with given endianness""" - hdr_data = super().default_structarr(endianness) - hdr_data['eol_check'] = (13, 10, 26, 10) - return hdr_data - - """ Checks only below here """ - - @classmethod - def _get_checks(klass): - # Add our own checks - return super()._get_checks() + (klass._chk_eol_check,) - - @staticmethod - def _chk_eol_check(hdr, fix=False): - rep = Report(HeaderDataError) - if np.all(hdr['eol_check'] == (13, 10, 26, 10)): - return hdr, rep - if np.all(hdr['eol_check'] == 0): - rep.problem_level = 20 - rep.problem_msg = 'EOL check all 0' - if fix: - hdr['eol_check'] = (13, 10, 26, 10) - rep.fix_msg = 'setting EOL check to 13, 10, 26, 10' - return hdr, rep - rep.problem_level = 40 - rep.problem_msg = ( - 'EOL check not 0 or 13, 10, 26, 10; data may be corrupted by EOL conversion' - ) - if fix: - hdr['eol_check'] = (13, 10, 26, 10) - rep.fix_msg = 'setting EOL check to 13, 10, 26, 10' - return hdr, rep - - @classmethod - def may_contain_header(klass, binaryblock): - if len(binaryblock) < klass.sizeof_hdr: - return False - - hdr_struct = np.ndarray( - shape=(), dtype=header_dtype, buffer=binaryblock[: klass.sizeof_hdr] - ) - bs_hdr_struct = hdr_struct.byteswap() - return 540 in (hdr_struct['sizeof_hdr'], bs_hdr_struct['sizeof_hdr']) - - -class Nifti2PairHeader(Nifti2Header): - """Class for NIfTI2 pair header""" - - # Signal whether this is single (header + data) file - is_single = False - - -class Nifti2Pair(Nifti1Pair): - """Class for NIfTI2 format image, header pair""" - - header_class = Nifti2PairHeader - _meta_sniff_len = header_class.sizeof_hdr - - -class Nifti2Image(Nifti1Image): - """Class for single file NIfTI2 format image""" - - header_class = Nifti2Header - _meta_sniff_len = header_class.sizeof_hdr - - -def load(filename): - """Load NIfTI2 single or pair image from `filename` - - Parameters - ---------- - filename : str - filename of image to be loaded - - Returns - ------- - img : Nifti2Image or Nifti2Pair - nifti2 single or pair image instance - - Raises - ------ - ImageFileError - if `filename` doesn't look like nifti2; - OSError - if `filename` does not exist. - """ - try: - img = Nifti2Image.load(filename) - except ImageFileError: - return Nifti2Pair.load(filename) - return img - - -def save(img, filename): - """Save NIfTI2 single or pair to `filename` - - Parameters - ---------- - filename : str - filename to which to save image - """ - try: - Nifti2Image.instance_to_filename(img, filename) - except ImageFileError: - Nifti2Pair.instance_to_filename(img, filename) diff --git a/nibabel/onetime.py b/nibabel/onetime.py deleted file mode 100644 index f6d3633af3..0000000000 --- a/nibabel/onetime.py +++ /dev/null @@ -1,132 +0,0 @@ -"""Descriptor support for NIPY - -Utilities to support special Python descriptors [1,2], in particular -:func:`~functools.cached_property`, which has been available in the Python -standard library since Python 3.8. We currently maintain aliases from -earlier names for this descriptor, specifically `OneTimeProperty` and `auto_attr`. - -:func:`~functools.cached_property` creates properties that are computed once -and then stored as regular attributes. They can thus be evaluated -later in the object's life cycle, but once evaluated they become normal, static -attributes with no function call overhead on access or any other constraints. - -A special ResetMixin class is provided to add a .reset() method to users who -may want to have their objects capable of resetting these computed properties -to their 'untriggered' state. - -References ----------- -[1] How-To Guide for Descriptors, Raymond -Hettinger. https://docs.python.org/howto/descriptor.html - -[2] Python data model, https://docs.python.org/reference/datamodel.html -""" - -from __future__ import annotations - -from functools import cached_property - -from nibabel.deprecated import deprecate_with_version - -# ----------------------------------------------------------------------------- -# Classes and Functions -# ----------------------------------------------------------------------------- - - -class ResetMixin: - """A Mixin class to add a .reset() method to users of cached_property. - - By default, cached properties, once computed, become static. If they happen - to depend on other parts of an object and those parts change, their values - may now be invalid. - - This class offers a .reset() method that users can call *explicitly* when - they know the state of their objects may have changed and they want to - ensure that *all* their special attributes should be invalidated. Once - reset() is called, all their cached properties are reset to their - :func:`~functools.cached_property` descriptors, - and their accessor functions will be triggered again. - - .. warning:: - - If a class has a set of attributes that are cached_property, but that - can be initialized from any one of them, do NOT use this mixin! For - instance, UniformTimeSeries can be initialized with only sampling_rate - and t0, sampling_interval and time are auto-computed. But if you were - to reset() a UniformTimeSeries, it would lose all 4, and there would be - then no way to break the circular dependency chains. - - If this becomes a problem in practice (for our analyzer objects it - isn't, as they don't have the above pattern), we can extend reset() to - check for a _no_reset set of names in the instance which are meant to be - kept protected. But for now this is NOT done, so caveat emptor. - - Examples - -------- - - >>> class A(ResetMixin): - ... def __init__(self,x=1.0): - ... self.x = x - ... - ... @cached_property - ... def y(self): - ... print('*** y computation executed ***') - ... return self.x / 2.0 - - >>> a = A(10) - - About to access y twice, the second time no computation is done: - - >>> a.y - *** y computation executed *** - 5.0 - >>> a.y - 5.0 - - Changing x - - >>> a.x = 20 - - a.y doesn't change to 10, since it is a static attribute: - - >>> a.y - 5.0 - - We now reset a, and this will then force all auto attributes to recompute - the next time we access them: - - >>> a.reset() - - About to access y twice again after reset(): - - >>> a.y - *** y computation executed *** - 10.0 - >>> a.y - 10.0 - """ - - def reset(self) -> None: - """Reset all cached_property attributes that may have fired already.""" - # To reset them, we simply remove them from the instance dict. At that - # point, it's as if they had never been computed. On the next access, - # the accessor function from the parent class will be called, simply - # because that's how the python descriptor protocol works. - for mname, mval in self.__class__.__dict__.items(): - if mname in self.__dict__ and isinstance(mval, cached_property): - delattr(self, mname) - - -OneTimeProperty = cached_property -auto_attr = cached_property - -# ----------------------------------------------------------------------------- -# Deprecated API -# ----------------------------------------------------------------------------- - -# For backwards compatibility -setattr_on_read = deprecate_with_version( - message='setattr_on_read has been renamed to auto_attr. Please use nibabel.onetime.auto_attr', - since='3.2', - until='5.0', -)(auto_attr) diff --git a/nibabel/openers.py b/nibabel/openers.py deleted file mode 100644 index 2d95d48130..0000000000 --- a/nibabel/openers.py +++ /dev/null @@ -1,283 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Context manager openers for various fileobject types""" - -from __future__ import annotations - -import gzip -import io -import typing as ty -from bz2 import BZ2File -from os.path import splitext - -from ._compression import HAVE_INDEXED_GZIP, IndexedGzipFile, pyzstd - -if ty.TYPE_CHECKING: - from types import TracebackType - - from _typeshed import WriteableBuffer - - from ._typing import Self - - ModeRT = ty.Literal['r', 'rt'] - ModeRB = ty.Literal['rb'] - ModeWT = ty.Literal['w', 'wt'] - ModeWB = ty.Literal['wb'] - ModeR = ty.Union[ModeRT, ModeRB] - ModeW = ty.Union[ModeWT, ModeWB] - Mode = ty.Union[ModeR, ModeW] - - OpenerDef = tuple[ty.Callable[..., io.IOBase], tuple[str, ...]] - - -@ty.runtime_checkable -class Fileish(ty.Protocol): - def read(self, size: int = -1, /) -> bytes: ... - def write(self, b: bytes, /) -> int | None: ... - - -class DeterministicGzipFile(gzip.GzipFile): - """Deterministic variant of GzipFile - - This writer does not add filename information to the header, and defaults - to a modification time (``mtime``) of 0 seconds. - """ - - def __init__( - self, - filename: str | None = None, - mode: Mode | None = None, - compresslevel: int = 9, - fileobj: io.FileIO | None = None, - mtime: int = 0, - ): - if mode is None: - mode = 'rb' - modestr: str = mode - - # These two guards are adapted from - # https://github.com/python/cpython/blob/6ab65c6/Lib/gzip.py#L171-L174 - if 'b' not in modestr: - modestr = f'{mode}b' - if fileobj is None: - if filename is None: - raise TypeError('Must define either fileobj or filename') - # Cast because GzipFile.myfileobj has type io.FileIO while open returns ty.IO - fileobj = self.myfileobj = ty.cast('io.FileIO', open(filename, modestr)) - super().__init__( - filename='', - mode=modestr, - compresslevel=compresslevel, - fileobj=fileobj, - mtime=mtime, - ) - - -def _gzip_open( - filename: str, - mode: Mode = 'rb', - compresslevel: int = 9, - mtime: int = 0, - keep_open: bool = False, -) -> gzip.GzipFile: - if not HAVE_INDEXED_GZIP or mode != 'rb': - gzip_file = DeterministicGzipFile(filename, mode, compresslevel, mtime=mtime) - - # use indexed_gzip if possible for faster read access. If keep_open == - # True, we tell IndexedGzipFile to keep the file handle open. Otherwise - # the IndexedGzipFile will close/open the file on each read. - else: - gzip_file = IndexedGzipFile(filename, drop_handles=not keep_open) - - return gzip_file - - -def _zstd_open( - filename: str, - mode: Mode = 'r', - *, - level_or_option: int | dict | None = None, - zstd_dict: pyzstd.ZstdDict | None = None, -) -> pyzstd.ZstdFile: - return pyzstd.ZstdFile(filename, mode, level_or_option=level_or_option, zstd_dict=zstd_dict) - - -class Opener: - r"""Class to accept, maybe open, and context-manage file-likes / filenames - - Provides context manager to close files that the constructor opened for - you. - - Parameters - ---------- - fileish : str or file-like - if str, then open with suitable opening method. If file-like, accept as - is - \*args : positional arguments - passed to opening method when `fileish` is str. ``mode``, if not - specified, is `rb`. ``compresslevel``, if relevant, and not specified, - is set from class variable ``default_compresslevel``. ``keep_open``, if - relevant, and not specified, is ``False``. - \*\*kwargs : keyword arguments - passed to opening method when `fileish` is str. Change of defaults as - for \*args - """ - - 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] = { - '.gz': gz_def, - '.bz2': bz2_def, - '.zst': zstd_def, - None: (open, ('mode', 'buffering')), # default - } - #: default compression level when writing gz and bz2 files - default_compresslevel = 1 - #: default option for zst files - default_zst_compresslevel = 3 - default_level_or_option = { - 'rb': None, - 'r': None, - 'wb': default_zst_compresslevel, - 'w': default_zst_compresslevel, - } - #: whether to ignore case looking for compression extensions - compress_ext_icase: bool = True - - fobj: io.IOBase - - def __init__(self, fileish: str | io.IOBase, *args, **kwargs): - if isinstance(fileish, (io.IOBase, Fileish)): - self.fobj = fileish - self.me_opened = False - self._name = getattr(fileish, 'name', None) - return - opener, arg_names = self._get_opener_argnames(fileish) - # Get full arguments to check for mode and compresslevel - full_kwargs = {**kwargs, **dict(zip(arg_names, args))} - # Set default mode - if 'mode' not in full_kwargs: - mode = 'rb' - kwargs['mode'] = mode - else: - mode = full_kwargs['mode'] - # Default compression level - if 'compresslevel' in arg_names and 'compresslevel' not in kwargs: - kwargs['compresslevel'] = self.default_compresslevel - if 'level_or_option' in arg_names and 'level_or_option' not in kwargs: - kwargs['level_or_option'] = self.default_level_or_option[mode] - # Default keep_open hint - if 'keep_open' in arg_names: - kwargs.setdefault('keep_open', False) - # Clear keep_open hint if it is not relevant for the file type - else: - kwargs.pop('keep_open', None) - self.fobj = opener(fileish, *args, **kwargs) - self._name = fileish - self.me_opened = True - - def _get_opener_argnames(self, fileish: str) -> OpenerDef: - _, ext = splitext(fileish) - if self.compress_ext_icase: - ext = ext.lower() - for key in self.compress_ext_map: - if key is None: - continue - if key.lower() == ext: - return self.compress_ext_map[key] - elif ext in self.compress_ext_map: - return self.compress_ext_map[ext] - return self.compress_ext_map[None] - - @property - def closed(self) -> bool: - return self.fobj.closed - - @property - def name(self) -> str | None: - """Return ``self.fobj.name`` or self._name if not present - - self._name will be None if object was created with a fileobj, otherwise - it will be the filename. - """ - return self._name - - @property - def mode(self) -> str: - # Check and raise our own error for type narrowing purposes - if hasattr(self.fobj, 'mode'): - return self.fobj.mode - raise AttributeError(f'{self.fobj.__class__.__name__} has no attribute "mode"') - - def fileno(self) -> int: - return self.fobj.fileno() - - def read(self, size: int = -1, /) -> bytes: - return self.fobj.read(size) - - def readinto(self, buffer: WriteableBuffer, /) -> int | None: - # Check and raise our own error for type narrowing purposes - if hasattr(self.fobj, 'readinto'): - return self.fobj.readinto(buffer) - raise AttributeError(f'{self.fobj.__class__.__name__} has no attribute "readinto"') - - def write(self, b: bytes, /) -> int | None: - return self.fobj.write(b) - - def seek(self, pos: int, whence: int = 0, /) -> int: - return self.fobj.seek(pos, whence) - - def tell(self, /) -> int: - return self.fobj.tell() - - def close(self, /) -> None: - return self.fobj.close() - - def __iter__(self) -> ty.Iterator[bytes]: - return iter(self.fobj) - - def close_if_mine(self) -> None: - """Close ``self.fobj`` iff we opened it in the constructor""" - if self.me_opened: - self.close() - - def __enter__(self) -> Self: - return self - - def __exit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: TracebackType | None, - ) -> None: - self.close_if_mine() - - -class ImageOpener(Opener): - """Opener-type class to collect extra compressed extensions - - A trivial sub-class of opener to which image classes can add extra - extensions with custom openers, such as compressed openers. - - To add an extension, add a line to the class definition (not __init__): - - ImageOpener.compress_ext_map[ext] = func_def - - ``ext`` is a file extension beginning with '.' and should be included in - the image class's ``valid_exts`` tuple. - - ``func_def`` is a `(function, (args,))` tuple, where `function accepts a - filename as the first parameter, and `args` defines the other arguments - that `function` accepts. These arguments must be any (unordered) subset of - `mode`, `compresslevel`, and `buffering`. - """ - - # Add new extensions to this dictionary - compress_ext_map = Opener.compress_ext_map.copy() diff --git a/nibabel/optpkg.py b/nibabel/optpkg.py deleted file mode 100644 index 90b8ded518..0000000000 --- a/nibabel/optpkg.py +++ /dev/null @@ -1,131 +0,0 @@ -"""Routines to support optional packages""" - -from __future__ import annotations - -import typing as ty - -from packaging.version import Version - -from .tripwire import TripWire - -if ty.TYPE_CHECKING: - from types import ModuleType - - -def _check_pkg_version(min_version: str | Version) -> ty.Callable[[ModuleType], bool]: - min_ver = Version(min_version) if isinstance(min_version, str) else min_version - - def check(pkg: ModuleType) -> bool: - pkg_ver = getattr(pkg, '__version__', None) - if isinstance(pkg_ver, str): - return min_ver <= Version(pkg_ver) - return False - - return check - - -def optional_package( - name: str, - trip_msg: str | None = None, - min_version: str | Version | ty.Callable[[ModuleType], bool] | None = None, -) -> tuple[ModuleType | TripWire, bool, ty.Callable[[], None]]: - """Return package-like thing and module setup for package `name` - - Parameters - ---------- - name : str - package name - trip_msg : None or str - message to give when someone tries to use the return package, but we - could not import it at an acceptable version, and have returned a - TripWire object instead. Default message if None. - min_version : None or str or Version or callable - If None, do not specify a minimum version. If str, convert to a - ``packaging.version.Version``. If str or ``Version`` compare to - version of package `name` with ``min_version <= pkg.__version__``. If - callable, accepts imported ``pkg`` as argument, and returns value of - callable is True for acceptable package versions, False otherwise. - - Returns - ------- - pkg_like : module or ``TripWire`` instance - If we can import the package, return it. Otherwise return an object - raising an error when accessed - have_pkg : bool - True if import for package was successful, false otherwise - module_setup : function - callable usually set as ``setup_module`` in calling namespace, to allow - skipping tests. - - Examples - -------- - Typical use would be something like this at the top of a module using an - optional package: - - >>> from nibabel.optpkg import optional_package - >>> pkg, have_pkg, setup_module = optional_package('not_a_package') - - Of course in this case the package doesn't exist, and so, in the module: - - >>> have_pkg - False - - and - - >>> pkg.some_function() #doctest: +IGNORE_EXCEPTION_DETAIL - Traceback (most recent call last): - ... - TripWireError: We need package not_a_package for these functions, - but ``import not_a_package`` raised an ImportError - - If the module does exist - we get the module - - >>> pkg, _, _ = optional_package('os') - >>> hasattr(pkg, 'path') - True - - Or a submodule if that's what we asked for - - >>> subpkg, _, _ = optional_package('os.path') - >>> hasattr(subpkg, 'dirname') - True - """ - if callable(min_version): - check_version = min_version - elif min_version is None: - check_version = lambda pkg: True - else: - check_version = _check_pkg_version(min_version) - # fromlist=[''] results in submodule being returned, rather than the top - # level module. See help(__import__) - fromlist = [''] if '.' in name else [] - exc = None - try: - pkg = __import__(name, fromlist=fromlist) - except Exception as exc_: - # Could fail due to some ImportError or for some other reason - # e.g. h5py might have been checking file system to support UTF-8 - # etc. We should not blow if they blow - exc = exc_ # So it is accessible outside of the code block - else: # import worked - # top level module - if check_version(pkg): - return pkg, True, lambda: None - # Failed version check - if trip_msg is None: - if callable(min_version): - trip_msg = f'Package {min_version} fails version check' - else: - trip_msg = f'These functions need {name} version >= {min_version}' - if trip_msg is None: - trip_msg = ( - f'We need package {name} for these functions, but ``import {name}`` raised {exc}' - ) - trip = TripWire(trip_msg) - - def setup_module() -> None: - import unittest - - raise unittest.SkipTest(f'No {name} for these tests') - - return trip, False, setup_module diff --git a/nibabel/orientations.py b/nibabel/orientations.py deleted file mode 100644 index f1cdd228be..0000000000 --- a/nibabel/orientations.py +++ /dev/null @@ -1,371 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Utilities for calculating and applying affine orientations""" - -import numpy as np -import numpy.linalg as npl - -from .deprecated import deprecate_with_version - - -class OrientationError(Exception): - pass - - -def io_orientation(affine, tol=None): - """Orientation of input axes in terms of output axes for `affine` - - Valid for an affine transformation from ``p`` dimensions to ``q`` - dimensions (``affine.shape == (q + 1, p + 1)``). - - The calculated orientations can be used to transform associated - arrays to best match the output orientations. If ``p`` > ``q``, then - some of the output axes should be considered dropped in this - orientation. - - Parameters - ---------- - affine : (q+1, p+1) ndarray-like - Transformation affine from ``p`` inputs to ``q`` outputs. Usually this - will be a shape (4,4) matrix, transforming 3 inputs to 3 outputs, but - the code also handles the more general case - tol : {None, float}, optional - threshold below which SVD values of the affine are considered zero. If - `tol` is None, and ``S`` is an array with singular values for `affine`, - and ``eps`` is the epsilon value for datatype of ``S``, then `tol` set - to ``S.max() * max((q, p)) * eps`` - - Returns - ------- - orientations : (p, 2) ndarray - one row per input axis, where the first value in each row is the closest - corresponding output axis. The second value in each row is 1 if the - input axis is in the same direction as the corresponding output axis and - -1 if it is in the opposite direction. If a row is [np.nan, np.nan], - which can happen when p > q, then this row should be considered dropped. - """ - affine = np.asarray(affine) - q, p = affine.shape[0] - 1, affine.shape[1] - 1 - # extract the underlying rotation, zoom, shear matrix - RZS = affine[:q, :p] - zooms = np.sqrt(np.sum(RZS * RZS, axis=0)) - # Zooms can be zero, in which case all elements in the column are zero, and - # we can leave them as they are - zooms[zooms == 0] = 1 - RS = RZS / zooms - # Transform below is polar decomposition, returning the closest - # shearless matrix R to RS - P, S, Qs = npl.svd(RS, full_matrices=False) - # Threshold the singular values to determine the rank. - if tol is None: - tol = S.max() * max(RS.shape) * np.finfo(S.dtype).eps - keep = S > tol - R = np.dot(P[:, keep], Qs[keep]) - # the matrix R is such that np.dot(R,R.T) is projection onto the - # columns of P[:,keep] and np.dot(R.T,R) is projection onto the rows - # of Qs[keep]. R (== np.dot(R, np.eye(p))) gives rotation of the - # unit input vectors to output coordinates. Therefore, the row - # index of abs max R[:,N], is the output axis changing most as input - # axis N changes. In case there are ties, we choose the axes - # iteratively, removing used axes from consideration as we go - ornt = np.ones((p, 2), dtype=np.int8) * np.nan - for in_ax in range(p): - col = R[:, in_ax] - if not np.allclose(col, 0): - out_ax = np.argmax(np.abs(col)) - ornt[in_ax, 0] = out_ax - assert col[out_ax] != 0 - if col[out_ax] < 0: - ornt[in_ax, 1] = -1 - else: - ornt[in_ax, 1] = 1 - # remove the identified axis from further consideration, by - # zeroing out the corresponding row in R - R[out_ax, :] = 0 - return ornt - - -def ornt_transform(start_ornt, end_ornt): - """Return the orientation that transforms from `start_ornt` to `end_ornt`. - - Parameters - ---------- - start_ornt : (n,2) orientation array - Initial orientation. - - end_ornt : (n,2) orientation array - Final orientation. - - Returns - ------- - orientations : (p, 2) ndarray - The orientation that will transform the `start_ornt` to the `end_ornt`. - """ - start_ornt = np.asarray(start_ornt) - end_ornt = np.asarray(end_ornt) - if start_ornt.shape != end_ornt.shape: - raise ValueError('The orientations must have the same shape') - if start_ornt.shape[1] != 2: - raise ValueError(f'Invalid shape for an orientation: {start_ornt.shape}') - result = np.empty_like(start_ornt) - for end_in_idx, (end_out_idx, end_flip) in enumerate(end_ornt): - for start_in_idx, (start_out_idx, start_flip) in enumerate(start_ornt): - if end_out_idx == start_out_idx: - if start_flip == end_flip: - flip = 1 - else: - flip = -1 - result[start_in_idx, :] = [end_in_idx, flip] - break - else: - raise ValueError(f'Unable to find out axis {end_out_idx} in start_ornt') - return result - - -def apply_orientation(arr, ornt): - """Apply transformations implied by `ornt` to the first - n axes of the array `arr` - - Parameters - ---------- - arr : array-like of data with ndim >= n - ornt : (n,2) orientation array - orientation transform. ``ornt[N,1]` is flip of axis N of the - array implied by `shape`, where 1 means no flip and -1 means - flip. For example, if ``N==0`` and ``ornt[0,1] == -1``, and - there's an array ``arr`` of shape `shape`, the flip would - correspond to the effect of ``np.flipud(arr)``. ``ornt[:,0]`` is - the transpose that needs to be done to the implied array, as in - ``arr.transpose(ornt[:,0])`` - - Returns - ------- - t_arr : ndarray - data array `arr` transformed according to ornt - """ - t_arr = np.asarray(arr) - ornt = np.asarray(ornt) - n = ornt.shape[0] - if t_arr.ndim < n: - raise OrientationError('Data array has fewer dimensions than orientation') - # no coordinates can be dropped for applying the orientations - if np.any(np.isnan(ornt[:, 0])): - raise OrientationError('Cannot drop coordinates when applying orientation to data') - # apply ornt transformations - for ax, flip in enumerate(ornt[:, 1]): - if flip == -1: - t_arr = np.flip(t_arr, axis=ax) - full_transpose = np.arange(t_arr.ndim) - # ornt indicates the transpose that has occurred - we reverse it - full_transpose[:n] = np.argsort(ornt[:, 0]) - t_arr = t_arr.transpose(full_transpose) - return t_arr - - -def inv_ornt_aff(ornt, shape): - """Affine transform reversing transforms implied in `ornt` - - Imagine you have an array ``arr`` of shape `shape`, and you apply the - transforms implied by `ornt` (more below), to get ``tarr``. - ``tarr`` may have a different shape ``shape_prime``. This routine - returns the affine that will take a array coordinate for ``tarr`` - and give you the corresponding array coordinate in ``arr``. - - Parameters - ---------- - ornt : (p, 2) ndarray - orientation transform. ``ornt[P, 1]` is flip of axis N of the array - implied by `shape`, where 1 means no flip and -1 means flip. For - example, if ``P==0`` and ``ornt[0, 1] == -1``, and there's an array - ``arr`` of shape `shape`, the flip would correspond to the effect of - ``np.flipud(arr)``. ``ornt[:,0]`` gives us the (reverse of the) - transpose that has been done to ``arr``. If there are any NaNs in - `ornt`, we raise an ``OrientationError`` (see notes) - shape : length p sequence - shape of array you may transform with `ornt` - - Returns - ------- - transform_affine : (p + 1, p + 1) ndarray - An array ``arr`` (shape `shape`) might be transformed according to - `ornt`, resulting in a transformed array ``tarr``. `transformed_affine` - is the transform that takes you from array coordinates in ``tarr`` to - array coordinates in ``arr``. - - Notes - ----- - If a row in `ornt` contains NaN, this means that the input row does not - influence the output space, and is thus effectively dropped from the output - space. In that case one ``tarr`` coordinate maps to many ``arr`` - coordinates, we can't invert the transform, and we raise an error - """ - ornt = np.asarray(ornt) - if np.any(np.isnan(ornt)): - raise OrientationError('We cannot invert orientation transform') - p = ornt.shape[0] - shape = np.array(shape)[:p] - # ornt implies a flip, followed by a transpose. We need the affine - # that inverts these. Thus we need the affine that first undoes the - # effect of the transpose, then undoes the effects of the flip. - # ornt indicates the transpose that has occurred to get the current - # ordering, relative to canonical, so we just use that. - # undo_reorder is a row permutatation matrix - axis_transpose = [int(v) for v in ornt[:, 0]] - undo_reorder = np.eye(p + 1)[axis_transpose + [p], :] - undo_flip = np.diag(list(ornt[:, 1]) + [1.0]) - center_trans = -(shape - 1) / 2.0 - undo_flip[:p, p] = (ornt[:, 1] * center_trans) - center_trans - return np.dot(undo_flip, undo_reorder) - - -@deprecate_with_version( - 'flip_axis is deprecated. Please use numpy.flip instead.', - '3.2', - '5.0', -) -def flip_axis(arr, axis=0): - """Flip contents of `axis` in array `arr` - - Equivalent to ``np.flip(arr, axis)``. - - Parameters - ---------- - arr : array-like - axis : int, optional - axis to flip. Default `axis` == 0 - - Returns - ------- - farr : array - Array with axis `axis` flipped - """ - return np.flip(arr, axis) - - -def ornt2axcodes(ornt, labels=None): - """Convert orientation `ornt` to labels for axis directions - - Parameters - ---------- - ornt : (N,2) array-like - orientation array - see io_orientation docstring - labels : optional, None or sequence of (2,) sequences - (2,) sequences are labels for (beginning, end) of output axis. That - is, if the first row in `ornt` is ``[1, 1]``, and the second (2,) - sequence in `labels` is ('back', 'front') then the first returned axis - code will be ``'front'``. If the first row in `ornt` had been - ``[1, -1]`` then the first returned value would have been ``'back'``. - If None, equivalent to ``(('L','R'),('P','A'),('I','S'))`` - that is - - RAS axes. - - Returns - ------- - axcodes : (N,) tuple - labels for positive end of voxel axes. Dropped axes get a label of - None. - - Examples - -------- - >>> ornt2axcodes([[1, 1],[0,-1],[2,1]], (('L','R'),('B','F'),('D','U'))) - ('F', 'L', 'U') - """ - if labels is None: - labels = list(zip('LPI', 'RAS')) - axcodes = [] - for axno, direction in np.asarray(ornt): - if np.isnan(axno): - axcodes.append(None) - continue - axint = int(np.round(axno)) - if axint != axno: - raise ValueError(f'Non integer axis number {axno:f}') - elif direction == 1: - axcode = labels[axint][1] - elif direction == -1: - axcode = labels[axint][0] - else: - raise ValueError('Direction should be -1 or 1') - axcodes.append(axcode) - return tuple(axcodes) - - -def axcodes2ornt(axcodes, labels=None): - """Convert axis codes `axcodes` to an orientation - - Parameters - ---------- - axcodes : (N,) tuple - axis codes - see ornt2axcodes docstring - labels : optional, None or sequence of (2,) sequences - (2,) sequences are labels for (beginning, end) of output axis. That - is, if the first element in `axcodes` is ``front``, and the second - (2,) sequence in `labels` is ('back', 'front') then the first - row of `ornt` will be ``[1, 1]``. If None, equivalent to - ``(('L','R'),('P','A'),('I','S'))`` - that is - RAS axes. - - Returns - ------- - ornt : (N,2) array-like - orientation array - see io_orientation docstring - - Examples - -------- - >>> axcodes2ornt(('F', 'L', 'U'), (('L','R'),('B','F'),('D','U'))) - array([[ 1., 1.], - [ 0., -1.], - [ 2., 1.]]) - """ - labels = list(zip('LPI', 'RAS')) if labels is None else labels - allowed_labels = sum(map(list, labels), [None]) - if len(allowed_labels) != len(set(allowed_labels)): - raise ValueError(f'Duplicate labels in {allowed_labels}') - if not set(axcodes).issubset(allowed_labels): - raise ValueError(f'Not all axis codes {list(axcodes)} in label set {allowed_labels}') - n_axes = len(axcodes) - ornt = np.ones((n_axes, 2), dtype=np.int8) * np.nan - for code_idx, code in enumerate(axcodes): - for label_idx, codes in enumerate(labels): - if code is None: - continue - if code in codes: - if code == codes[0]: - ornt[code_idx, :] = [label_idx, -1] - else: - ornt[code_idx, :] = [label_idx, 1] - break - return ornt - - -def aff2axcodes(aff, labels=None, tol=None): - """axis direction codes for affine `aff` - - Parameters - ---------- - aff : (N,M) array-like - affine transformation matrix - labels : optional, None or sequence of (2,) sequences - Labels for negative and positive ends of output axes of `aff`. See - docstring for ``ornt2axcodes`` for more detail - tol : None or float - Tolerance for SVD of affine - see ``io_orientation`` for more detail. - - Returns - ------- - axcodes : (N,) tuple - labels for positive end of voxel axes. Dropped axes get a label of - None. - - Examples - -------- - >>> aff = [[0,1,0,10],[-1,0,0,20],[0,0,1,30],[0,0,0,1]] - >>> aff2axcodes(aff, (('L','R'),('B','F'),('D','U'))) - ('B', 'R', 'U') - """ - ornt = io_orientation(aff, tol) - return ornt2axcodes(ornt, labels) diff --git a/nibabel/parrec.py b/nibabel/parrec.py deleted file mode 100644 index 22520a603e..0000000000 --- a/nibabel/parrec.py +++ /dev/null @@ -1,1343 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Read images in PAR/REC format - -This is yet another MRI image format generated by Philips scanners. It is an -ASCII header (PAR) plus a binary blob (REC). - -This implementation aims to read version 4.0 through 4.2 of this format. Other -versions could probably be supported, but we need example images to test -against. If you want us to support another version, and have an image we can -add to the test suite, let us know. You would make us very happy by submitting -a pull request. - -############### -PAR file format -############### - -The PAR format appears to have two sections: - -General information -################### - -This is a set of lines each giving one key : value pair, examples:: - - . EPI factor <0,1=no EPI> : 39 - . Dynamic scan <0=no 1=yes> ? : 1 - . Diffusion <0=no 1=yes> ? : 0 - -(from ``nibabel/tests/data/phantom_EPI_asc_CLEAR_2_1.PAR``) - -Image information -################# - -There is a ``#`` prefixed list of fields under the heading "IMAGE INFORMATION -DEFINITION". From the same file, here is the start of this list:: - - # === IMAGE INFORMATION DEFINITION ============================================= - # The rest of this file contains ONE line per image, this line contains the following information: - # - # slice number (integer) - # echo number (integer) - # dynamic scan number (integer) - -There follows a space separated table with values for these fields, each row -containing all the named values. Here are the first few lines from the example -file above:: - - # === IMAGE INFORMATION ========================================================== - # sl ec dyn ph ty idx pix scan% rec size (re)scale window angulation offcentre thick gap info spacing echo dtime ttime diff avg flip freq RR-int turbo delay b grad cont anis diffusion L.ty - - 1 1 1 1 0 2 0 16 62 64 64 0.00000 1.29035 4.28404e-003 1070 1860 -13.26 -0.00 -0.00 2.51 -0.81 -8.69 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 2 1 1 1 0 2 1 16 62 64 64 0.00000 1.29035 4.28404e-003 1122 1951 -13.26 -0.00 -0.00 2.51 6.98 -10.53 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 3 1 1 1 0 2 2 16 62 64 64 0.00000 1.29035 4.28404e-003 1137 1977 -13.26 -0.00 -0.00 2.51 14.77 -12.36 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - -Orientation -########### - -PAR files refer to orientations "ap", "fh" and "rl". - -Nibabel's required affine output axes are RAS (left to Right, posterior to -Anterior, inferior to Superior). The correspondence of the PAR file's axes to -RAS axes is: - -* ap = anterior -> posterior = negative A in RAS = P -* fh = foot -> head = S in RAS = S -* rl = right -> left = negative R in RAS = L - -We therefore call the PAR file's axis system "PSL" (Posterior, Superior, Left). - -The orientation of the PAR file axes corresponds to DICOM's LPS coordinate -system (right to Left, anterior to Posterior, inferior to Superior), but in a -different order. - -Data type -######### - -It seems that everyone agrees that Philips stores REC data in little-endian -format - see https://github.com/nipy/nibabel/issues/274 - -Philips XML header files, and some previous experience, suggest that the REC -data is always stored as 8 or 16 bit unsigned integers - see -https://github.com/nipy/nibabel/issues/275 - -Data Sorting -############ - -PAR/REC files have a large number of potential image dimensions. To handle -sorting of volumes in PAR/REC files based on these fields and not the order -slices first appear in the PAR file, the ``strict_sort`` flag of -``nibabel.load`` (or ``parrec.load``) should be set to ``True``. The fields -that are taken into account during sorting are: - - - slice number - - echo number - - cardiac phase number - - gradient orientation number - - diffusion b value number - - label type (ASL tag vs. control) - - dynamic scan number - - image_type_mr (Re, Im, Mag, Phase) - -Slices are sorted into the third dimension and the -order of preference for sorting along the 4th dimension corresponds to the -order in the list above. If the image data has more than 4 dimensions these -will all be concatenated along the 4th dimension. For example, for a scan with -two echos and two dynamics, the 4th dimension will have both echos of dynamic 1 -prior to the two echos for dynamic 2. - -The``get_volume_labels`` method of the header returns a dictionary containing -the PAR field labels for this 4th dimension. - -The volume sorting described above can be enabled in the parrec2nii command -utility via the option "--strict-sort". The dimension info can be exported -to a CSV file by adding the option "--volume-info". -""" - -import re -import warnings -from collections import OrderedDict -from copy import deepcopy -from io import StringIO -from locale import getpreferredencoding - -import numpy as np - -from .affines import apply_affine, dot_reduce, from_matvec -from .eulerangles import euler2mat -from .fileslice import fileslice, strided_scalar -from .nifti1 import unit_codes -from .openers import ImageOpener -from .spatialimages import SpatialHeader, SpatialImage -from .volumeutils import Recoder, array_from_file - -# PSL to RAS affine -PSL_TO_RAS = np.array( - [ - [0, 0, -1, 0], # L -> R - [-1, 0, 0, 0], # P -> A - [0, 1, 0, 0], # S -> S - [0, 0, 0, 1], - ] -) - -# Acquisition (tra/sag/cor) to PSL axes -# These come from looking at transverse, sagittal, coronal datasets where we -# can see the LR, PA, SI orientation of the slice axes from the scanned object -ACQ_TO_PSL = dict( - transverse=np.array( - [ - [0, 1, 0, 0], # P - [0, 0, 1, 0], # S - [1, 0, 0, 0], # L - [0, 0, 0, 1], - ] - ), - sagittal=np.diag([1, -1, -1, 1]), - coronal=np.array( - [ - [0, 0, 1, 0], # P - [0, -1, 0, 0], # S - [1, 0, 0, 0], # L - [0, 0, 0, 1], - ] - ), -) - -DEG2RAD = np.pi / 180.0 - -# General information dict definitions -# assign props to PAR header entries -# values are: (shortname[, dtype[, shape]]) -# if shape is None, the number of elements is to be determined on read -_hdr_key_dict = { - 'Patient name': ('patient_name',), - 'Examination name': ('exam_name',), - 'Protocol name': ('protocol_name',), - 'Examination date/time': ('exam_date',), - 'Series Type': ('series_type',), - 'Acquisition nr': ('acq_nr', int), - 'Reconstruction nr': ('recon_nr', int), - 'Scan Duration [sec]': ('scan_duration', float), - 'Max. number of cardiac phases': ('max_cardiac_phases', int), - 'Max. number of echoes': ('max_echoes', int), - 'Max. number of slices/locations': ('max_slices', int), - 'Max. number of dynamics': ('max_dynamics', int), - 'Max. number of mixes': ('max_mixes', int), - 'Patient position': ('patient_position',), - 'Preparation direction': ('prep_direction',), - 'Technique': ('tech',), - 'Scan resolution (x, y)': ('scan_resolution', int, (2,)), - 'Scan mode': ('scan_mode',), - 'Repetition time [ms]': ('repetition_time', float, None), - 'FOV (ap,fh,rl) [mm]': ('fov', float, (3,)), - 'Water Fat shift [pixels]': ('water_fat_shift', float), - 'Angulation midslice(ap,fh,rl)[degr]': ('angulation', float, (3,)), - 'Off Centre midslice(ap,fh,rl) [mm]': ('off_center', float, (3,)), - 'Flow compensation <0=no 1=yes> ?': ('flow_compensation', int), - 'Presaturation <0=no 1=yes> ?': ('presaturation', int), - 'Phase encoding velocity [cm/sec]': ('phase_enc_velocity', float, (3,)), - 'MTC <0=no 1=yes> ?': ('mtc', int), - 'SPIR <0=no 1=yes> ?': ('spir', int), - 'EPI factor <0,1=no EPI>': ('epi_factor', int), - 'Dynamic scan <0=no 1=yes> ?': ('dyn_scan', int), - 'Diffusion <0=no 1=yes> ?': ('diffusion', int), - 'Diffusion echo time [ms]': ('diffusion_echo_time', float), - # Lines below added for par / rec versions > 4 - 'Max. number of diffusion values': ('max_diffusion_values', int), - 'Max. number of gradient orients': ('max_gradient_orient', int), - # Line below added for par / rec version > 4.1 - 'Number of label types <0=no ASL>': ('nr_label_types', int), - # The following are duplicates of the above fields, but with slightly - # different abbreviation, spelling, or capatilization. Both variants have - # been observed in the wild in V4.2 PAR files: - # https://github.com/nipy/nibabel/issues/505 - 'Series_data_type': ('series_type',), - 'Patient Position': ('patient_position',), - 'Repetition time [msec]': ('repetition_time', float, None), - 'Diffusion echo time [msec]': ('diffusion_echo_time', float), -} - -# Image information as coded into a numpy structured array -# header items order per image definition line -image_def_dtds = {} -image_def_dtds['V4'] = [ - ('slice number', int), - ('echo number', int), - ('dynamic scan number', int), - ('cardiac phase number', int), - ('image_type_mr', int), - ('scanning sequence', int), - ('index in REC file', int), - ('image pixel size', int), - ('scan percentage', int), - ('recon resolution', int, (2)), - ('rescale intercept', float), - ('rescale slope', float), - ('scale slope', float), - # Window center, width recorded as integer but can be float - ('window center', float), - ('window width', float), - ('image angulation', float, (3,)), - ('image offcentre', float, (3,)), - ('slice thickness', float), - ('slice gap', float), - ('image_display_orientation', int), - ('slice orientation', int), - ('fmri_status_indication', int), - ('image_type_ed_es', int), - ('pixel spacing', float, (2,)), - ('echo_time', float), - ('dyn_scan_begin_time', float), - ('trigger_time', float), - ('diffusion_b_factor', float), - ('number of averages', int), - ('image_flip_angle', float), - ('cardiac frequency', int), - ('minimum RR-interval', int), - ('maximum RR-interval', int), - ('TURBO factor', int), - ('Inversion delay', float), -] - -# Extra image def fields for 4.1 compared to 4 -# fmt: off -image_def_dtds['V4.1'] = image_def_dtds['V4'] + [ - ('diffusion b value number', int), # (imagekey!) - ('gradient orientation number', int), # (imagekey!) - ('contrast type', 'S30'), # XXX might be too short? - ('diffusion anisotropy type', 'S30'), # XXX might be too short? - ('diffusion', float, (3,)), -] - -# Extra image def fields for 4.2 compared to 4.1 -image_def_dtds['V4.2'] = image_def_dtds['V4.1'] + [ - ('label type', int), # (imagekey!) -] -# fmt: on - -#: PAR header versions we claim to understand -supported_versions = list(image_def_dtds.keys()) - -#: Deprecated; please don't use -image_def_dtype = np.dtype(image_def_dtds['V4.2']) - -#: slice orientation codes -slice_orientation_codes = Recoder( - ( # code, label - (1, 'transverse'), - (2, 'sagittal'), - (3, 'coronal'), - ), - fields=('code', 'label'), -) - - -class PARRECError(Exception): - """Exception for PAR/REC format related problems. - - To be raised whenever PAR/REC is not happy, or we are not happy with - PAR/REC. - """ - - -# Value after colon may be absent -GEN_RE = re.compile(r'.\s+(.*?)\s*:\s*(.*)') - - -def _split_header(fobj): - """Split header into `version`, `gen_dict`, `image_lines`""" - version = None - gen_dict = {} - image_lines = [] - # Small state-machine - state = 'top-header' - for line in fobj: - line = line.strip() - if line == '': - continue - if state == 'top-header': - if not line.startswith('#'): - state = 'general-info' - elif 'image export tool' in line: - version = line.split()[-1] - if state == 'general-info': - if not line.startswith('.'): - state = 'comment-block' - else: # Let match raise error for unexpected field format - key, value = GEN_RE.match(line).groups() - gen_dict[key] = value - if state == 'comment-block': - if not line.startswith('#'): - state = 'image-info' - if state == 'image-info': - if line.startswith('#'): - break - image_lines.append(line) - return version, gen_dict, image_lines - - -def _process_gen_dict(gen_dict): - """Process `gen_dict` key, values into `general_info`""" - general_info = {} - for key, value in gen_dict.items(): - # get props for this hdr field - props = _hdr_key_dict[key] - # turn values into meaningful dtype - if len(props) == 2: - # only dtype spec and no shape - value = props[1](value) - elif len(props) == 3: - # array with dtype and shape - value = np.fromstring(value, props[1], sep=' ') - # if shape is None, allow arbitrary length - if props[2] is not None: - value.shape = props[2] - general_info[props[0]] = value - return general_info - - -def _process_image_lines(image_lines, version): - """Process image information definition lines according to `version`""" - # postproc image def props - image_def_dtd = image_def_dtds[version] - # create an array for all image defs - image_defs = np.zeros(len(image_lines), dtype=image_def_dtd) - # for every image definition - for i, line in enumerate(image_lines): - items = line.split() - item_counter = 0 - # for all image properties we know about - for props in image_def_dtd: - if len(props) == 2: - name, np_type = props - value = items[item_counter] - if not np.dtype(np_type).kind == 'S': - value = np_type(value) - item_counter += 1 - elif len(props) == 3: - name, np_type, shape = props - nelements = np.prod(shape) - value = items[item_counter : item_counter + nelements] - value = [np_type(v) for v in value] - item_counter += nelements - image_defs[name][i] = value - return image_defs - - -def vol_numbers(slice_nos): - """Calculate volume numbers inferred from slice numbers `slice_nos` - - The volume number for each slice is the number of times this slice number - has occurred previously in the `slice_nos` sequence - - Parameters - ---------- - slice_nos : sequence - Sequence of slice numbers, e.g. ``[1, 2, 3, 4, 1, 2, 3, 4]``. - - Returns - ------- - vol_nos : list - A list, the same length of `slice_nos` giving the volume number for - each corresponding slice number. - """ - counter = {} - vol_nos = [] - for s_no in slice_nos: - count = counter.setdefault(s_no, 0) - vol_nos.append(count) - counter[s_no] += 1 - return vol_nos - - -def vol_is_full(slice_nos, slice_max, slice_min=1): - """Vector with True for slices in complete volume, False otherwise - - Parameters - ---------- - slice_nos : sequence - Sequence of slice numbers, e.g. ``[1, 2, 3, 4, 1, 2, 3, 4]``. - slice_max : int - Highest slice number for a full slice set. Slice set will be - ``range(slice_min, slice_max+1)``. - slice_min : int, optional - Lowest slice number for full slice set. Default is 1. - - Returns - ------- - is_full : array - Bool vector with True for slices in full volumes, False for slices in - partial volumes. A full volume is a volume with all slices in the - ``slice set`` as defined above. - - Raises - ------ - ValueError - if any value in `slice_nos` is outside slice set indices. - """ - slice_set = set(range(slice_min, slice_max + 1)) - if not slice_set.issuperset(slice_nos): - raise ValueError(f'Slice numbers outside inclusive range {slice_min} to {slice_max}') - vol_nos = np.array(vol_numbers(slice_nos)) - slice_nos = np.asarray(slice_nos) - is_full = np.ones(slice_nos.shape, dtype=bool) - for vol_no in set(vol_nos): - ours = vol_nos == vol_no - if not set(slice_nos[ours]) == slice_set: - is_full[ours] = False - return is_full - - -def _truncation_checks(general_info, image_defs, permit_truncated): - """Check for presence of truncation in PAR file parameters - - Raise error if truncation present and `permit_truncated` is False. - """ - - def _err_or_warn(msg): - if not permit_truncated: - raise PARRECError(msg) - warnings.warn(msg) - - def _chk_trunc(idef_name, gdef_max_name): - if gdef_max_name not in general_info: - return - id_values = image_defs[idef_name + ' number'] - n_have = len(set(id_values)) - n_expected = general_info[gdef_max_name] - if n_have != n_expected: - _err_or_warn( - f'Header inconsistency: Found {n_have} {idef_name} ' - f'values, but expected {n_expected}' - ) - - _chk_trunc('slice', 'max_slices') - _chk_trunc('echo', 'max_echoes') - _chk_trunc('dynamic scan', 'max_dynamics') - _chk_trunc('diffusion b value', 'max_diffusion_values') - _chk_trunc('gradient orientation', 'max_gradient_orient') - - # Final check for partial volumes - if not np.all(vol_is_full(image_defs['slice number'], general_info['max_slices'])): - _err_or_warn('Found one or more partial volume(s)') - - -def one_line(long_str): - """Make maybe mutli-line `long_str` into one long line""" - return ' '.join(line.strip() for line in long_str.splitlines()) - - -def parse_PAR_header(fobj): - """Parse a PAR header and aggregate all information into useful containers. - - Parameters - ---------- - fobj : file-object - The PAR header file object. - - Returns - ------- - general_info : dict - Contains all "General Information" from the header file - image_info : ndarray - Structured array with fields giving all "Image information" in the - header - """ - # single pass through the header - version, gen_dict, image_lines = _split_header(fobj) - if version not in supported_versions: - warnings.warn( - one_line( - f""" PAR/REC version '{version}' is currently not supported -- making an - attempt to read nevertheless. Please email the NiBabel mailing - list, if you are interested in adding support for this version. - """ - ) - ) - general_info = _process_gen_dict(gen_dict) - image_defs = _process_image_lines(image_lines, version) - return general_info, image_defs - - -def _data_from_rec( - rec_fileobj, in_shape, dtype, slice_indices, out_shape, scalings=None, mmap=True -): - """Load and return array data from REC file - - Parameters - ---------- - rec_fileobj : file-like - The file to process. - in_shape : tuple - The input shape inferred from the PAR file. - dtype : dtype - The datatype. - slice_indices : array of int - The indices used to re-index the resulting array properly. - out_shape : tuple - The output shape. - scalings : {None, sequence}, optional - Scalings to use. If not None, a length 2 sequence giving (``slope``, - ``intercept``), where ``slope`` and ``intercept`` are arrays that can - be broadcast to `out_shape`. - mmap : {True, False, 'c', 'r', 'r+'}, optional - `mmap` controls the use of numpy memory mapping for reading data. If - False, do not try numpy ``memmap`` for data array. If one of {'c', - 'r', 'r+'}, try numpy memmap with ``mode=mmap``. A `mmap` value of - True gives the same behavior as ``mmap='c'``. If `rec_fileobj` cannot - be memory-mapped, ignore `mmap` value and read array from file. - - Returns - ------- - data : array - The scaled and sorted array. - """ - rec_data = array_from_file(in_shape, dtype, rec_fileobj, mmap=mmap) - rec_data = rec_data[..., slice_indices] - rec_data = rec_data.reshape(out_shape, order='F') - if scalings is not None: - # Don't do in-place b/c this goes int16 -> float64 - rec_data = rec_data * scalings[0] - rec_data += scalings[1] - return rec_data - - -def exts2pars(exts_source): - """Parse, return any PAR headers from NIfTI extensions in `exts_source` - - Parameters - ---------- - exts_source : sequence or `Nifti1Image`, `Nifti1Header` instance - A sequence of extensions, or header containing NIfTI extensions, or an - image containing a header with NIfTI extensions. - - Returns - ------- - par_headers : list - A list of PARRECHeader objects, usually empty or with one element, each - element contains a PARRECHeader read from the contained extensions. - """ - headers = [] - exts_source = exts_source.header if hasattr(exts_source, 'header') else exts_source - exts_source = exts_source.extensions if hasattr(exts_source, 'extensions') else exts_source - for extension in exts_source: - content = extension.get_content() - content = content.decode(getpreferredencoding(False)) - if not content.startswith('# === DATA DESCRIPTION FILE ==='): - continue - gen_info, image_info = parse_PAR_header(StringIO(content)) - headers.append(PARRECHeader(gen_info, image_info)) - return headers - - -class PARRECArrayProxy: - def __init__(self, file_like, header, *, mmap=True, scaling='dv'): - """Initialize PARREC array proxy - - Parameters - ---------- - file_like : file-like object - Filename or object implementing ``read, seek, tell`` - header : PARRECHeader instance - Implementing ``get_data_shape, get_data_dtype``, - ``get_sorted_slice_indices``, ``get_data_scaling``, - ``get_rec_shape``. - mmap : {True, False, 'c', 'r'}, optional, keyword only - `mmap` controls the use of numpy memory mapping for reading data. - If False, do not try numpy ``memmap`` for data array. If one of - {'c', 'r'}, try numpy memmap with ``mode=mmap``. A `mmap` value of - True gives the same behavior as ``mmap='c'``. If `file_like` - cannot be memory-mapped, ignore `mmap` value and read array from - file. - scaling : {'fp', 'dv'}, optional, keyword only - Type of scaling to use - see header ``get_data_scaling`` method. - """ - if mmap not in (True, False, 'c', 'r'): - raise ValueError("mmap should be one of {True, False, 'c', 'r'}") - self.file_like = file_like - # Copies of values needed to read array - self._shape = header.get_data_shape() - self._dtype = header.get_data_dtype() - self._slice_indices = header.get_sorted_slice_indices() - self._mmap = mmap - self._slice_scaling = header.get_data_scaling(scaling) - self._rec_shape = header.get_rec_shape() - - @property - def shape(self): - return self._shape - - @property - def ndim(self): - return len(self.shape) - - @property - def dtype(self): - return self._dtype - - @property - def is_proxy(self): - return True - - def _get_unscaled(self, slicer): - indices = self._slice_indices - if slicer == (): - with ImageOpener(self.file_like) as fileobj: - rec_data = array_from_file(self._rec_shape, self._dtype, fileobj, mmap=self._mmap) - rec_data = rec_data[..., indices] - return rec_data.reshape(self._shape, order='F') - elif indices[0] != 0 or np.any(np.diff(indices) != 1): - # We can't load direct from REC file, use inefficient slicing - return self._get_unscaled(())[slicer] - - # Slices all sequential from zero, can use fileslice - # This gives more efficient volume by volume loading, for example - with ImageOpener(self.file_like) as fileobj: - return fileslice(fileobj, slicer, self._shape, self._dtype, 0, 'F') - - def _get_scaled(self, dtype, slicer): - raw_data = self._get_unscaled(slicer) - if self._slice_scaling is None: - if dtype is None: - return raw_data - final_type = np.promote_types(raw_data.dtype, dtype) - return raw_data.astype(final_type, copy=False) - - # Broadcast scaling to shape of original data - fake_data = strided_scalar(self._shape) - _, slopes, inters = np.broadcast_arrays(fake_data, *self._slice_scaling) - - final_type = np.result_type(raw_data, slopes, inters) - if dtype is not None: - final_type = np.promote_types(final_type, dtype) - - # Slice scaling to give output shape - return raw_data * slopes[slicer].astype(final_type) + inters[slicer].astype(final_type) - - def get_unscaled(self): - """Read data from file - - This is an optional part of the proxy API - """ - return self._get_unscaled(slicer=()) - - def __array__(self, dtype=None): - """Read data from file and apply scaling, casting to ``dtype`` - - If ``dtype`` is unspecified, the dtype of the returned array is the - narrowest dtype that can represent the data without overflow. - Generally, it is the wider of the dtypes of the slopes or intercepts. - - Parameters - ---------- - dtype : numpy dtype specifier, optional - A numpy dtype specifier specifying the type of the returned array. - - Returns - ------- - array - Scaled image data with type `dtype`. - """ - arr = self._get_scaled(dtype=dtype, slicer=()) - if dtype is not None: - arr = arr.astype(dtype, copy=False) - return arr - - def __getitem__(self, slicer): - return self._get_scaled(dtype=None, slicer=slicer) - - -class PARRECHeader(SpatialHeader): - """PAR/REC header""" - - def __init__(self, info, image_defs, permit_truncated=False, strict_sort=False): - """ - Parameters - ---------- - info : dict - "General information" from the PAR file (as returned by - `parse_PAR_header()`). - image_defs : array - Structured array with image definitions from the PAR file (as - returned by `parse_PAR_header()`). - permit_truncated : bool, optional - If True, a warning is emitted instead of an error when a truncated - recording is detected. - strict_sort : bool, optional, keyword-only - If True, a larger number of header fields are used while sorting - the REC data array. This may produce a different sort order than - `strict_sort=False`, where volumes are sorted by the order in which - the slices appear in the .PAR file. - """ - self.general_info = info.copy() - self.image_defs = image_defs.copy() - self.permit_truncated = permit_truncated - self.strict_sort = strict_sort - _truncation_checks(info, image_defs, permit_truncated) - # charge with basic properties to be able to use base class - # functionality - # dtype - bitpix = self._get_unique_image_prop('image pixel size') - if bitpix not in (8, 16): - raise PARRECError( - f'Only 8- and 16-bit data supported (not {bitpix}) ' - 'please report this to the nibabel developers' - ) - # REC data always little endian - dt = np.dtype('uint' + str(bitpix)).newbyteorder('<') - super().__init__(data_dtype=dt, shape=self._calc_data_shape(), zooms=self._calc_zooms()) - - @classmethod - def from_header(klass, header=None): - if header is None: - raise PARRECError('Cannot create PARRECHeader from air.') - if type(header) == klass: - return header.copy() - raise PARRECError('Cannot create PARREC header from non-PARREC header.') - - @classmethod - def from_fileobj(klass, fileobj, permit_truncated=False, strict_sort=False): - info, image_defs = parse_PAR_header(fileobj) - return klass(info, image_defs, permit_truncated, strict_sort) - - def copy(self): - return PARRECHeader( - deepcopy(self.general_info), - self.image_defs.copy(), - self.permit_truncated, - self.strict_sort, - ) - - def as_analyze_map(self): - """Convert PAR parameters to NIFTI1 format""" - # Entries in the dict correspond to the parameters found in - # the NIfTI1 header, specifically in nifti1.py `header_dtd` defs. - # Here we set the parameters we can to simplify PAR/REC - # to NIfTI conversion. - descr = ( - f'{self.general_info["exam_name"]};' - f'{self.general_info["patient_name"]};' - f'{self.general_info["exam_date"].replace(" ", "")};' - f'{self.general_info["protocol_name"]}' - )[:80] - is_fmri = self.general_info['max_dynamics'] > 1 - # PAR/REC uses msec, but in _calc_zooms we convert to sec - t = 'sec' if is_fmri else 'unknown' - xyzt_units = unit_codes['mm'] + unit_codes[t] - return dict(descr=descr, xyzt_units=xyzt_units) # , pixdim=pixdim) - - def get_water_fat_shift(self): - """Water fat shift, in pixels""" - return self.general_info['water_fat_shift'] - - def get_echo_train_length(self): - """Echo train length of the recording""" - return self.general_info['epi_factor'] - - def get_q_vectors(self): - """Get Q vectors from the data - - Returns - ------- - q_vectors : None or array - Array of q vectors (bvals * bvecs), or None if not a diffusion - acquisition. - """ - bvals, bvecs = self.get_bvals_bvecs() - if bvals is None or bvecs is None: - return None - return bvecs * bvals[:, np.newaxis] - - def get_bvals_bvecs(self): - """Get bvals and bvecs from data - - Returns - ------- - b_vals : None or array - Array of b values, shape (n_directions,), or None if not a - diffusion acquisition. - b_vectors : None or array - Array of b vectors, shape (n_directions, 3), or None if not a - diffusion acquisition. - """ - if self.general_info['diffusion'] == 0: - return None, None - reorder = self.get_sorted_slice_indices() - if len(self.get_data_shape()) == 3: - # Any original diffusion scans will have >=2 volumes. However, a - # single dynamic is possible for a post-processed diffusion volume - # such as an ADC map. The b-values are unavailable in this case. - return None, None - else: - n_slices, n_vols = self.get_data_shape()[-2:] - bvals = self.image_defs['diffusion_b_factor'][reorder].reshape( - (n_slices, n_vols), order='F' - ) - # All bvals within volume should be the same - assert not np.any(np.diff(bvals, axis=0)) - bvals = bvals[0] - if 'diffusion' not in self.image_defs.dtype.names: - return bvals, None - bvecs = self.image_defs['diffusion'][reorder].reshape((n_slices, n_vols, 3), order='F') - # All 3 values of bvecs should be same within volume - assert not np.any(np.diff(bvecs, axis=0)) - bvecs = bvecs[0] - # rotate bvecs to match stored image orientation - permute_to_psl = ACQ_TO_PSL[self.get_slice_orientation()] - bvecs = apply_affine(np.linalg.inv(permute_to_psl), bvecs) - return bvals, bvecs - - def get_def(self, name): - """Return a single image definition field (or None if missing)""" - idef = self.image_defs - return idef[name] if name in idef.dtype.names else None - - def _get_unique_image_prop(self, name): - """Scan image definitions and return unique value of a property. - - * Get array for named field of ``self.image_defs``; - * Check that all rows in the array are the same and raise error - otherwise; - * Return the row. - - Parameters - ---------- - name : str - Name of the property in ``self.image_defs`` - - Returns - ------- - unique_value : scalar or array - - Raises - ------ - PARRECError - if the rows of ``self.image_defs[name]`` do not all compare equal. - """ - props = self.image_defs[name] - if np.any(np.diff(props, axis=0)): - raise PARRECError( - f'Varying {name} in image sequence ({props}). This is not supported.' - ) - return props[0] - - def get_data_offset(self): - """PAR header always has 0 data offset (into REC file)""" - return 0 - - def set_data_offset(self, offset): - """PAR header always has 0 data offset (into REC file)""" - if offset != 0: - raise PARRECError('PAR header assumes offset 0') - - def _calc_zooms(self): - """Compute image zooms from header data. - - Spatial axis are first three. - - Returns - ------- - zooms : array - Length 3 array for 3D image, length 4 array for 4D image. - - Notes - ----- - This routine gets called in ``__init__``, so may not be able to use - some attributes available in the fully initialized object. - """ - # slice orientation for the whole image series - slice_gap = self._get_unique_image_prop('slice gap') - # scaling per image axis - n_dim = 4 if self._get_n_vols() > 1 else 3 - zooms = np.ones(n_dim) - # spatial sizes are inplane X mm, inplane Y mm + inter slice gap - zooms[:2] = self._get_unique_image_prop('pixel spacing') - slice_thickness = self._get_unique_image_prop('slice thickness') - zooms[2] = slice_thickness + slice_gap - # If 4D dynamic scan, convert time from milliseconds to seconds - if len(zooms) > 3 and self.general_info['dyn_scan']: - if len(self.general_info['repetition_time']) > 1: - warnings.warn('multiple TRs found in .PAR file') - zooms[3] = self.general_info['repetition_time'][0] / 1000.0 - return zooms - - def get_affine(self, origin='scanner'): - """Compute affine transformation into scanner space. - - The method only considers global rotation and offset settings in the - header and ignores potentially deviating information in the image - definitions. - - Parameters - ---------- - origin : {'scanner', 'fov'} - Transformation origin. By default the transformation is computed - relative to the scanner's iso center. If 'fov' is requested the - transformation origin will be the center of the field of view - instead. - - Returns - ------- - aff : (4, 4) array - 4x4 array, with output axis order corresponding to RAS or (x,y,z) - or (lr, pa, fh). - - Notes - ----- - Transformations appear to be specified in (ap, fh, rl) axes. The - orientation of data is recorded in the "slice orientation" field of the - PAR header "General Information". - - We need to: - - * translate to coordinates in terms of the center of the FOV - * apply voxel size scaling - * reorder / flip the data to Philips' PSL axes - * apply the rotations - * apply any isocenter scaling offset if `origin` == "scanner" - * reorder and flip to RAS axes - """ - # shape, zooms in original data ordering (ijk ordering) - ijk_shape = np.array(self.get_data_shape()[:3]) - to_center = from_matvec(np.eye(3), -(ijk_shape - 1) / 2.0) - zoomer = np.diag(list(self.get_zooms()[:3]) + [1]) - slice_orientation = self.get_slice_orientation() - permute_to_psl = ACQ_TO_PSL.get(slice_orientation) - if permute_to_psl is None: - raise PARRECError(f'Unknown slice orientation ({slice_orientation}).') - # hdr has deg, we need radians - # Order is [ap, fh, rl] - ap_rot, fh_rot, rl_rot = self.general_info['angulation'] * DEG2RAD - Mx = euler2mat(x=ap_rot) - My = euler2mat(y=fh_rot) - Mz = euler2mat(z=rl_rot) - # By trial and error, this unexpected order of rotations seem to give - # the closest to the observed (converted NIfTI) affine. - rot = from_matvec(dot_reduce(Mz, Mx, My)) - # compose the PSL affine - psl_aff = dot_reduce(rot, permute_to_psl, zoomer, to_center) - if origin == 'scanner': - # offset to scanner's isocenter (in ap, fh, rl) - iso_offset = self.general_info['off_center'] - psl_aff[:3, 3] += iso_offset - # Currently in PSL; apply PSL -> RAS - return np.dot(PSL_TO_RAS, psl_aff) - - def _get_n_slices(self): - """Get number of slices for output data""" - return len(set(self.image_defs['slice number'])) - - def _get_n_vols(self): - """Get number of volumes for output data""" - slice_nos = self.image_defs['slice number'] - vol_nos = vol_numbers(slice_nos) - is_full = vol_is_full(slice_nos, self.general_info['max_slices']) - return len(set(np.array(vol_nos)[is_full])) - - def _calc_data_shape(self): - """Calculate the output shape of the image data - - Returns length 3 tuple for 3D image, length 4 tuple for 4D. - - Returns - ------- - n_inplaneX : int - number of voxels in X direction. - n_inplaneY : int - number of voxels in Y direction. - n_slices : int - number of slices. - n_vols : int - number of volumes or absent for 3D image. - - Notes - ----- - This routine gets called in ``__init__``, so may not be able to use - some attributes available in the fully initialized object. - """ - inplane_shape = tuple(self._get_unique_image_prop('recon resolution')) - shape = inplane_shape + (self._get_n_slices(),) - n_vols = self._get_n_vols() - return shape + (n_vols,) if n_vols > 1 else shape - - def get_data_scaling(self, method='dv'): - """Returns scaling slope and intercept. - - Parameters - ---------- - method : {'fp', 'dv'} - Scaling settings to be reported -- see notes below. - - Returns - ------- - slope : array - scaling slope - intercept : array - scaling intercept - - Notes - ----- - The PAR header contains two different scaling settings: 'dv' (value on - console) and 'fp' (floating point value). Here is how they are defined: - - DV = PV * RS + RI - FP = DV / (RS * SS) - - where: - - PV: value in REC - RS: rescale slope - RI: rescale intercept - SS: scale slope - """ - # These will be 3D or 4D - scale_slope = self.image_defs['scale slope'] - rescale_slope = self.image_defs['rescale slope'] - rescale_intercept = self.image_defs['rescale intercept'] - if method == 'dv': - slope, intercept = rescale_slope, rescale_intercept - elif method == 'fp': - slope = 1.0 / scale_slope - intercept = rescale_intercept / (rescale_slope * scale_slope) - else: - raise ValueError(f"Unknown scaling method '{method}'.") - reorder = self.get_sorted_slice_indices() - slope = slope[reorder] - intercept = intercept[reorder] - shape = (1, 1) + self.get_data_shape()[2:] - slope = slope.reshape(shape, order='F') - intercept = intercept.reshape(shape, order='F') - return slope, intercept - - def get_slice_orientation(self): - """Returns the slice orientation label. - - Returns - ------- - orientation : {'transverse', 'sagittal', 'coronal'} - """ - lab = self._get_unique_image_prop('slice orientation') - return slice_orientation_codes.label[lab] - - def get_rec_shape(self): - inplane_shape = tuple(self._get_unique_image_prop('recon resolution')) - return inplane_shape + (len(self.image_defs),) - - def _strict_sort_order(self): - """Determine the sort order based on several image definition fields. - - The fields taken into consideration, if present, are (in order from - slowest to fastest variation after sorting): - - - image_defs['image_type_mr'] # Re, Im, Mag, Phase - - image_defs['dynamic scan number'] # repetition - - image_defs['label type'] # ASL tag/control - - image_defs['diffusion b value number'] # diffusion b value - - image_defs['gradient orientation number'] # diffusion directoin - - image_defs['cardiac phase number'] # cardiac phase - - image_defs['echo number'] # echo - - image_defs['slice number'] # slice - - Data sorting is done in two stages: - - 1. an initial sort using the keys described above - 2. a resort after generating two additional sort keys: - - * a key to assign unique volume numbers to any volumes that - didn't have a unique sort based on the keys above - (see :func:`vol_numbers`). - * a sort key based on `vol_is_full` to identify truncated - volumes - - A case where the initial sort may not create a unique label for each - volume is diffusion scans acquired in the older V4 .PAR format, where - diffusion direction info is not available. - """ - # sort keys present in all supported .PAR versions - idefs = self.image_defs - slice_nos = idefs['slice number'] - dynamics = idefs['dynamic scan number'] - phases = idefs['cardiac phase number'] - echos = idefs['echo number'] - image_type = idefs['image_type_mr'] - - # sort keys only present in a subset of .PAR files - asl_keys = (idefs['label type'],) if 'label type' in idefs.dtype.names else () - if self.general_info['diffusion'] != 0: - bvals = self.get_def('diffusion b value number') - if bvals is None: - bvals = self.get_def('diffusion_b_factor') - bvecs = self.get_def('gradient orientation number') - if bvecs is None: - # no b-vectors available - diffusion_keys = (bvals,) - else: - diffusion_keys = (bvecs, bvals) - else: - diffusion_keys = () - - # initial sort (last key is highest precedence) - keys = (slice_nos, echos, phases) + diffusion_keys + asl_keys + (dynamics, image_type) - initial_sort_order = np.lexsort(keys) - - # sequentially number the volumes based on the initial sort - vol_nos = vol_numbers(slice_nos[initial_sort_order]) - # identify truncated volumes - is_full = vol_is_full(slice_nos[initial_sort_order], self.general_info['max_slices']) - - # second stage of sorting - return initial_sort_order[np.lexsort((vol_nos, is_full))] - - def _lax_sort_order(self): - """ - Sorts by (fast to slow): slice number, volume number. - - We calculate volume number by looking for repeating slice numbers (see - :func:`vol_numbers`). - """ - slice_nos = self.image_defs['slice number'] - is_full = vol_is_full(slice_nos, self.general_info['max_slices']) - keys = (slice_nos, vol_numbers(slice_nos), np.logical_not(is_full)) - return np.lexsort(keys) - - def get_sorted_slice_indices(self): - """Return indices to sort (and maybe discard) slices in REC file. - - If the recording is truncated, the returned indices take care of - discarding any slice indices from incomplete volumes. - - If `self.strict_sort` is True, a more complicated sorting based on - multiple fields from the .PAR file is used. This may produce a - different sort order than `strict_sort=False`, where volumes are sorted - by the order in which the slices appear in the .PAR file. - - Returns - ------- - slice_indices : list - List for indexing into the last (third) dimension of the REC data - array, and (equivalently) the only dimension of - ``self.image_defs``. - """ - if not self.strict_sort: - sort_order = self._lax_sort_order() - else: - sort_order = self._strict_sort_order() - - # Figure out how many we need to remove from the end, and trim them. - # Based on our sorting, they should always be last. - n_used = np.prod(self.get_data_shape()[2:]) - return sort_order[:n_used] - - def get_volume_labels(self): - """Dynamic labels corresponding to the final data dimension(s). - - This is useful for custom data sorting. A subset of the info in - ``self.image_defs`` is returned in an order that matches the final - data dimension(s). Only labels that have more than one unique value - across the dataset will be returned. - - Returns - ------- - sort_info : dict - Each key corresponds to volume labels for a dynamically varying - sequence dimension. The ordering of the labels matches the volume - ordering determined via ``self.get_sorted_slice_indices``. - """ - sorted_indices = self.get_sorted_slice_indices() - image_defs = self.image_defs - - # define which keys which might vary across image volumes - dynamic_keys = [ - 'cardiac phase number', - 'echo number', - 'label type', - 'image_type_mr', - 'dynamic scan number', - 'scanning sequence', - 'gradient orientation number', - 'diffusion b value number', - ] - - # remove dynamic keys that may not be present in older .PAR versions - dynamic_keys = [d for d in dynamic_keys if d in image_defs.dtype.fields] - - non_unique_keys = [] - for key in dynamic_keys: - ndim = image_defs[key].ndim - if ndim == 1: - num_unique = len(np.unique(image_defs[key])) - else: - raise ValueError('unexpected image_defs shape > 1D') - if num_unique > 1: - non_unique_keys.append(key) - - # each key in dynamic keys will be identical across slices, so use - # the value at slice 1. - sl1_indices = image_defs['slice number'][sorted_indices] == 1 - - sort_info = OrderedDict() - for key in non_unique_keys: - sort_info[key] = image_defs[key][sorted_indices][sl1_indices] - return sort_info - - -class PARRECImage(SpatialImage): - """PAR/REC image""" - - header_class = PARRECHeader - header: PARRECHeader - valid_exts = ('.rec', '.par') - files_types = (('image', '.rec'), ('header', '.par')) - - makeable = False - rw = False - - ImageArrayProxy = PARRECArrayProxy - - @classmethod - def from_file_map( - klass, file_map, *, mmap=True, permit_truncated=False, scaling='dv', strict_sort=False - ): - """Create PARREC image from file map `file_map` - - Parameters - ---------- - file_map : dict - dict with keys ``image, header`` and values being fileholder - objects for the respective REC and PAR files. - mmap : {True, False, 'c', 'r'}, optional, keyword only - `mmap` controls the use of numpy memory mapping for reading image - array data. If False, do not try numpy ``memmap`` for data array. - If one of {'c', 'r'}, try numpy memmap with ``mode=mmap``. A - `mmap` value of True gives the same behavior as ``mmap='c'``. If - image data file cannot be memory-mapped, ignore `mmap` value and - read array from file. - permit_truncated : {False, True}, optional, keyword-only - If False, raise an error for an image where the header shows signs - that fewer slices / volumes were recorded than were expected. - scaling : {'dv', 'fp'}, optional, keyword-only - Scaling method to apply to data (see - :meth:`PARRECHeader.get_data_scaling`). - strict_sort : bool, optional, keyword-only - If True, a larger number of header fields are used while sorting - the REC data array. This may produce a different sort order than - `strict_sort=False`, where volumes are sorted by the order in which - the slices appear in the .PAR file. - """ - with file_map['header'].get_prepare_fileobj('rt') as hdr_fobj: - hdr = klass.header_class.from_fileobj( - hdr_fobj, permit_truncated=permit_truncated, strict_sort=strict_sort - ) - rec_fobj = file_map['image'].get_prepare_fileobj() - data = klass.ImageArrayProxy(rec_fobj, hdr, mmap=mmap, scaling=scaling) - return klass(data, hdr.get_affine(), header=hdr, extra=None, file_map=file_map) - - @classmethod - def from_filename( - klass, filename, *, mmap=True, permit_truncated=False, scaling='dv', strict_sort=False - ): - """Create PARREC image from filename `filename` - - Parameters - ---------- - filename : str - Filename of "PAR" or "REC" file - mmap : {True, False, 'c', 'r'}, optional, keyword only - `mmap` controls the use of numpy memory mapping for reading image - array data. If False, do not try numpy ``memmap`` for data array. - If one of {'c', 'r'}, try numpy memmap with ``mode=mmap``. A - `mmap` value of True gives the same behavior as ``mmap='c'``. If - image data file cannot be memory-mapped, ignore `mmap` value and - read array from file. - permit_truncated : {False, True}, optional, keyword-only - If False, raise an error for an image where the header shows signs - that fewer slices / volumes were recorded than were expected. - scaling : {'dv', 'fp'}, optional, keyword-only - Scaling method to apply to data (see - :meth:`PARRECHeader.get_data_scaling`). - strict_sort : bool, optional, keyword-only - If True, a larger number of header fields are used while sorting - the REC data array. This may produce a different sort order than - `strict_sort=False`, where volumes are sorted by the order in which - the slices appear in the .PAR file. - """ - file_map = klass.filespec_to_file_map(filename) - return klass.from_file_map( - file_map, - mmap=mmap, - permit_truncated=permit_truncated, - scaling=scaling, - strict_sort=strict_sort, - ) - - load = from_filename # type: ignore[assignment] - - -load = PARRECImage.from_filename diff --git a/nibabel/pkg_info.py b/nibabel/pkg_info.py deleted file mode 100644 index 7232806a0a..0000000000 --- a/nibabel/pkg_info.py +++ /dev/null @@ -1,143 +0,0 @@ -from __future__ import annotations - -import sys -from contextlib import suppress -from subprocess import run - -from packaging.version import Version - -try: - from ._version import __version__ -except ImportError: - __version__ = '0+unknown' - - -COMMIT_HASH = '$Format:%h$' - - -def _cmp(a: Version, b: Version) -> int: - """Implementation of ``cmp`` for Python 3""" - return (a > b) - (a < b) - - -def cmp_pkg_version(version_str: str, pkg_version_str: str = __version__) -> int: - """Compare ``version_str`` to current package version - - This comparator follows `PEP-440`_ conventions for determining version - ordering. - - To be valid, a version must have a numerical major version. It may be - optionally followed by a dot and a numerical minor version, which may, - in turn, optionally be followed by a dot and a numerical micro version, - and / or by an "extra" string. - The extra string may further contain a "+". Any value to the left of a "+" - labels the version as pre-release, while values to the right indicate a - post-release relative to the values to the left. That is, - ``1.2.0+1`` is post-release for ``1.2.0``, while ``1.2.0rc1+1`` is - post-release for ``1.2.0rc1`` and pre-release for ``1.2.0``. - - Parameters - ---------- - version_str : str - Version string to compare to current package version - pkg_version_str : str, optional - Version of our package. Optional, set from ``__version__`` by default. - - Returns - ------- - version_cmp : int - 1 if `version_str` is a later version than `pkg_version_str`, 0 if - same, -1 if earlier. - - Examples - -------- - >>> cmp_pkg_version('1.2.1', '1.2.0') - 1 - >>> cmp_pkg_version('1.2.0dev', '1.2.0') - -1 - >>> cmp_pkg_version('1.2.0dev', '1.2.0rc1') - -1 - >>> cmp_pkg_version('1.2.0rc1', '1.2.0') - -1 - >>> cmp_pkg_version('1.2.0rc1+1', '1.2.0rc1') - 1 - >>> cmp_pkg_version('1.2.0rc1+1', '1.2.0') - -1 - >>> cmp_pkg_version('1.2.0.post1', '1.2.0') - 1 - - .. _`PEP-440`: https://www.python.org/dev/peps/pep-0440/ - """ - return _cmp(Version(version_str), Version(pkg_version_str)) - - -def pkg_commit_hash(pkg_path: str | None = None) -> tuple[str, str]: - """Get short form of commit hash - - In this file is a variable called COMMIT_HASH. This contains a substitution - pattern that may have been filled by the execution of ``git archive``. - - We get the commit hash from (in order of preference): - - * A substituted value in ``archive_subst_hash`` - * A truncated commit hash value that is part of the local portion of the - version - * git's output, if we are in a git repository - - If all these fail, we return a not-found placeholder tuple - - Parameters - ---------- - pkg_path : str - directory containing package - - Returns - ------- - hash_from : str - Where we got the hash from - description - hash_str : str - short form of hash - """ - if not COMMIT_HASH.startswith('$Format'): # it has been substituted - return 'archive substitution', COMMIT_HASH - ver = Version(__version__) - if ver.local is not None and ver.local.startswith('g'): - return 'installation', ver.local[1:8] - # maybe we are in a repository, but consider that we may not have git - with suppress(FileNotFoundError): - proc = run( - ('git', 'rev-parse', '--short', 'HEAD'), - capture_output=True, - cwd=pkg_path, - ) - if proc.stdout: - return 'repository', proc.stdout.decode().strip() - - return '(none found)', '' - - -def get_pkg_info(pkg_path: str) -> dict[str, str]: - """Return dict describing the context of this package - - Parameters - ---------- - pkg_path : str - path containing __init__.py for package - - Returns - ------- - context : dict - with named parameters of interest - """ - src, hsh = pkg_commit_hash(pkg_path) - import numpy - - return dict( - pkg_path=pkg_path, - commit_source=src, - commit_hash=hsh, - sys_version=sys.version, - sys_executable=sys.executable, - sys_platform=sys.platform, - np_version=numpy.__version__, - ) diff --git a/nibabel/pointset.py b/nibabel/pointset.py deleted file mode 100644 index 1d20b82fe5..0000000000 --- a/nibabel/pointset.py +++ /dev/null @@ -1,197 +0,0 @@ -"""Point-set structures - -Imaging data are sampled at points in space, and these points -can be described by coordinates. -These structures are designed to enable operations on sets of -points, as opposed to the data sampled at those points. - -Abstractly, a point set is any collection of points, but there are -two types that warrant special consideration in the neuroimaging -context: grids and meshes. - -A *grid* is a collection of regularly-spaced points. The canonical -examples of grids are the indices of voxels and their affine -projection into a reference space. - -A *mesh* is a collection of points and some structure that enables -adjacent points to be identified. A *triangular mesh* in particular -uses triplets of adjacent vertices to describe faces. -""" - -from __future__ import annotations - -import math -import typing as ty -from dataclasses import dataclass, replace - -import numpy as np - -from nibabel.casting import able_int_type -from nibabel.fileslice import strided_scalar -from nibabel.spatialimages import SpatialImage - -if ty.TYPE_CHECKING: - from ._typing import Self, TypeVar - - _DType = TypeVar('_DType', bound=np.dtype[ty.Any]) - - -class CoordinateArray(ty.Protocol): - ndim: int - shape: tuple[int, int] - - @ty.overload - def __array__(self, dtype: None = ..., /) -> np.ndarray[ty.Any, np.dtype[ty.Any]]: ... - - @ty.overload - def __array__(self, dtype: _DType, /) -> np.ndarray[ty.Any, _DType]: ... - - -@dataclass -class Pointset: - """A collection of points described by coordinates. - - Parameters - ---------- - coords : array-like - (*N*, *n*) array with *N* being points and columns their *n*-dimensional coordinates - affine : :class:`numpy.ndarray` - Affine transform to be applied to coordinates array - homogeneous : :class:`bool` - Indicate whether the provided coordinates are homogeneous, - i.e., homogeneous 3D coordinates have the form ``(x, y, z, 1)`` - """ - - coordinates: CoordinateArray - affine: np.ndarray - homogeneous: bool = False - - # Force use of __rmatmul__ with numpy arrays - __array_priority__ = 99 - - def __init__( - self, - coordinates: CoordinateArray, - affine: np.ndarray | None = None, - homogeneous: bool = False, - ): - self.coordinates = coordinates - self.homogeneous = homogeneous - - if affine is None: - self.affine = np.eye(self.dim + 1) - else: - self.affine = np.asanyarray(affine) - - if self.affine.shape != (self.dim + 1,) * 2: - raise ValueError(f'Invalid affine for {self.dim}D coordinates:\n{self.affine}') - if np.any(self.affine[-1, :-1] != 0) or self.affine[-1, -1] != 1: - raise ValueError(f'Invalid affine matrix:\n{self.affine}') - - @property - def n_coords(self) -> int: - """Number of coordinates - - Subclasses should override with more efficient implementations. - """ - return self.coordinates.shape[0] - - @property - def dim(self) -> int: - """The dimensionality of the space the coordinates are in""" - return self.coordinates.shape[1] - self.homogeneous - - # Use __rmatmul__ to prefer to compose affines. Mypy does not like that - # this conflicts with ndarray.__matmul__. We will need some more feedback - # on how this plays out for type-checking or code suggestions before we - # can do better than ignore. - def __rmatmul__(self, affine: np.ndarray) -> Self: # type: ignore[misc] - """Apply an affine transformation to the pointset - - This will return a new pointset with an updated affine matrix only. - """ - return replace(self, affine=np.asanyarray(affine) @ self.affine) - - def _homogeneous_coords(self): - if self.homogeneous: - return np.asanyarray(self.coordinates) - - ones = strided_scalar( - shape=(self.coordinates.shape[0], 1), - scalar=np.array(1, dtype=self.coordinates.dtype), - ) - return np.hstack((self.coordinates, ones)) - - def get_coords(self, *, as_homogeneous: bool = False): - """Retrieve the coordinates - - Parameters - ---------- - as_homogeneous : :class:`bool` - Return homogeneous coordinates if ``True``, or Cartesian - coordinates if ``False``. - - name : :class:`str` - Select a particular coordinate system if more than one may exist. - By default, `None` is equivalent to `"world"` and corresponds to - an RAS+ coordinate system. - """ - ident = np.allclose(self.affine, np.eye(self.affine.shape[0])) - if self.homogeneous == as_homogeneous and ident: - return np.asanyarray(self.coordinates) - coords = self._homogeneous_coords() - if not ident: - coords = (self.affine @ coords.T).T - if not as_homogeneous: - coords = coords[:, :-1] - return coords - - -class Grid(Pointset): - r"""A regularly-spaced collection of coordinates - - This class provides factory methods for generating Pointsets from - :class:`~nibabel.spatialimages.SpatialImage`\s and generating masks - from coordinate sets. - """ - - @classmethod - def from_image(cls, spatialimage: SpatialImage) -> Self: - return cls(coordinates=GridIndices(spatialimage.shape[:3]), affine=spatialimage.affine) - - @classmethod - def from_mask(cls, mask: SpatialImage) -> Self: - mask_arr = np.bool_(mask.dataobj) - return cls( - coordinates=np.c_[np.nonzero(mask_arr)].astype(able_int_type(mask.shape)), - affine=mask.affine, - ) - - def to_mask(self, shape=None) -> SpatialImage: - if shape is None: - shape = tuple(np.max(self.coordinates, axis=0)[: self.dim] + 1) - mask_arr = np.zeros(shape, dtype='bool') - mask_arr[tuple(np.asanyarray(self.coordinates)[:, : self.dim].T)] = True - return SpatialImage(mask_arr, self.affine) - - -class GridIndices: - """Class for generating indices just-in-time""" - - __slots__ = ('dtype', 'gridshape', 'shape') - ndim = 2 - - def __init__(self, shape, dtype=None): - self.gridshape = shape - self.dtype = dtype or able_int_type(shape) - self.shape = (math.prod(self.gridshape), len(self.gridshape)) - - def __repr__(self): - return f'<{self.__class__.__name__}{self.gridshape}>' - - def __array__(self, dtype=None): - if dtype is None: - dtype = self.dtype - - axes = [np.arange(s, dtype=dtype) for s in self.gridshape] - return np.reshape(np.meshgrid(*axes, copy=False, indexing='ij'), (len(axes), -1)).T diff --git a/nibabel/processing.py b/nibabel/processing.py deleted file mode 100644 index 673ceada63..0000000000 --- a/nibabel/processing.py +++ /dev/null @@ -1,406 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Image processing functions - -Image processing functions for: - - * smoothing - * resampling - * converting SD to and from FWHM - -Smoothing and resampling routines need scipy. -""" - -import numpy as np -import numpy.linalg as npl - -from .optpkg import optional_package - -spnd = optional_package('scipy.ndimage')[0] - -from .affines import AffineError, append_diag, from_matvec, rescale_affine, to_matvec -from .imageclasses import spatial_axes_first -from .nifti1 import Nifti1Image -from .orientations import axcodes2ornt, io_orientation, ornt_transform -from .spaces import vox2out_vox - -SIGMA2FWHM = np.sqrt(8 * np.log(2)) - - -def fwhm2sigma(fwhm): - """Convert a FWHM value to sigma in a Gaussian kernel. - - Parameters - ---------- - fwhm : array-like - FWHM value or values - - Returns - ------- - sigma : array or float - sigma values corresponding to `fwhm` values - - Examples - -------- - >>> sigma = fwhm2sigma(6) - >>> sigmae = fwhm2sigma([6, 7, 8]) - >>> sigma == sigmae[0] - True - """ - return np.asarray(fwhm) / SIGMA2FWHM - - -def sigma2fwhm(sigma): - """Convert a sigma in a Gaussian kernel to a FWHM value - - Parameters - ---------- - sigma : array-like - sigma value or values - - Returns - ------- - fwhm : array or float - fwhm values corresponding to `sigma` values - - Examples - -------- - >>> fwhm = sigma2fwhm(3) - >>> fwhms = sigma2fwhm([3, 4, 5]) - >>> fwhm == fwhms[0] - True - """ - return np.asarray(sigma) * SIGMA2FWHM - - -def adapt_affine(affine, n_dim): - """Adapt input / output dimensions of spatial `affine` for `n_dims` - - Adapts a spatial (4, 4) affine that is being applied to an image with fewer - than 3 spatial dimensions, or more than 3 dimensions. If there are more - than three dimensions, assume an identity transformation for these - dimensions. - - Parameters - ---------- - affine : array-like - affine transform. Usually shape (4, 4). For what follows ``N, M = - affine.shape`` - n_dims : int - Number of dimensions of underlying array, and therefore number of input - dimensions for affine. - - Returns - ------- - adapted : shape (M, n_dims+1) array - Affine array adapted to number of input dimensions. Columns of the - affine corresponding to missing input dimensions have been dropped, - columns corresponding to extra input dimensions have an extra identity - column added - """ - affine = np.asarray(affine) - rzs, trans = to_matvec(affine) - # For missing input dimensions, drop columns in rzs - rzs = rzs[:, :n_dim] - adapted = from_matvec(rzs, trans) - n_extra_columns = n_dim - adapted.shape[1] + 1 - if n_extra_columns > 0: - adapted = append_diag(adapted, np.ones((n_extra_columns,))) - return adapted - - -def resample_from_to( - from_img, - to_vox_map, - order=3, - mode='constant', - cval=0.0, - out_class=Nifti1Image, -): - """Resample image `from_img` to mapped voxel space `to_vox_map` - - Resample using N-d spline interpolation. - - Parameters - ---------- - from_img : object - Object having attributes ``dataobj``, ``affine``, ``header`` and - ``shape``. If `out_class` is not None, ``img.__class__`` should be able - to construct an image from data, affine and header. - to_vox_map : image object or length 2 sequence - If object, has attributes ``shape`` giving input voxel shape, and - ``affine`` giving mapping of input voxels to output space. If length 2 - sequence, elements are (shape, affine) with same meaning as above. The - affine is a (4, 4) array-like. - order : int, optional - The order of the spline interpolation, default is 3. The order has to - be in the range 0-5 (see ``scipy.ndimage.affine_transform``) - mode : str, optional - Points outside the boundaries of the input are filled according - to the given mode ('constant', 'nearest', 'reflect' or 'wrap'). - Default is 'constant' (see ``scipy.ndimage.affine_transform``) - cval : scalar, optional - Value used for points outside the boundaries of the input if - ``mode='constant'``. Default is 0.0 (see - ``scipy.ndimage.affine_transform``) - out_class : None or SpatialImage class, optional - Class of output image. If None, use ``from_img.__class__``. - - Returns - ------- - out_img : object - Image of instance specified by `out_class`, containing data output from - resampling `from_img` into axes aligned to the output space of - ``from_img.affine`` - """ - # This check requires `shape` attribute of image - if not spatial_axes_first(from_img): - raise ValueError( - f'Cannot predict position of spatial axes for Image type {type(from_img)}' - ) - try: - to_shape, to_affine = to_vox_map.shape, to_vox_map.affine - except AttributeError: - to_shape, to_affine = to_vox_map - a_to_affine = adapt_affine(to_affine, len(to_shape)) - if out_class is None: - out_class = from_img.__class__ - from_n_dim = len(from_img.shape) - if from_n_dim < 3: - raise AffineError('from_img must be at least 3D') - a_from_affine = adapt_affine(from_img.affine, from_n_dim) - to_vox2from_vox = npl.inv(a_from_affine).dot(a_to_affine) - rzs, trans = to_matvec(to_vox2from_vox) - data = spnd.affine_transform( - from_img.dataobj, rzs, trans, to_shape, order=order, mode=mode, cval=cval - ) - return out_class(data, to_affine, from_img.header) - - -def resample_to_output( - in_img, - voxel_sizes=None, - order=3, - mode='constant', - cval=0.0, - out_class=Nifti1Image, -): - """Resample image `in_img` to output voxel axes (world space) - - Parameters - ---------- - in_img : object - Object having attributes ``dataobj``, ``affine``, ``header``. If - `out_class` is not None, ``img.__class__`` should be able to construct - an image from data, affine and header. - voxel_sizes : None or sequence - Gives the diagonal entries of ``out_img.affine` (except the trailing 1 - for the homogeneous coordinates) (``out_img.affine == - np.diag(voxel_sizes + [1])``). If None, return identity - `out_img.affine`. If scalar, interpret as vector ``[voxel_sizes] * - len(in_img.shape)``. - order : int, optional - The order of the spline interpolation, default is 3. The order has to - be in the range 0-5 (see ``scipy.ndimage.affine_transform``). - mode : str, optional - Points outside the boundaries of the input are filled according to the - given mode ('constant', 'nearest', 'reflect' or 'wrap'). Default is - 'constant' (see ``scipy.ndimage.affine_transform``). - cval : scalar, optional - Value used for points outside the boundaries of the input if - ``mode='constant'``. Default is 0.0 (see - ``scipy.ndimage.affine_transform``). - out_class : None or SpatialImage class, optional - Class of output image. If None, use ``in_img.__class__``. - - Returns - ------- - out_img : object - Image of instance specified by `out_class`, containing data output from - resampling `in_img` into axes aligned to the output space of - ``in_img.affine`` - """ - if out_class is None: - out_class = in_img.__class__ - in_shape = in_img.shape - n_dim = len(in_shape) - if voxel_sizes is not None: - voxel_sizes = np.asarray(voxel_sizes) - if voxel_sizes.ndim == 0: # Scalar - voxel_sizes = np.repeat(voxel_sizes, n_dim) - # Allow 2D images by promoting to 3D. We might want to see what a slice - # looks like when resampled into world coordinates - if n_dim < 3: # Expand image to 3D, make voxel sizes match - new_shape = in_shape + (1,) * (3 - n_dim) - data = np.asanyarray(in_img.dataobj).reshape(new_shape) # 2D data should be small - in_img = out_class(data, in_img.affine, in_img.header) - if voxel_sizes is not None and len(voxel_sizes) == n_dim: - # Need to pad out voxel sizes to match new image dimensions - voxel_sizes = tuple(voxel_sizes) + (1,) * (3 - n_dim) - out_vox_map = vox2out_vox((in_img.shape, in_img.affine), voxel_sizes) - return resample_from_to(in_img, out_vox_map, order, mode, cval, out_class) - - -def smooth_image( - img, - fwhm, - mode='nearest', - cval=0.0, - out_class=Nifti1Image, -): - """Smooth image `img` along voxel axes by FWHM `fwhm` millimeters - - Parameters - ---------- - img : object - Object having attributes ``dataobj``, ``affine``, ``header`` and - ``shape``. If `out_class` is not None, ``img.__class__`` should be able - to construct an image from data, affine and header. - fwhm : scalar or length 3 sequence - FWHM *in mm* over which to smooth. The smoothing applies to the voxel - axes, not to the output axes, but is in millimeters. The function - adjusts the FWHM to voxels using the voxel sizes calculated from the - affine. A scalar implies the same smoothing across the spatial - dimensions of the image, but 0 smoothing over any further dimensions - such as time. A vector should be the same length as the number of - image dimensions. - mode : str, optional - Points outside the boundaries of the input are filled according - to the given mode ('constant', 'nearest', 'reflect' or 'wrap'). - Default is 'nearest'. This is different from the default for - ``scipy.ndimage.affine_transform``, which is 'constant'. 'nearest' - might be a better choice when smoothing to the edge of an image where - there is still strong brain signal, otherwise this signal will get - blurred towards zero. - cval : scalar, optional - Value used for points outside the boundaries of the input if - ``mode='constant'``. Default is 0.0 (see - ``scipy.ndimage.affine_transform``). - out_class : None or SpatialImage class, optional - Class of output image. If None, use ``img.__class__``. - - Returns - ------- - smoothed_img : object - Image of instance specified by `out_class`, containing data output from - smoothing `img` data by given FWHM kernel. - """ - # This check requires `shape` attribute of image - if not spatial_axes_first(img): - raise ValueError(f'Cannot predict position of spatial axes for Image type {type(img)}') - if out_class is None: - out_class = img.__class__ - n_dim = len(img.shape) - # TODO: make sure time axis is last - # Pad out fwhm from scalar, adding 0 for fourth etc (time etc) dimensions - fwhm = np.asarray(fwhm) - if fwhm.size == 1: - fwhm_scalar = fwhm - fwhm = np.zeros((n_dim,)) - fwhm[:3] = fwhm_scalar - # Voxel sizes - RZS = img.affine[:, :n_dim] - vox = np.sqrt(np.sum(RZS**2, 0)) - # Smoothing in terms of voxels - vox_fwhm = fwhm / vox - vox_sd = fwhm2sigma(vox_fwhm) - # Do the smoothing - sm_data = spnd.gaussian_filter(img.dataobj, vox_sd, mode=mode, cval=cval) - return out_class(sm_data, img.affine, img.header) - - -def conform( - from_img, - out_shape=(256, 256, 256), - voxel_size=(1.0, 1.0, 1.0), - order=3, - mode='constant', - cval=0.0, - orientation='RAS', - out_class=None, -): - """Resample image to ``out_shape`` with voxels of size ``voxel_size``. - - Using the default arguments, this function is meant to replicate most parts - of FreeSurfer's ``mri_convert --conform`` command. Specifically, this - function: - - - Resamples data to ``output_shape`` - - Resamples voxel sizes to ``voxel_size`` - - Reorients to RAS (``mri_convert --conform`` reorients to LIA) - - Unlike ``mri_convert --conform``, this command does not: - - - Transform data to range [0, 255] - - Cast to unsigned eight-bit integer - - Parameters - ---------- - from_img : object - Object having attributes ``dataobj``, ``affine``, ``header`` and - ``shape``. If `out_class` is not None, ``img.__class__`` should be able - to construct an image from data, affine and header. - out_shape : sequence, optional - The shape of the output volume. Default is (256, 256, 256). - voxel_size : sequence, optional - The size in millimeters of the voxels in the resampled output. Default - is 1mm isotropic. - order : int, optional - The order of the spline interpolation, default is 3. The order has to - be in the range 0-5 (see ``scipy.ndimage.affine_transform``) - mode : str, optional - Points outside the boundaries of the input are filled according to the - given mode ('constant', 'nearest', 'reflect' or 'wrap'). Default is - 'constant' (see :func:`scipy.ndimage.affine_transform`) - cval : scalar, optional - Value used for points outside the boundaries of the input if - ``mode='constant'``. Default is 0.0 (see - ``scipy.ndimage.affine_transform``) - orientation : str, optional - Orientation of output image. Default is "RAS". - out_class : None or SpatialImage class, optional - Class of output image. If None, use ``from_img.__class__``. - - Returns - ------- - out_img : object - Image of instance specified by `out_class`, containing data output from - resampling `from_img` into axes aligned to the output space of - ``from_img.affine`` - """ - # Only support 3D images. This can be made more general in the future, once tests - # are written. - required_ndim = 3 - if from_img.ndim != required_ndim: - raise ValueError('Only 3D images are supported.') - elif len(out_shape) != required_ndim: - raise ValueError(f'`out_shape` must have {required_ndim} values') - elif len(voxel_size) != required_ndim: - raise ValueError(f'`voxel_size` must have {required_ndim} values') - - start_ornt = io_orientation(from_img.affine) - end_ornt = axcodes2ornt(orientation) - transform = ornt_transform(start_ornt, end_ornt) - - # Reorient first to ensure shape matches expectations - reoriented = from_img.as_reoriented(transform) - - out_aff = rescale_affine(reoriented.affine, reoriented.shape, voxel_size, out_shape) - - # Resample input image. - out_img = resample_from_to( - from_img=from_img, - to_vox_map=(out_shape, out_aff), - order=order, - mode=mode, - cval=cval, - out_class=out_class, - ) - - return out_img diff --git a/nibabel/py.typed b/nibabel/py.typed deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/nibabel/pydicom_compat.py b/nibabel/pydicom_compat.py deleted file mode 100644 index 76423b40a8..0000000000 --- a/nibabel/pydicom_compat.py +++ /dev/null @@ -1,60 +0,0 @@ -"""Adapter module for working with pydicom < 1.0 and >= 1.0 - -In what follows, "dicom is available" means we can import either a) ``dicom`` -(pydicom < 1.0) or or b) ``pydicom`` (pydicom >= 1.0). - -Regardless of whether dicom is available this module should be importable -without error, and always defines: - -* have_dicom : True if we can import pydicom or dicom; -* 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 - is importable else None; - -A test decorator is available in nibabel.nicom.tests: - -* dicom_test : test decorator that skips test if dicom not available. - -A deprecated copy is available here for backward compatibility. -""" - -from __future__ import annotations - -import warnings -from typing import Callable - -from .deprecated import deprecate_with_version -from .optpkg import optional_package - -warnings.warn( - "We will remove the 'pydicom_compat' module from nibabel 7.0. " - "Please consult pydicom's documentation for any future needs.", - DeprecationWarning, - stacklevel=2, -) - -pydicom, have_dicom, _ = optional_package('pydicom') - -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 # type: ignore[import-not-found] - from pydicom.dicomio import dcmread as read_file # noqa:F401 - from pydicom.sequence import Sequence # noqa:F401 - - tag_for_keyword = pydicom.datadict.tag_for_keyword - - -@deprecate_with_version( - 'dicom_test has been moved to nibabel.nicom.tests', since='3.1', until='5.0' -) -def dicom_test(func): - # Import locally to avoid circular dependency - from .nicom.tests import dicom_test - - return dicom_test(func) diff --git a/nibabel/quaternions.py b/nibabel/quaternions.py deleted file mode 100644 index 77cf8d2d3f..0000000000 --- a/nibabel/quaternions.py +++ /dev/null @@ -1,510 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -""" -Functions to operate on, or return, quaternions - -The module also includes functions for the closely related angle, axis -pair as a specification for rotation. - -Quaternions here consist of 4 values ``w, x, y, z``, where ``w`` is the -real (scalar) part, and ``x, y, z`` are the complex (vector) part. - -Note - rotation matrices here apply to column vectors, that is, -they are applied on the left of the vector. For example: - ->>> import numpy as np ->>> from nibabel.quaternions import quat2mat ->>> q = [0, 1, 0, 0] # 180 degree rotation around axis 0 ->>> M = quat2mat(q) # from this module ->>> vec = np.array([1, 2, 3]).reshape((3,1)) # column vector ->>> tvec = np.dot(M, vec) -""" - -import math - -import numpy as np - -from .casting import sctypes - -MAX_FLOAT = sctypes['float'][-1] -FLOAT_EPS = np.finfo(float).eps - - -def fillpositive(xyz, w2_thresh=None): - """Compute unit quaternion from last 3 values - - Parameters - ---------- - xyz : iterable - iterable containing 3 values, corresponding to quaternion x, y, z - w2_thresh : None or float, optional - threshold to determine if w squared is non-zero. - If None (default) then w2_thresh set equal to - 3 * ``np.finfo(xyz.dtype).eps``, if possible, otherwise - 3 * ``np.finfo(np.float64).eps`` - - Returns - ------- - wxyz : array shape (4,) - Full 4 values of quaternion - - Notes - ----- - If w, x, y, z are the values in the full quaternion, assumes w is - positive. - - Gives error if w*w is estimated to be negative - - w = 0 corresponds to a 180 degree rotation - - The unit quaternion specifies that np.dot(wxyz, wxyz) == 1. - - If w is positive (assumed here), w is given by: - - w = np.sqrt(1.0-(x*x+y*y+z*z)) - - w2 = 1.0-(x*x+y*y+z*z) can be near zero, which will lead to - numerical instability in sqrt. Here we use the system maximum - float type to reduce numerical instability - - Examples - -------- - >>> import numpy as np - >>> wxyz = fillpositive([0,0,0]) - >>> np.all(wxyz == [1, 0, 0, 0]) - True - >>> wxyz = fillpositive([1,0,0]) # Corner case; w is 0 - >>> np.all(wxyz == [0, 1, 0, 0]) - True - >>> np.dot(wxyz, wxyz) - 1.0 - """ - # Check inputs (force error if < 3 values) - if len(xyz) != 3: - raise ValueError('xyz should have length 3') - # If necessary, guess precision of input - if w2_thresh is None: - try: # trap errors for non-array, integer array - w2_thresh = np.finfo(xyz.dtype).eps * 3 - except (AttributeError, ValueError): - w2_thresh = FLOAT_EPS * 3 - # Use maximum precision - xyz = np.asarray(xyz, dtype=MAX_FLOAT) - # Calculate w - w2 = 1.0 - xyz @ xyz - if np.abs(w2) < np.abs(w2_thresh): - w = 0 - elif w2 < 0: - raise ValueError(f'w2 should be positive, but is {w2:e}') - else: - w = np.sqrt(w2) - return np.r_[w, xyz] - - -def quat2mat(q): - """Calculate rotation matrix corresponding to quaternion - - Parameters - ---------- - q : 4 element array-like - - Returns - ------- - M : (3,3) array - Rotation matrix corresponding to input quaternion *q* - - Notes - ----- - Rotation matrix applies to column vectors, and is applied to the - left of coordinate vectors. The algorithm here allows non-unit - quaternions. - - References - ---------- - Algorithm from - https://en.wikipedia.org/wiki/Rotation_matrix#Quaternion - - Examples - -------- - >>> import numpy as np - >>> M = quat2mat([1, 0, 0, 0]) # Identity quaternion - >>> np.allclose(M, np.eye(3)) - True - >>> M = quat2mat([0, 1, 0, 0]) # 180 degree rotn around axis 0 - >>> np.allclose(M, np.diag([1, -1, -1])) - True - """ - w, x, y, z = q - Nq = w * w + x * x + y * y + z * z - if Nq < FLOAT_EPS: - return np.eye(3) - s = 2.0 / Nq - X = x * s - Y = y * s - Z = z * s - wX, wY, wZ = w * X, w * Y, w * Z - xX, xY, xZ = x * X, x * Y, x * Z - yY, yZ, zZ = y * Y, y * Z, z * Z - return np.array( - [ - [1.0 - (yY + zZ), xY - wZ, xZ + wY], - [xY + wZ, 1.0 - (xX + zZ), yZ - wX], - [xZ - wY, yZ + wX, 1.0 - (xX + yY)], - ] - ) - - -def mat2quat(M): - """Calculate quaternion corresponding to given rotation matrix - - Parameters - ---------- - M : array-like - 3x3 rotation matrix - - Returns - ------- - q : (4,) array - closest quaternion to input matrix, having positive q[0] - - Notes - ----- - Method claimed to be robust to numerical errors in M - - Constructs quaternion by calculating maximum eigenvector for matrix - K (constructed from input `M`). Although this is not tested, a - maximum eigenvalue of 1 corresponds to a valid rotation. - - A quaternion q*-1 corresponds to the same rotation as q; thus the - sign of the reconstructed quaternion is arbitrary, and we return - quaternions with positive w (q[0]). - - References - ---------- - * https://en.wikipedia.org/wiki/Rotation_matrix#Quaternion - * Bar-Itzhack, Itzhack Y. (2000), "New method for extracting the - quaternion from a rotation matrix", AIAA Journal of Guidance, - Control and Dynamics 23(6):1085-1087 (Engineering Note), ISSN - 0731-5090 - - Examples - -------- - >>> import numpy as np - >>> q = mat2quat(np.eye(3)) # Identity rotation - >>> np.allclose(q, [1, 0, 0, 0]) - True - >>> q = mat2quat(np.diag([1, -1, -1])) - >>> np.allclose(q, [0, 1, 0, 0]) # 180 degree rotn around axis 0 - True - - """ - # Qyx refers to the contribution of the y input vector component to - # the x output vector component. Qyx is therefore the same as - # M[0,1]. The notation is from the Wikipedia article. - Qxx, Qyx, Qzx, Qxy, Qyy, Qzy, Qxz, Qyz, Qzz = M.flat - # Fill only lower half of symmetric matrix - K = ( - np.array( - [ - [Qxx - Qyy - Qzz, 0, 0, 0], - [Qyx + Qxy, Qyy - Qxx - Qzz, 0, 0], - [Qzx + Qxz, Qzy + Qyz, Qzz - Qxx - Qyy, 0], - [Qyz - Qzy, Qzx - Qxz, Qxy - Qyx, Qxx + Qyy + Qzz], - ] - ) - / 3.0 - ) - # Use Hermitian eigenvectors, values for speed - vals, vecs = np.linalg.eigh(K) - # Select largest eigenvector, reorder to w,x,y,z quaternion - q = vecs[[3, 0, 1, 2], np.argmax(vals)] - # Prefer quaternion with positive w - # (q * -1 corresponds to same rotation as q) - if q[0] < 0: - q *= -1 - return q - - -def mult(q1, q2): - """Multiply two quaternions - - Parameters - ---------- - q1 : 4 element sequence - q2 : 4 element sequence - - Returns - ------- - q12 : shape (4,) array - - Notes - ----- - See : https://en.wikipedia.org/wiki/Quaternions#Hamilton_product - """ - w1, x1, y1, z1 = q1 - w2, x2, y2, z2 = q2 - w = w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2 - x = w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2 - y = w1 * y2 + y1 * w2 + z1 * x2 - x1 * z2 - z = w1 * z2 + z1 * w2 + x1 * y2 - y1 * x2 - return np.array([w, x, y, z]) - - -def conjugate(q): - """Conjugate of quaternion - - Parameters - ---------- - q : 4 element sequence - w, i, j, k of quaternion - - Returns - ------- - conjq : array shape (4,) - w, i, j, k of conjugate of `q` - """ - return np.array(q) * np.array([1.0, -1, -1, -1]) - - -def norm(q): - """Return norm of quaternion - - Parameters - ---------- - q : 4 element sequence - w, i, j, k of quaternion - - Returns - ------- - n : scalar - quaternion norm - """ - return np.dot(q, q) - - -def isunit(q): - """Return True is this is very nearly a unit quaternion""" - return np.allclose(norm(q), 1) - - -def inverse(q): - """Return multiplicative inverse of quaternion `q` - - Parameters - ---------- - q : 4 element sequence - w, i, j, k of quaternion - - Returns - ------- - invq : array shape (4,) - w, i, j, k of quaternion inverse - """ - return conjugate(q) / norm(q) - - -def eye(): - """Return identity quaternion""" - return np.array([1.0, 0, 0, 0]) - - -def rotate_vector(v, q): - """Apply transformation in quaternion `q` to vector `v` - - Parameters - ---------- - v : 3 element sequence - 3 dimensional vector - q : 4 element sequence - w, i, j, k of quaternion - - Returns - ------- - vdash : array shape (3,) - `v` rotated by quaternion `q` - - Notes - ----- - See: - https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation#Describing_rotations_with_quaternions - - """ - varr = np.zeros((4,)) - varr[1:] = v - return mult(q, mult(varr, conjugate(q)))[1:] - - -def nearly_equivalent(q1, q2, rtol=1e-5, atol=1e-8): - """Returns True if `q1` and `q2` give near equivalent transforms - - `q1` may be nearly numerically equal to `q2`, or nearly equal to `q2` * -1 - (because a quaternion multiplied by -1 gives the same transform). - - Parameters - ---------- - q1 : 4 element sequence - w, x, y, z of first quaternion - q2 : 4 element sequence - w, x, y, z of second quaternion - - Returns - ------- - equiv : bool - True if `q1` and `q2` are nearly equivalent, False otherwise - - Examples - -------- - >>> q1 = [1, 0, 0, 0] - >>> nearly_equivalent(q1, [0, 1, 0, 0]) - False - >>> nearly_equivalent(q1, [1, 0, 0, 0]) - True - >>> nearly_equivalent(q1, [-1, 0, 0, 0]) - True - """ - q1 = np.array(q1) - q2 = np.array(q2) - if np.allclose(q1, q2, rtol, atol): - return True - return np.allclose(q1 * -1, q2, rtol, atol) - - -def angle_axis2quat(theta, vector, is_normalized=False): - """Quaternion for rotation of angle `theta` around `vector` - - Parameters - ---------- - theta : scalar - angle of rotation - vector : 3 element sequence - vector specifying axis for rotation. - is_normalized : bool, optional - True if vector is already normalized (has norm of 1). Default - False - - Returns - ------- - quat : 4 element sequence of symbols - quaternion giving specified rotation - - Examples - -------- - >>> q = angle_axis2quat(np.pi, [1, 0, 0]) - >>> np.allclose(q, [0, 1, 0, 0]) - True - - Notes - ----- - Formula from http://mathworld.wolfram.com/EulerParameters.html - """ - vector = np.array(vector) - if not is_normalized: - # Cannot divide in-place because input vector may be integer type, - # whereas output will be float type; this may raise an error in - # versions of numpy > 1.6.1 - vector = vector / math.sqrt(np.dot(vector, vector)) - t2 = theta / 2.0 - st2 = math.sin(t2) - return np.concatenate(([math.cos(t2)], vector * st2)) - - -def angle_axis2mat(theta, vector, is_normalized=False): - """Rotation matrix of angle `theta` around `vector` - - Parameters - ---------- - theta : scalar - angle of rotation - vector : 3 element sequence - vector specifying axis for rotation. - is_normalized : bool, optional - True if vector is already normalized (has norm of 1). Default - False - - Returns - ------- - mat : array shape (3,3) - rotation matrix for specified rotation - - Notes - ----- - From: https://en.wikipedia.org/wiki/Rotation_matrix#Axis_and_angle - """ - x, y, z = vector - if not is_normalized: - n = math.sqrt(x * x + y * y + z * z) - x = x / n - y = y / n - z = z / n - c, s = math.cos(theta), math.sin(theta) - C = 1 - c - xs, ys, zs = x * s, y * s, z * s - xC, yC, zC = x * C, y * C, z * C - xyC, yzC, zxC = x * yC, y * zC, z * xC - return np.array( - [ - [x * xC + c, xyC - zs, zxC + ys], - [xyC + zs, y * yC + c, yzC - xs], - [zxC - ys, yzC + xs, z * zC + c], - ] - ) - - -def quat2angle_axis(quat, identity_thresh=None): - """Convert quaternion to rotation of angle around axis - - Parameters - ---------- - quat : 4 element sequence - w, x, y, z forming quaternion - identity_thresh : None or scalar, optional - threshold below which the norm of the vector part of the - quaternion (x, y, z) is deemed to be 0, leading to the identity - rotation. None (the default) leads to a threshold estimated - based on the precision of the input. - - Returns - ------- - theta : scalar - angle of rotation - vector : array shape (3,) - axis around which rotation occurs - - Examples - -------- - >>> theta, vec = quat2angle_axis([0, 1, 0, 0]) - >>> np.allclose(theta, np.pi) - True - >>> vec - array([1., 0., 0.]) - - If this is an identity rotation, we return a zero angle and an - arbitrary vector - - >>> quat2angle_axis([1, 0, 0, 0]) - (0.0, array([1., 0., 0.])) - - Notes - ----- - A quaternion for which x, y, z are all equal to 0, is an identity - rotation. In this case we return a 0 angle and an arbitrary - vector, here [1, 0, 0] - """ - w, x, y, z = quat - vec = np.asarray([x, y, z]) - if identity_thresh is None: - try: - identity_thresh = np.finfo(vec.dtype).eps * 3 - except ValueError: # integer type - identity_thresh = FLOAT_EPS * 3 - n = math.sqrt(x * x + y * y + z * z) - if n < identity_thresh: - # if vec is nearly 0,0,0, this is an identity rotation - return 0.0, np.array([1.0, 0, 0]) - return 2 * math.acos(w), vec / n diff --git a/nibabel/rstutils.py b/nibabel/rstutils.py deleted file mode 100644 index 1ba63f4339..0000000000 --- a/nibabel/rstutils.py +++ /dev/null @@ -1,117 +0,0 @@ -"""ReStructured Text utilities - -* Make ReST table given array of values -""" - -import numpy as np - - -def rst_table( - cell_values, - row_names=None, - col_names=None, - title='', - val_fmt='{0:5.2f}', - format_chars=None, -): - """Return string for ReST table with entries `cell_values` - - Parameters - ---------- - cell_values : (R, C) array-like - At least 2D. Can be greater than 2D, in which case you should adapt - the `val_fmt` to deal with the multiple entries that will go in each - cell - row_names : None or (R,) length sequence, optional - Row names. If None, use ``row[0]`` etc. - col_names : None or (C,) length sequence, optional - Column names. If None, use ``col[0]`` etc. - title : str, optional - Title for table. Add as heading above table - val_fmt : str, optional - Format string using string ``format`` method mini-language. Converts - the result of ``cell_values[r, c]`` to a string to make the cell - contents. Default assumes a floating point value in a 2D `cell_values`. - format_chars : None or dict, optional - With keys 'down', 'along', 'thick_long', 'cross' and 'title_heading'. - Values are characters for: lines going down; lines going along; thick - lines along; two lines crossing; and the title overline / underline. - All missing values filled with rst defaults. - - Returns - ------- - table_str : str - Multiline string with ascii table, suitable for printing - """ - # formatting - if format_chars is None: - format_chars = {} - down = format_chars.pop('down', '|') - along = format_chars.pop('along', '-') - thick_long = format_chars.pop('thick_long', '=') - cross = format_chars.pop('cross', '+') - title_heading = format_chars.pop('title_heading', '*') - if len(format_chars) != 0: - raise ValueError(f'Unexpected ``format_char`` keys {", ".join(format_chars)}') - down_joiner = ' ' + down + ' ' - down_starter = down + ' ' - down_ender = ' ' + down - cross_joiner = along + cross + along - cross_starter = cross + along - cross_ender = along + cross - cross_thick_joiner = thick_long + cross + thick_long - cross_thick_starter = cross + thick_long - cross_thick_ender = thick_long + cross - # lengths of row names, column names and values - cell_values = np.asarray(cell_values) - R, C = cell_values.shape[:2] - if row_names is None: - row_names = [f'row[{r}]' for r in range(R)] - elif len(row_names) != R: - raise ValueError('len(row_names) != number of rows') - if col_names is None: - col_names = [f'col[{c}]' for c in range(C)] - elif len(col_names) != C: - raise ValueError('len(col_names) != number of columns') - row_len = max(len(name) for name in row_names) - col_len = max(len(name) for name in col_names) - # Compile row value strings, find longest, extend col length to match - row_str_list = [] - for row_no in range(R): - row_strs = [val_fmt.format(val) for val in cell_values[row_no]] - max_len = max(len(s) for s in row_strs) - if max_len > col_len: - col_len = max_len - row_str_list.append(row_strs) - row_name_fmt = '{0:<' + str(row_len) + '}' - row_names = [row_name_fmt.format(name) for name in row_names] - col_name_fmt = '{0:^' + str(col_len) + '}' - col_names = [col_name_fmt.format(name) for name in col_names] - col_headings = [' ' * row_len] + col_names - col_header = down_joiner.join(col_headings) - row_val_fmt = '{0:<' + str(col_len) + '}' - table_strs = [] - if title != '': - table_strs += [ - title_heading * len(title), - title, - title_heading * len(title), - '', - ] - along_headings = [along * len(h) for h in col_headings] - crossed_line = cross_starter + cross_joiner.join(along_headings) + cross_ender - thick_long_headings = [thick_long * len(h) for h in col_headings] - crossed_thick_line = ( - cross_thick_starter + cross_thick_joiner.join(thick_long_headings) + cross_thick_ender - ) - table_strs += [ - crossed_line, - down_starter + col_header + down_ender, - crossed_thick_line, - ] - for row_no, row_name in enumerate(row_names): - row_vals = [row_val_fmt.format(row_str) for row_str in row_str_list[row_no]] - row_line = down_starter + down_joiner.join([row_name] + row_vals) + down_ender - table_strs.append(row_line) - table_strs.append(crossed_line) - return '\n'.join(table_strs) diff --git a/nibabel/spaces.py b/nibabel/spaces.py deleted file mode 100644 index d06a39b0ed..0000000000 --- a/nibabel/spaces.py +++ /dev/null @@ -1,139 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Routines to work with spaces - -A space is defined by coordinate axes. - -A voxel space can be expressed by a shape implying an array, where the axes are -the axes of the array. - -A mapped voxel space (mapped voxels) is either: - -* an image, with attributes ``shape`` (the voxel space) and ``affine`` (the - mapping), or -* a length 2 sequence with the same information (shape, affine). -""" - -from itertools import product - -import numpy as np - -from .affines import apply_affine - - -def vox2out_vox(mapped_voxels, voxel_sizes=None): - """output-aligned shape, affine for input implied by `mapped_voxels` - - The input (voxel) space, and the affine mapping to output space, are given - in `mapped_voxels`. - - The output space is implied by the affine, we don't need to know what that - is, we just return something with the same (implied) output space. - - Our job is to work out another voxel space where the voxel array axes and - the output axes are aligned (top left 3 x 3 of affine is diagonal with all - positive entries) and which contains all the voxels of the implied input - image at their correct output space positions, once resampled into the - output voxel space. - - Parameters - ---------- - mapped_voxels : object or length 2 sequence - If object, has attributes ``shape`` giving input voxel shape, and - ``affine`` giving mapping of input voxels to output space. If length 2 - sequence, elements are (shape, affine) with same meaning as above. The - affine is a (4, 4) array-like. - voxel_sizes : None or sequence - Gives the diagonal entries of `output_affine` (except the trailing 1 - for the homogeneous coordinates) (``output_affine == np.diag(voxel_sizes - + [1])``). If None, return identity `output_affine`. - - Returns - ------- - output_shape : sequence - Shape of output image that has voxel axes aligned to original image - output space axes, and encloses all the voxel data from the original - image implied by input shape. - output_affine : (4, 4) array - Affine of output image that has voxel axes aligned to the output axes - implied by input affine. Top-left 3 x 3 part of affine is diagonal with - all positive entries. The entries come from `voxel_sizes` if - specified, or are all 1. If the image is < 3D, then the missing - dimensions will have a 1 in the matching diagonal. - """ - try: - in_shape, in_affine = mapped_voxels.shape, mapped_voxels.affine - except AttributeError: - in_shape, in_affine = mapped_voxels - n_axes = len(in_shape) - if n_axes > 3: - raise ValueError('This function can only deal with 3D images') - if n_axes < 3: - in_shape += (1,) * (3 - n_axes) - out_vox = np.ones((3,)) - if voxel_sizes is not None: - if not len(voxel_sizes) == n_axes: - raise ValueError('voxel sizes length should match shape') - if not np.all(np.array(voxel_sizes) > 0): - raise ValueError('voxel sizes should all be positive') - out_vox[:n_axes] = voxel_sizes - in_mn_mx = zip([0, 0, 0], np.array(in_shape) - 1) - in_corners = list(product(*in_mn_mx)) - out_corners = apply_affine(in_affine, in_corners) - out_mn = out_corners.min(axis=0) - out_mx = out_corners.max(axis=0) - out_shape = np.ceil((out_mx - out_mn) / out_vox) + 1 - out_affine = np.diag(list(out_vox) + [1]) - out_affine[:3, 3] = out_mn - return tuple(int(i) for i in out_shape[:n_axes]), out_affine - - -def slice2volume(index, axis, shape=None): - """Affine expressing selection of a single slice from 3D volume - - Imagine we have taken a slice from an image data array, ``s = data[:, :, - index]``. This function returns the affine to map the array coordinates of - ``s`` to the array coordinates of ``data``. - - This can be useful for resampling a single slice from a volume. For - example, to resample slice ``k`` in the space of ``img1`` from the matching - spatial voxel values in ``img2``, you might do something like:: - - slice_shape = img1.shape[:2] - slice_aff = slice2volume(k, 2) - whole_aff = np.linalg.inv(img2.affine).dot(img1.affine.dot(slice_aff)) - - and then use ``whole_aff`` in ``scipy.ndimage.affine_transform``: - - rzs, trans = to_matvec(whole_aff) - data = img2.get_fdata() - new_slice = scipy.ndimage.affine_transform(data, rzs, trans, slice_shape) - - Parameters - ---------- - index : int - index of selected slice - axis : {0, 1, 2} - axis to which `index` applies - - Returns - ------- - slice_aff : shape (4, 3) affine - Affine relating input coordinates in a slice to output coordinates in - the embedded volume - """ - if index < 0: - raise ValueError('Cannot handle negative index') - if not 0 <= axis <= 2: - raise ValueError('Axis should be between 0 and 2') - axes = list(range(4)) - axes.remove(axis) - slice_aff = np.eye(4)[:, axes] - slice_aff[axis, -1] = index - return slice_aff diff --git a/nibabel/spatialimages.py b/nibabel/spatialimages.py deleted file mode 100644 index bce17e7341..0000000000 --- a/nibabel/spatialimages.py +++ /dev/null @@ -1,690 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""A simple spatial image class - -The image class maintains the association between a 3D (or greater) -array, and an affine transform that maps voxel coordinates to some world space. -It also has a ``header`` - some standard set of meta-data that is specific to -the image format, and ``extra`` - a dictionary container for any other -metadata. - -It has attributes: - - * extra - -methods: - - * .get_fdata() - * .to_filename(fname) - writes data to filename(s) derived from - ``fname``, where the derivation may differ between formats. - * to_file_map() - save image to files with which the image is already - associated. - -properties: - - * shape - * affine - * header - * dataobj - -classmethods: - - * from_filename(fname) - make instance by loading from filename - * from_file_map(fmap) - make instance from file map - * instance_to_filename(img, fname) - save ``img`` instance to - filename ``fname``. - -You cannot slice an image, and trying to slice an image generates an -informative TypeError. - -There are several ways of writing data. -======================================= - -There is the usual way, which is the default:: - - img.to_filename(fname) - -and that is, to take the data encapsulated by the image and cast it to -the datatype the header expects, setting any available header scaling -into the header to help the data match. - -You can load the data into an image from file with:: - - img.from_filename(fname) - -The image stores its associated files in its ``file_map`` attribute. In order -to just save an image, for which you know there is an associated filename, or -other storage, you can do:: - - img.to_file_map() - -You can get the data out again with:: - - img.get_fdata() - -Less commonly, for some image types that support it, you might want to -fetch out the unscaled array via the object containing the data:: - - unscaled_data = img.dataoobj.get_unscaled() - -Analyze-type images (including nifti) support this, but others may not -(MINC, for example). - -Sometimes you might to avoid any loss of precision by making the -data type the same as the input:: - - hdr = img.header - hdr.set_data_dtype(data.dtype) - img.to_filename(fname) - -Files interface -=============== - -The image has an attribute ``file_map``. This is a mapping, that has keys -corresponding to the file types that an image needs for storage. For -example, the Analyze data format needs an ``image`` and a ``header`` -file type for storage: - - >>> import numpy as np - >>> import nibabel as nib - >>> data = np.arange(24, dtype='f4').reshape((2,3,4)) - >>> img = nib.AnalyzeImage(data, np.eye(4)) - >>> sorted(img.file_map) - ['header', 'image'] - -The values of ``file_map`` are not in fact files but objects with -attributes ``filename``, ``fileobj`` and ``pos``. - -The reason for this interface, is that the contents of files has to -contain enough information so that an existing image instance can save -itself back to the files pointed to in ``file_map``. When a file holder -holds active file-like objects, then these may be affected by the -initial file read; in this case, the contains file-like objects need to -carry the position at which a write (with ``to_file_map``) should place the -data. The ``file_map`` contents should therefore be such, that this will -work: - - >>> # write an image to files - >>> from io import BytesIO - >>> import nibabel as nib - >>> file_map = nib.AnalyzeImage.make_file_map() - >>> file_map['image'].fileobj = BytesIO() - >>> file_map['header'].fileobj = BytesIO() - >>> img = nib.AnalyzeImage(data, np.eye(4)) - >>> img.file_map = file_map - >>> img.to_file_map() - >>> # read it back again from the written files - >>> img2 = nib.AnalyzeImage.from_file_map(file_map) - >>> np.all(img2.get_fdata(dtype=np.float32) == data) - True - >>> # write, read it again - >>> img2.to_file_map() - >>> img3 = nib.AnalyzeImage.from_file_map(file_map) - >>> np.all(img3.get_fdata(dtype=np.float32) == data) - True -""" - -from __future__ import annotations - -import typing as ty -from functools import cache -from typing import Literal - -import numpy as np - -from ._typing import TypeVar -from .casting import sctypes_aliases -from .dataobj_images import DataobjImage -from .filebasedimages import FileBasedHeader, FileBasedImage -from .fileslice import canonical_slicers -from .orientations import apply_orientation, inv_ornt_aff -from .viewers import OrthoSlicer3D -from .volumeutils import shape_zoom_affine - -if ty.TYPE_CHECKING: - import io - from collections.abc import Sequence - - import numpy.typing as npt - - from ._typing import Self - from .arrayproxy import ArrayLike - from .fileholders import FileMap - -SpatialImgT = TypeVar('SpatialImgT', bound='SpatialImage') - - -class HasDtype(ty.Protocol): - def get_data_dtype(self) -> np.dtype: ... - def set_data_dtype(self, dtype: npt.DTypeLike) -> None: ... - - -@ty.runtime_checkable -class SpatialProtocol(ty.Protocol): - def get_data_dtype(self) -> np.dtype: ... - def get_data_shape(self) -> tuple[int, ...]: ... - def get_zooms(self) -> tuple[float, ...]: ... - - -class HeaderDataError(Exception): - """Class to indicate error in getting or setting header data""" - - -class HeaderTypeError(Exception): - """Class to indicate error in parameters into header functions""" - - -class SpatialHeader(FileBasedHeader, SpatialProtocol): - """Template class to implement header protocol""" - - default_x_flip: bool = True - data_layout: Literal['F', 'C'] = 'F' - - _dtype: np.dtype - _shape: tuple[int, ...] - _zooms: tuple[float, ...] - - def __init__( - self, - data_dtype: npt.DTypeLike = np.float32, - shape: Sequence[int] = (0,), - zooms: Sequence[float] | None = None, - ): - self.set_data_dtype(data_dtype) - self._zooms = () - self.set_data_shape(shape) - if zooms is not None: - self.set_zooms(zooms) - - @classmethod - def from_header( - klass, - header: SpatialProtocol | FileBasedHeader | ty.Mapping | None = None, - ) -> Self: - if header is None: - return klass() - # I can't do isinstance here because it is not necessarily true - # that a subclass has exactly the same interface as its parent - # - for example Nifti1Images inherit from Analyze, but have - # different field names - if type(header) == klass: - return header.copy() - if isinstance(header, SpatialProtocol): - return klass(header.get_data_dtype(), header.get_data_shape(), header.get_zooms()) - return super().from_header(header) - - def __eq__(self, other: object) -> bool: - if isinstance(other, SpatialHeader): - return (self.get_data_dtype(), self.get_data_shape(), self.get_zooms()) == ( - other.get_data_dtype(), - other.get_data_shape(), - other.get_zooms(), - ) - return NotImplemented - - def copy(self) -> Self: - """Copy object to independent representation - - The copy should not be affected by any changes to the original - object. - """ - return self.__class__(self._dtype, self._shape, self._zooms) - - def get_data_dtype(self) -> np.dtype: - return self._dtype - - def set_data_dtype(self, dtype: npt.DTypeLike) -> None: - self._dtype = np.dtype(dtype) - - def get_data_shape(self) -> tuple[int, ...]: - return self._shape - - def set_data_shape(self, shape: Sequence[int]) -> None: - ndim = len(shape) - if ndim == 0: - self._shape = (0,) - self._zooms = (1.0,) - return - self._shape = tuple(int(s) for s in shape) - # set any unset zooms to 1.0 - nzs = min(len(self._zooms), ndim) - self._zooms = self._zooms[:nzs] + (1.0,) * (ndim - nzs) - - def get_zooms(self) -> tuple[float, ...]: - return self._zooms - - def set_zooms(self, zooms: Sequence[float]) -> None: - zooms = tuple(float(z) for z in zooms) - shape = self.get_data_shape() - ndim = len(shape) - if len(zooms) != ndim: - raise HeaderDataError(f'Expecting {ndim} zoom values for ndim {ndim}') - if any(z < 0 for z in zooms): - raise HeaderDataError('zooms must be positive') - self._zooms = zooms - - def get_base_affine(self) -> np.ndarray: - shape = self.get_data_shape() - zooms = self.get_zooms() - return shape_zoom_affine(shape, zooms, self.default_x_flip) - - get_best_affine = get_base_affine - - def data_to_fileobj(self, data: npt.ArrayLike, fileobj: io.IOBase, rescale: bool = True): - """Write array data `data` as binary to `fileobj` - - Parameters - ---------- - data : array-like - data to write - fileobj : file-like object - file-like object implementing 'write' - rescale : {True, False}, optional - Whether to try and rescale data to match output dtype specified by - header. For this minimal header, `rescale` has no effect - """ - data = np.asarray(data) - dtype = self.get_data_dtype() - fileobj.write(data.astype(dtype).tobytes(order=self.data_layout)) - - def data_from_fileobj(self, fileobj: io.IOBase) -> np.ndarray: - """Read binary image data from `fileobj`""" - dtype = self.get_data_dtype() - shape = self.get_data_shape() - data_size = int(np.prod(shape) * dtype.itemsize) - data_bytes = fileobj.read(data_size) - return np.ndarray(shape, dtype, data_bytes, order=self.data_layout) - - -@cache -def _supported_np_types(klass: type[HasDtype]) -> set[type[np.generic]]: - """Numpy data types that instances of ``klass`` support - - Parameters - ---------- - klass : class - Class implementing `get_data_dtype` and `set_data_dtype` methods. The object - should raise ``HeaderDataError`` for setting unsupported dtypes. The - object will likely be a header or a :class:`SpatialImage` - - Returns - ------- - np_types : set - set of numpy types that ``klass`` instances support - """ - try: - obj = klass() - except TypeError as e: - if hasattr(klass, 'header_class'): - obj = klass.header_class() - else: - raise e - supported = set() - for np_type in sctypes_aliases: - try: - obj.set_data_dtype(np_type) - except HeaderDataError: - continue - # Did set work? - if np.dtype(obj.get_data_dtype()) == np.dtype(np_type): - supported.add(np_type) - return supported - - -def supported_np_types(obj: HasDtype) -> set[type[np.generic]]: - """Numpy data types that instance `obj` supports - - Parameters - ---------- - obj : object - Object implementing `get_data_dtype` and `set_data_dtype`. The object - should raise ``HeaderDataError`` for setting unsupported dtypes. The - object will likely be a header or a :class:`SpatialImage` - - Returns - ------- - np_types : set - set of numpy types that `obj` supports - """ - return _supported_np_types(obj.__class__) - - -class ImageDataError(Exception): - pass - - -class SpatialFirstSlicer(ty.Generic[SpatialImgT]): - """Slicing interface that returns a new image with an updated affine - - Checks that an image's first three axes are spatial - """ - - img: SpatialImgT - - def __init__(self, img: SpatialImgT): - # Local import to avoid circular import on module load - from .imageclasses import spatial_axes_first - - if not spatial_axes_first(img): - raise ValueError( - 'Cannot predict position of spatial axes for image type {img.__class__.__name__}' - ) - self.img = img - - def __getitem__(self, slicer: object) -> SpatialImgT: - try: - slicer = self.check_slicing(slicer) - except ValueError as err: - raise IndexError(*err.args) - - dataobj = self.img.dataobj[slicer] - if any(dim == 0 for dim in dataobj.shape): - raise IndexError('Empty slice requested') - - affine = self.slice_affine(slicer) - return self.img.__class__(dataobj.copy(), affine, self.img.header) - - def check_slicing( - self, - slicer: object, - return_spatial: bool = False, - ) -> tuple[slice | int | None, ...]: - """Canonicalize slicers and check for scalar indices in spatial dims - - Parameters - ---------- - slicer : object - something that can be used to slice an array as in - ``arr[sliceobj]`` - return_spatial : bool - return only slices along spatial dimensions (x, y, z) - - Returns - ------- - slicer : object - Validated slicer object that will slice image's `dataobj` - without collapsing spatial dimensions - """ - canonical = canonical_slicers(slicer, self.img.shape) - # We can get away with this because we've checked the image's - # first three axes are spatial. - # More general slicers will need to be smarter, here. - spatial_slices = canonical[:3] - for subslicer in spatial_slices: - if subslicer is None: - raise IndexError('New axis not permitted in spatial dimensions') - elif isinstance(subslicer, int): - raise IndexError( - 'Scalar indices disallowed in spatial dimensions; Use `[x]` or `x:x+1`.' - ) - return spatial_slices if return_spatial else canonical - - def slice_affine(self, slicer: object) -> np.ndarray: - """Retrieve affine for current image, if sliced by a given index - - Applies scaling if down-sampling is applied, and adjusts the intercept - to account for any cropping. - - Parameters - ---------- - slicer : object - something that can be used to slice an array as in - ``arr[sliceobj]`` - - Returns - ------- - affine : (4,4) ndarray - Affine with updated scale and intercept - """ - slicer = self.check_slicing(slicer, return_spatial=True) - - # Transform: - # sx 0 0 tx - # 0 sy 0 ty - # 0 0 sz tz - # 0 0 0 1 - transform = np.eye(4, dtype=int) - - for i, subslicer in enumerate(slicer): - if isinstance(subslicer, slice): - if subslicer.step == 0: - raise ValueError('slice step cannot be 0') - transform[i, i] = subslicer.step if subslicer.step is not None else 1 - transform[i, 3] = subslicer.start or 0 - # If slicer is None, nothing to do - - return self.img.affine.dot(transform) - - -class SpatialImage(DataobjImage): - """Template class for volumetric (3D/4D) images""" - - header_class: type[SpatialHeader] = SpatialHeader - ImageSlicer: type[SpatialFirstSlicer] = SpatialFirstSlicer - - _header: SpatialHeader - header: SpatialHeader - - def __init__( - self, - dataobj: ArrayLike, - affine: np.ndarray | None, - header: FileBasedHeader | ty.Mapping | None = None, - extra: ty.Mapping | None = None, - file_map: FileMap | None = None, - ): - """Initialize image - - The image is a combination of (array-like, affine matrix, header), with - optional metadata in `extra`, and filename / file-like objects - contained in the `file_map` mapping. - - Parameters - ---------- - dataobj : object - Object containing image data. It should be some object that returns an - array from ``np.asanyarray``. It should have a ``shape`` attribute - or property - affine : None or (4,4) array-like - homogeneous affine giving relationship between voxel coordinates and - world coordinates. Affine can also be None. In this case, - ``obj.affine`` also returns None, and the affine as written to disk - will depend on the file format. - header : None or mapping or header instance, optional - metadata for this image format - extra : None or mapping, optional - metadata to associate with image that cannot be stored in the - metadata of this image type - file_map : mapping, optional - mapping giving file information for this image format - """ - super().__init__(dataobj, header=header, extra=extra, file_map=file_map) - if affine is not None: - # Check that affine is array-like 4,4. Maybe this is too strict at - # this abstract level, but so far I think all image formats we know - # do need 4,4. - # Copy affine to isolate from environment. Specify float type to - # avoid surprising integer rounding when setting values into affine - affine = np.array(affine, dtype=np.float64, copy=True) - if not affine.shape == (4, 4): - raise ValueError('Affine should be shape 4,4') - self._affine = affine - - # if header not specified, get data type from input array - if header is None: - if hasattr(dataobj, 'dtype'): - self._header.set_data_dtype(dataobj.dtype) - # make header correspond with image and affine - self.update_header() - self._data_cache = None - - @property - def affine(self): - return self._affine - - def update_header(self) -> None: - """Harmonize header with image data and affine - - >>> data = np.zeros((2,3,4)) - >>> affine = np.diag([1.0,2.0,3.0,1.0]) - >>> img = SpatialImage(data, affine) - >>> img.shape == (2, 3, 4) - True - >>> img.update_header() - >>> img.header.get_data_shape() == (2, 3, 4) - True - >>> img.header.get_zooms() - (1.0, 2.0, 3.0) - """ - hdr = self._header - shape = self._dataobj.shape - # We need to update the header if the data shape has changed. It's a - # bit difficult to change the data shape using the standard API, but - # maybe it happened - if hdr.get_data_shape() != shape: - hdr.set_data_shape(shape) - # If the affine is not None, and it is different from the main affine - # in the header, update the header - if self._affine is None: - return - if np.allclose(self._affine, hdr.get_best_affine()): - return - self._affine2header() - - def _affine2header(self) -> None: - """Unconditionally set affine into the header""" - assert self._affine is not None - RZS = self._affine[:3, :3] - vox = np.sqrt(np.sum(RZS * RZS, axis=0)) - hdr = self._header - zooms = list(hdr.get_zooms()) - n_to_set = min(len(zooms), 3) - zooms[:n_to_set] = vox[:n_to_set] - hdr.set_zooms(zooms) - - def __str__(self) -> str: - shape = self.shape - affine = self.affine - return f""" -{self.__class__} -data shape {shape} -affine: -{affine} -metadata: -{self._header} -""" - - def get_data_dtype(self) -> np.dtype: - return self._header.get_data_dtype() - - def set_data_dtype(self, dtype: npt.DTypeLike) -> None: - self._header.set_data_dtype(dtype) - - @classmethod - def from_image(klass, img: SpatialImage | FileBasedImage) -> Self: - """Class method to create new instance of own class from `img` - - Parameters - ---------- - img : ``spatialimage`` instance - In fact, an object with the API of ``spatialimage`` - - specifically ``dataobj``, ``affine``, ``header`` and ``extra``. - - Returns - ------- - cimg : ``spatialimage`` instance - Image, of our own class - """ - if isinstance(img, SpatialImage): - return klass( - img.dataobj, - img.affine, - klass.header_class.from_header(img.header), - extra=img.extra.copy(), - ) - return super().from_image(img) - - @property - def slicer(self) -> SpatialFirstSlicer[Self]: - """Slicer object that returns cropped and subsampled images - - The image is resliced in the current orientation; no rotation or - resampling is performed, and no attempt is made to filter the image - to avoid `aliasing`_. - - The affine matrix is updated with the new intercept (and scales, if - down-sampling is used), so that all values are found at the same RAS - locations. - - Slicing may include non-spatial dimensions. - However, this method does not currently adjust the repetition time in - the image header. - - .. _aliasing: https://en.wikipedia.org/wiki/Aliasing - """ - return self.ImageSlicer(self) - - def __getitem__(self, idx: object) -> None: - """No slicing or dictionary interface for images - - Use the slicer attribute to perform cropping and subsampling at your - own risk. - """ - raise TypeError( - 'Cannot slice image objects; consider using `img.slicer[slice]` ' - 'to generate a sliced image (see documentation for caveats) or ' - 'slicing image array data with `img.dataobj[slice]` or ' - '`img.get_fdata()[slice]`' - ) - - def orthoview(self) -> OrthoSlicer3D: - """Plot the image using OrthoSlicer3D - - Returns - ------- - viewer : instance of OrthoSlicer3D - The viewer. - - Notes - ----- - This requires matplotlib. If a non-interactive backend is used, - consider using viewer.show() (equivalently plt.show()) to show - the figure. - """ - return OrthoSlicer3D(self.dataobj, self.affine, title=self.get_filename()) - - def as_reoriented(self, ornt: Sequence[Sequence[int]]) -> Self: - """Apply an orientation change and return a new image - - If ornt is identity transform, return the original image, unchanged - - Parameters - ---------- - ornt : (n,2) orientation array - orientation transform. ``ornt[N,1]` is flip of axis N of the - array implied by `shape`, where 1 means no flip and -1 means - flip. For example, if ``N==0`` and ``ornt[0,1] == -1``, and - there's an array ``arr`` of shape `shape`, the flip would - correspond to the effect of ``np.flipud(arr)``. ``ornt[:,0]`` is - the transpose that needs to be done to the implied array, as in - ``arr.transpose(ornt[:,0])`` - - Notes - ----- - Subclasses should override this if they have additional requirements - when re-orienting an image. - """ - - if np.array_equal(ornt, [[0, 1], [1, 1], [2, 1]]): - return self - - t_arr = apply_orientation(np.asanyarray(self.dataobj), ornt) - new_aff = self.affine.dot(inv_ornt_aff(ornt, self.shape)) - - return self.__class__(t_arr, new_aff, self.header) diff --git a/nibabel/spm2analyze.py b/nibabel/spm2analyze.py deleted file mode 100644 index 9c4c544cf5..0000000000 --- a/nibabel/spm2analyze.py +++ /dev/null @@ -1,136 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Read / write access to SPM2 version of analyze image format""" - -import numpy as np - -from . import spm99analyze as spm99 # module import - -image_dimension_dtd = spm99.image_dimension_dtd.copy() -image_dimension_dtd[image_dimension_dtd.index(('funused2', 'f4'))] = ('scl_inter', 'f4') - -# Full header numpy dtype combined across sub-fields -header_dtype = np.dtype(spm99.header_key_dtd + image_dimension_dtd + spm99.data_history_dtd) - - -class Spm2AnalyzeHeader(spm99.Spm99AnalyzeHeader): - """Class for SPM2 variant of basic Analyze header - - SPM2 variant adds the following to basic Analyze format: - - * voxel origin; - * slope scaling of data; - * reading - but not writing - intercept of data. - """ - - # Copies of module level definitions - template_dtype = header_dtype - - def get_slope_inter(self): - """Get data scaling (slope) and intercept from header data - - Uses the algorithm from SPM2 spm_vol_ana.m by John Ashburner - - Parameters - ---------- - self : header - Mapping with fields: - * scl_slope - slope - * scl_inter - possible intercept (SPM2 use - shared by nifti) - * glmax - the (recorded) maximum value in the data (unscaled) - * glmin - recorded minimum unscaled value - * cal_max - the calibrated (scaled) maximum value in the dataset - * cal_min - ditto minimum value - - Returns - ------- - scl_slope : None or float - slope. None if there is no valid scaling from these fields - scl_inter : None or float - intercept. Also None if there is no valid slope, intercept - - Examples - -------- - >>> fields = {'scl_slope': 1, 'scl_inter': 0, 'glmax': 0, 'glmin': 0, - ... 'cal_max': 0, 'cal_min': 0} - >>> hdr = Spm2AnalyzeHeader() - >>> for key, value in fields.items(): - ... hdr[key] = value - >>> hdr.get_slope_inter() - (1.0, 0.0) - >>> hdr['scl_inter'] = 0.5 - >>> hdr.get_slope_inter() - (1.0, 0.5) - >>> hdr['scl_inter'] = np.nan - >>> hdr.get_slope_inter() - (1.0, 0.0) - - If 'scl_slope' is 0, nan or inf, cannot use 'scl_slope'. - Without valid information in the gl / cal fields, we cannot get - scaling, and return None - - >>> hdr['scl_slope'] = 0 - >>> hdr.get_slope_inter() - (None, None) - >>> hdr['scl_slope'] = np.nan - >>> hdr.get_slope_inter() - (None, None) - - Valid information in the gl AND cal fields are needed - - >>> hdr['cal_max'] = 0.8 - >>> hdr['cal_min'] = 0.2 - >>> hdr.get_slope_inter() - (None, None) - >>> hdr['glmax'] = 110 - >>> hdr['glmin'] = 10 - >>> np.allclose(hdr.get_slope_inter(), [0.6/100, 0.2-0.6/100*10]) - True - """ - # get scaling factor from 'scl_slope' (funused1) - slope = float(self['scl_slope']) - if np.isfinite(slope) and slope: - # try to get offset from scl_inter - inter = float(self['scl_inter']) - if not np.isfinite(inter): - inter = 0.0 - return slope, inter - # no non-zero and finite scaling, try gl/cal fields - unscaled_range = self['glmax'] - self['glmin'] - scaled_range = self['cal_max'] - self['cal_min'] - if unscaled_range and scaled_range: - slope = float(scaled_range) / unscaled_range - inter = self['cal_min'] - slope * self['glmin'] - return slope, inter - return None, None - - @classmethod - def may_contain_header(klass, binaryblock): - if len(binaryblock) < klass.sizeof_hdr: - return False - - hdr_struct = np.ndarray( - shape=(), dtype=header_dtype, buffer=binaryblock[: klass.sizeof_hdr] - ) - bs_hdr_struct = hdr_struct.byteswap() - return binaryblock[344:348] not in (b'ni1\x00', b'n+1\x00') and 348 in ( - hdr_struct['sizeof_hdr'], - bs_hdr_struct['sizeof_hdr'], - ) - - -class Spm2AnalyzeImage(spm99.Spm99AnalyzeImage): - """Class for SPM2 variant of basic Analyze image""" - - header_class = Spm2AnalyzeHeader - header: Spm2AnalyzeHeader - - -load = Spm2AnalyzeImage.from_filename -save = Spm2AnalyzeImage.instance_to_filename diff --git a/nibabel/spm99analyze.py b/nibabel/spm99analyze.py deleted file mode 100644 index cdedf223e0..0000000000 --- a/nibabel/spm99analyze.py +++ /dev/null @@ -1,337 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Read / write access to SPM99 version of analyze image format""" - -import warnings -from io import BytesIO - -import numpy as np - -from . import analyze # module import -from .batteryrunners import Report -from .optpkg import optional_package -from .spatialimages import HeaderDataError, HeaderTypeError - -have_scipy = optional_package('scipy')[1] - -""" Support subtle variations of SPM version of Analyze """ -header_key_dtd = analyze.header_key_dtd -# funused1 in dime subfield is scalefactor -image_dimension_dtd = analyze.image_dimension_dtd.copy() -image_dimension_dtd[image_dimension_dtd.index(('funused1', 'f4'))] = ('scl_slope', 'f4') -# originator text field used as image origin (translations) -data_history_dtd = analyze.data_history_dtd.copy() -data_history_dtd[data_history_dtd.index(('originator', 'S10'))] = ('origin', 'i2', (5,)) - -# Full header numpy dtype combined across sub-fields -header_dtype = np.dtype(header_key_dtd + image_dimension_dtd + data_history_dtd) - - -class SpmAnalyzeHeader(analyze.AnalyzeHeader): - """Basic scaling Spm Analyze header""" - - # Copies of module level definitions - template_dtype = header_dtype - - # data scaling capabilities - has_data_slope = True - has_data_intercept = False - - @classmethod - def default_structarr(klass, endianness=None): - """Create empty header binary block with given endianness""" - hdr_data = super().default_structarr(endianness) - hdr_data['scl_slope'] = 1 - return hdr_data - - def get_slope_inter(self): - """Get scalefactor and intercept - - If scalefactor is 0.0 return None to indicate no scalefactor. - Intercept is always None because SPM99 analyze cannot store intercepts. - """ - slope = self._structarr['scl_slope'] - # Return invalid slopes as None - if np.isnan(slope) or slope in (0, -np.inf, np.inf): - return None, None - return slope, None - - def set_slope_inter(self, slope, inter=None): - """Set slope and / or intercept into header - - Set slope and intercept for image data, such that, if the image - data is ``arr``, then the scaled image data will be ``(arr * - slope) + inter`` - - The SPM Analyze header can't save an intercept value, and we raise an - error unless `inter` is None, NaN or 0 - - Parameters - ---------- - slope : None or float - If None, implies `slope` of NaN. NaN is a signal to the image - writing routines to rescale on save. 0, Inf, -Inf are invalid and - cause a HeaderDataError - inter : None or float, optional - intercept. Must be None, NaN or 0, because SPM99 cannot store - intercepts. - """ - if slope is None: - slope = np.nan - if slope in (0, -np.inf, np.inf): - raise HeaderDataError('Slope cannot be 0 or infinite') - self._structarr['scl_slope'] = slope - if inter in (None, 0) or np.isnan(inter): - return - raise HeaderTypeError('Cannot set non-zero intercept for SPM headers') - - -class Spm99AnalyzeHeader(SpmAnalyzeHeader): - """Class for SPM99 variant of basic Analyze header - - SPM99 variant adds the following to basic Analyze format: - - * voxel origin; - * slope scaling of data. - """ - - def get_origin_affine(self): - """Get affine from header, using SPM origin field if sensible - - The default translations are got from the ``origin`` - field, if set, or from the center of the image otherwise. - - Examples - -------- - >>> hdr = Spm99AnalyzeHeader() - >>> hdr.set_data_shape((3, 5, 7)) - >>> hdr.set_zooms((3, 2, 1)) - >>> hdr.default_x_flip - True - >>> hdr.get_origin_affine() # from center of image - array([[-3., 0., 0., 3.], - [ 0., 2., 0., -4.], - [ 0., 0., 1., -3.], - [ 0., 0., 0., 1.]]) - >>> hdr['origin'][:3] = [3,4,5] - >>> hdr.get_origin_affine() # using origin - array([[-3., 0., 0., 6.], - [ 0., 2., 0., -6.], - [ 0., 0., 1., -4.], - [ 0., 0., 0., 1.]]) - >>> hdr['origin'] = 0 # unset origin - >>> hdr.set_data_shape((3, 5, 7)) - >>> hdr.get_origin_affine() # from center of image - array([[-3., 0., 0., 3.], - [ 0., 2., 0., -4.], - [ 0., 0., 1., -3.], - [ 0., 0., 0., 1.]]) - """ - hdr = self._structarr - zooms = hdr['pixdim'][1:4].copy() - if self.default_x_flip: - zooms[0] *= -1 - # Get translations from origin, or center of image - # Remember that the origin is for matlab (1-based indexing) - origin = hdr['origin'][:3] - dims = hdr['dim'][1:4] - if np.any(origin) and np.all(origin > -dims) and np.all(origin < dims * 2): - origin = origin - 1 - else: - origin = (dims - 1) / 2.0 - aff = np.eye(4) - aff[:3, :3] = np.diag(zooms) - aff[:3, -1] = -origin * zooms - return aff - - get_best_affine = get_origin_affine - - def set_origin_from_affine(self, affine): - """Set SPM origin to header from affine matrix. - - The ``origin`` field was read but not written by SPM99 and 2. It was - used for storing a central voxel coordinate, that could be used in - aligning the image to some standard position - a proxy for a full - translation vector that was usually stored in a separate matlab .mat - file. - - Nifti uses the space occupied by the SPM ``origin`` field for important - other information (the transform codes), so writing the origin will - make the header a confusing Nifti file. If you work with both Analyze - and Nifti, you should probably avoid doing this. - - Parameters - ---------- - affine : array-like, shape (4,4) - Affine matrix to set - - Returns - ------- - None - - Examples - -------- - >>> hdr = Spm99AnalyzeHeader() - >>> hdr.set_data_shape((3, 5, 7)) - >>> hdr.set_zooms((3,2,1)) - >>> hdr.get_origin_affine() - array([[-3., 0., 0., 3.], - [ 0., 2., 0., -4.], - [ 0., 0., 1., -3.], - [ 0., 0., 0., 1.]]) - >>> affine = np.diag([3,2,1,1]) - >>> affine[:3,3] = [-6, -6, -4] - >>> hdr.set_origin_from_affine(affine) - >>> np.all(hdr['origin'][:3] == [3,4,5]) - True - >>> hdr.get_origin_affine() - array([[-3., 0., 0., 6.], - [ 0., 2., 0., -6.], - [ 0., 0., 1., -4.], - [ 0., 0., 0., 1.]]) - """ - if affine.shape != (4, 4): - raise ValueError('Need 4x4 affine to set') - hdr = self._structarr - RZS = affine[:3, :3] - Z = np.sqrt(np.sum(RZS * RZS, axis=0)) - T = affine[:3, 3] - # Remember that the origin is for matlab (1-based) indexing - hdr['origin'][:3] = -T / Z + 1 - - @classmethod - def _get_checks(klass): - checks = super()._get_checks() - return checks + (klass._chk_origin,) - - @staticmethod - def _chk_origin(hdr, fix=False): - rep = Report(HeaderDataError) - origin = hdr['origin'][0:3] - dims = hdr['dim'][1:4] - if not np.any(origin) or (np.all(origin > -dims) and np.all(origin < dims * 2)): - return hdr, rep - rep.problem_level = 20 - rep.problem_msg = 'very large origin values relative to dims' - if fix: - rep.fix_msg = 'leaving as set, ignoring for affine' - return hdr, rep - - -class Spm99AnalyzeImage(analyze.AnalyzeImage): - """Class for SPM99 variant of basic Analyze image""" - - header_class = Spm99AnalyzeHeader - header: Spm99AnalyzeHeader - files_types = (('image', '.img'), ('header', '.hdr'), ('mat', '.mat')) - has_affine = True - makeable = True - rw = have_scipy - - @classmethod - def from_file_map(klass, file_map, *, mmap=True, keep_file_open=None): - """Class method to create image from mapping in ``file_map`` - - Parameters - ---------- - file_map : dict - Mapping with (key, value) pairs of (``file_type``, FileHolder - instance giving file-likes for each file needed for this image - type. - mmap : {True, False, 'c', 'r'}, optional, keyword only - `mmap` controls the use of numpy memory mapping for reading image - array data. If False, do not try numpy ``memmap`` for data array. - If one of {'c', 'r'}, try numpy memmap with ``mode=mmap``. A - `mmap` value of True gives the same behavior as ``mmap='c'``. If - image data file cannot be memory-mapped, ignore `mmap` value and - read array from file. - keep_file_open : { None, True, False }, optional, keyword only - `keep_file_open` controls whether a new file handle is created - every time the image is accessed, or a single file handle is - created and used for the lifetime of this ``ArrayProxy``. If - ``True``, a single file handle is created and used. If ``False``, - a new file handle is created every time the image is accessed. - If ``file_map`` refers to an open file handle, this setting has no - effect. The default value (``None``) will result in the value of - ``nibabel.arrayproxy.KEEP_FILE_OPEN_DEFAULT`` being used. - - Returns - ------- - img : Spm99AnalyzeImage instance - - """ - ret = super().from_file_map(file_map, mmap=mmap, keep_file_open=keep_file_open) - try: - matf = file_map['mat'].get_prepare_fileobj() - except OSError: - return ret - # Allow for possibility of empty file -> no update to affine - with matf: - contents = matf.read() - if len(contents) == 0: - return ret - import scipy.io as sio # type: ignore[import] - - mats = sio.loadmat(BytesIO(contents)) - if 'mat' in mats: # this overrides a 'M', and includes any flip - mat = mats['mat'] - if mat.ndim > 2: - warnings.warn('More than one affine in "mat" matrix, using first') - mat = mat[:, :, 0] - ret._affine = mat - elif 'M' in mats: # the 'M' matrix does not include flips - hdr = ret._header - if hdr.default_x_flip: - ret._affine = np.dot(np.diag([-1, 1, 1, 1]), mats['M']) - else: - ret._affine = mats['M'] - else: - raise ValueError('mat file found but no "mat" or "M" in it') - # Adjust for matlab 1,1,1 voxel origin - to_111 = np.eye(4) - to_111[:3, 3] = 1 - ret._affine = np.dot(ret._affine, to_111) - return ret - - def to_file_map(self, file_map=None, dtype=None): - """Write image to `file_map` or contained ``self.file_map`` - - Extends Analyze ``to_file_map`` method by writing ``mat`` file - - Parameters - ---------- - file_map : None or mapping, optional - files mapping. If None (default) use object's ``file_map`` - attribute instead - """ - if file_map is None: - file_map = self.file_map - super().to_file_map(file_map, dtype=dtype) - mat = self._affine - if mat is None: - return - import scipy.io as sio - - hdr = self._header - if hdr.default_x_flip: - M = np.dot(np.diag([-1, 1, 1, 1]), mat) - else: - M = mat - # Adjust for matlab 1,1,1 voxel origin - from_111 = np.eye(4) - from_111[:3, 3] = -1 - M = np.dot(M, from_111) - mat = np.dot(mat, from_111) - # use matlab 4 format to allow gzipped write without error - with file_map['mat'].get_prepare_fileobj(mode='wb') as mfobj: - sio.savemat(mfobj, {'M': M, 'mat': mat}, format='4') - - -load = Spm99AnalyzeImage.from_filename -save = Spm99AnalyzeImage.instance_to_filename diff --git a/nibabel/streamlines/__init__.py b/nibabel/streamlines/__init__.py deleted file mode 100644 index 02e11e4f29..0000000000 --- a/nibabel/streamlines/__init__.py +++ /dev/null @@ -1,136 +0,0 @@ -"""Multiformat-capable streamline format read / write interface""" - -import os -import warnings - -from .array_sequence import ArraySequence -from .header import Field -from .tck import TckFile -from .tractogram import LazyTractogram, Tractogram -from .tractogram_file import ExtensionWarning -from .trk import TrkFile - -# List of all supported formats -FORMATS = { - '.trk': TrkFile, - '.tck': TckFile, -} - - -def is_supported(fileobj): - """Checks if the file-like object if supported by NiBabel. - - Parameters - ---------- - fileobj : string or file-like object - If string, a filename; otherwise an open file-like object pointing - to a streamlines file (and ready to read from the beginning of the - header) - - Returns - ------- - is_supported : boolean - """ - return detect_format(fileobj) is not None - - -def detect_format(fileobj): - """Returns the StreamlinesFile object guessed from the file-like object. - - Parameters - ---------- - fileobj : string or file-like object - If string, a filename; otherwise an open file-like object pointing - to a tractogram file (and ready to read from the beginning of the - header) - - Returns - ------- - tractogram_file : :class:`TractogramFile` class - The class type guessed from the content of `fileobj`. - """ - for format in FORMATS.values(): - try: - if format.is_correct_format(fileobj): - return format - except OSError: - pass - - if isinstance(fileobj, str): - _, ext = os.path.splitext(fileobj) - return FORMATS.get(ext.lower()) - - return None - - -def load(fileobj, lazy_load=False): - """Loads streamlines in *RAS+* and *mm* space from a file-like object. - - Parameters - ---------- - fileobj : string or file-like object - If string, a filename; otherwise an open file-like object - pointing to a streamlines file (and ready to read from the beginning - of the streamlines file's header). - lazy_load : {False, True}, optional - If True, load streamlines in a lazy manner i.e. they will not be kept - in memory and only be loaded when needed. - Otherwise, load all streamlines in memory. - - Returns - ------- - tractogram_file : :class:`TractogramFile` object - Returns an instance of a :class:`TractogramFile` containing data and - metadata of the tractogram loaded from `fileobj`. - - Notes - ----- - The streamline coordinate (0,0,0) refers to the center of the voxel. - """ - tractogram_file = detect_format(fileobj) - - if tractogram_file is None: - raise ValueError(f"Unknown format for 'fileobj': {fileobj}") - - return tractogram_file.load(fileobj, lazy_load=lazy_load) - - -def save(tractogram, filename, **kwargs): - r"""Saves a tractogram to a file. - - Parameters - ---------- - tractogram : :class:`Tractogram` object or :class:`TractogramFile` object - If :class:`Tractogram` object, the file format will be guessed from - `filename` and a :class:`TractogramFile` object will be created using - provided keyword arguments. - If :class:`TractogramFile` object, the file format is known and will - be used to save its content to `filename`. - filename : str - Name of the file where the tractogram will be saved. - \*\*kwargs : keyword arguments - Keyword arguments passed to :class:`TractogramFile` constructor. - Should not be specified if `tractogram` is already an instance of - :class:`TractogramFile`. - """ - tractogram_file_class = detect_format(filename) - if isinstance(tractogram, Tractogram): - if tractogram_file_class is None: - msg = f"Unknown tractogram file format: '{filename}'" - raise ValueError(msg) - - tractogram_file = tractogram_file_class(tractogram, **kwargs) - - else: # Assume it's a TractogramFile object. - tractogram_file = tractogram - if tractogram_file_class is None or not isinstance(tractogram_file, tractogram_file_class): - msg = ( - "The extension you specified is unusual for the provided 'TractogramFile' object." - ) - warnings.warn(msg, ExtensionWarning) - - if kwargs: - msg = "A 'TractogramFile' object was provided, no need for keyword arguments." - raise ValueError(msg) - - tractogram_file.save(filename) diff --git a/nibabel/streamlines/array_sequence.py b/nibabel/streamlines/array_sequence.py deleted file mode 100644 index 63336352bd..0000000000 --- a/nibabel/streamlines/array_sequence.py +++ /dev/null @@ -1,618 +0,0 @@ -import numbers -from functools import reduce -from operator import mul - -import numpy as np - -MEGABYTE = 1024 * 1024 - - -def is_array_sequence(obj): - """Return True if `obj` is an array sequence.""" - try: - return obj.is_array_sequence - except AttributeError: - return False - - -def is_ndarray_of_int_or_bool(obj): - return isinstance(obj, np.ndarray) and ( - np.issubdtype(obj.dtype, np.integer) or np.issubdtype(obj.dtype, np.bool_) - ) - - -class _BuildCache: - def __init__(self, arr_seq, common_shape, dtype): - self.offsets = list(arr_seq._offsets) - self.lengths = list(arr_seq._lengths) - self.next_offset = arr_seq._get_next_offset() - self.bytes_per_buf = arr_seq._buffer_size * MEGABYTE - # Use the passed dtype only if null data array - self.dtype = dtype if arr_seq._data.size == 0 else arr_seq._data.dtype - if arr_seq.common_shape != () and common_shape != arr_seq.common_shape: - raise ValueError('All dimensions, except the first one, must match exactly') - self.common_shape = common_shape - n_in_row = reduce(mul, common_shape, 1) - bytes_per_row = n_in_row * dtype.itemsize - self.rows_per_buf = max(1, self.bytes_per_buf // bytes_per_row) - - def update_seq(self, arr_seq): - arr_seq._offsets = np.array(self.offsets) - arr_seq._lengths = np.array(self.lengths) - - -def _define_operators(cls): - """Decorator which adds support for some Python operators.""" - - def _wrap(cls, op, inplace=False, unary=False): - def fn_unary_op(self): - try: - return self._op(op) - except SystemError as e: - message = ( - 'Numpy returned an uninformative error. It possibly should be ' - "'Integers to negative integer powers are not allowed.' " - 'See https://github.com/numpy/numpy/issues/19634 for details.' - ) - raise ValueError(message) from e - - def fn_binary_op(self, value): - try: - return self._op(op, value, inplace=inplace) - except SystemError as e: - message = ( - 'Numpy returned an uninformative error. It possibly should be ' - "'Integers to negative integer powers are not allowed.' " - 'See https://github.com/numpy/numpy/issues/19634 for details.' - ) - raise ValueError(message) from e - - setattr(cls, op, fn_unary_op if unary else fn_binary_op) - fn = getattr(cls, op) - fn.__name__ = op - fn.__doc__ = getattr(np.ndarray, op).__doc__ - - for op in ( - '__add__', - '__sub__', - '__mul__', - '__mod__', - '__pow__', - '__floordiv__', - '__truediv__', - '__lshift__', - '__rshift__', - '__or__', - '__and__', - '__xor__', - ): - _wrap(cls, op=op, inplace=False) - _wrap(cls, op=f'__i{op.strip("_")}__', inplace=True) - - for op in ('__eq__', '__ne__', '__lt__', '__le__', '__gt__', '__ge__'): - _wrap(cls, op) - - for op in ('__neg__', '__abs__', '__invert__'): - _wrap(cls, op, unary=True) - - return cls - - -@_define_operators -class ArraySequence: - """Sequence of ndarrays having variable first dimension sizes. - - This is a container that can store multiple ndarrays where each ndarray - might have a different first dimension size but a *common* size for the - remaining dimensions. - - More generally, an instance of :class:`ArraySequence` of length $N$ is - composed of $N$ ndarrays of shape $(d_1, d_2, ... d_D)$ where $d_1$ - can vary in length between arrays but $(d_2, ..., d_D)$ have to be the - same for every ndarray. - """ - - def __init__(self, iterable=None, buffer_size=4): - """Initialize array sequence instance - - Parameters - ---------- - iterable : None or iterable or :class:`ArraySequence`, optional - If None, create an empty :class:`ArraySequence` object. - If iterable, create a :class:`ArraySequence` object initialized - from array-like objects yielded by the iterable. - If :class:`ArraySequence`, create a view (no memory is allocated). - For an actual copy use :meth:`.copy` instead. - buffer_size : float, optional - Size (in Mb) for memory allocation when `iterable` is a generator. - """ - # Create new empty `ArraySequence` object. - self._is_view = False - self._data = np.array([]) - self._offsets = np.array([], dtype=np.intp) - self._lengths = np.array([], dtype=np.intp) - self._buffer_size = buffer_size - self._build_cache = None - - if iterable is None: - return - - if is_array_sequence(iterable): - # Create a view. - self._data = iterable._data - self._offsets = iterable._offsets - self._lengths = iterable._lengths - self._is_view = True - return - - self.extend(iterable) - - @property - def is_sliced_view(self): - return self._lengths.sum() != self._data.shape[0] - - @property - def is_array_sequence(self): - return True - - @property - def common_shape(self): - """Matching shape of the elements in this array sequence.""" - return self._data.shape[1:] - - @property - def total_nb_rows(self): - """Total number of rows in this array sequence.""" - return np.sum(self._lengths) - - def get_data(self): - """Returns a *copy* of the elements in this array sequence. - - Notes - ----- - To modify the data on this array sequence, one can use - in-place mathematical operators (e.g., `seq += ...`) or the use - assignment operator (i.e, `seq[...] = value`). - """ - return self.copy()._data - - def _check_shape(self, arrseq): - """Check whether this array sequence is compatible with another.""" - msg = 'cannot perform operation - array sequences have different' - if len(self._lengths) != len(arrseq._lengths): - msg += f' lengths: {len(self._lengths)} vs. {len(arrseq._lengths)}.' - raise ValueError(msg) - - if self.total_nb_rows != arrseq.total_nb_rows: - msg += f' amount of data: {self.total_nb_rows} vs. {arrseq.total_nb_rows}.' - raise ValueError(msg) - - if self.common_shape != arrseq.common_shape: - msg += f' common shape: {self.common_shape} vs. {arrseq.common_shape}.' - raise ValueError(msg) - - return True - - def _get_next_offset(self): - """Offset in ``self._data`` at which to write next rowelement""" - if len(self._offsets) == 0: - return 0 - imax = np.argmax(self._offsets) - return self._offsets[imax] + self._lengths[imax] - - def append(self, element, cache_build=False): - """Appends `element` to this array sequence. - - Append can be a lot faster if it knows that it is appending several - elements instead of a single element. In that case it can cache the - parameters it uses between append operations, in a "build cache". To - tell append to do this, use ``cache_build=True``. If you use - ``cache_build=True``, you need to finalize the append operations with - :meth:`finalize_append`. - - Parameters - ---------- - element : ndarray - Element to append. The shape must match already inserted elements - shape except for the first dimension. - cache_build : {False, True} - Whether to save the build cache from this append routine. If True, - append can assume it is the only player updating `self`, and the - caller must finalize `self` after all append operations, with - ``self.finalize_append()``. - - Returns - ------- - None - - Notes - ----- - If you need to add multiple elements you should consider - `ArraySequence.extend`. - """ - element = np.asarray(element) - if element.size == 0: - return - el_shape = element.shape - n_items, common_shape = el_shape[0], el_shape[1:] - build_cache = self._build_cache - in_cached_build = build_cache is not None - if not in_cached_build: # One shot append, not part of sequence - build_cache = _BuildCache(self, common_shape, element.dtype) - next_offset = build_cache.next_offset - req_rows = next_offset + n_items - if self._data.shape[0] < req_rows: - self._resize_data_to(req_rows, build_cache) - self._data[next_offset:req_rows] = element - build_cache.offsets.append(next_offset) - build_cache.lengths.append(n_items) - build_cache.next_offset = req_rows - if in_cached_build: - return - if cache_build: - self._build_cache = build_cache - else: - build_cache.update_seq(self) - - def finalize_append(self): - """Finalize process of appending several elements to `self` - - :meth:`append` can be a lot faster if it knows that it is appending - several elements instead of a single element. To tell the append - method this is the case, use ``cache_build=True``. This method - finalizes the series of append operations after a call to - :meth:`append` with ``cache_build=True``. - """ - if self._build_cache is None: - return - self._build_cache.update_seq(self) - self._build_cache = None - self.shrink_data() - - def _resize_data_to(self, n_rows, build_cache): - """Resize data array if required""" - # Calculate new data shape, rounding up to nearest buffer size - n_bufs = np.ceil(n_rows / build_cache.rows_per_buf) - extended_n_rows = int(n_bufs * build_cache.rows_per_buf) - new_shape = (extended_n_rows,) + build_cache.common_shape - if self._data.size == 0: - self._data = np.empty(new_shape, dtype=build_cache.dtype) - else: - try: - self._data.resize(new_shape) - except ValueError: - self._data = self._data.copy() - self._data.resize(new_shape, refcheck=False) - - def shrink_data(self): - self._data.resize((self._get_next_offset(),) + self.common_shape, refcheck=False) - - def extend(self, elements): - """Appends all `elements` to this array sequence. - - Parameters - ---------- - elements : iterable of ndarrays or :class:`ArraySequence` object - If iterable of ndarrays, each ndarray will be concatenated along - the first dimension then appended to the data of this - ArraySequence. - If :class:`ArraySequence` object, its data are simply appended to - the data of this ArraySequence. - - Returns - ------- - None - - Notes - ----- - The shape of the elements to be added must match the one of the data of - this :class:`ArraySequence` except for the first dimension. - """ - # If possible try pre-allocating memory. - try: - iter_len = len(elements) - except TypeError: - pass - else: # We do know the iterable length - if iter_len == 0: - return - e0 = np.asarray(elements[0]) - n_elements = np.sum([len(e) for e in elements]) - self._build_cache = _BuildCache(self, e0.shape[1:], e0.dtype) - self._resize_data_to(self._get_next_offset() + n_elements, self._build_cache) - - for e in elements: - self.append(e, cache_build=True) - - self.finalize_append() - - def copy(self): - """Creates a copy of this :class:`ArraySequence` object. - - Returns - ------- - seq_copy : :class:`ArraySequence` instance - Copy of `self`. - - Notes - ----- - We do not simply deepcopy this object because we have a chance to use - less memory. For example, if the array sequence being copied is the - result of a slicing operation on an array sequence. - """ - seq = self.__class__() - total_lengths = np.sum(self._lengths) - seq._data = np.empty((total_lengths,) + self._data.shape[1:], dtype=self._data.dtype) - - next_offset = 0 - offsets = [] - for offset, length in zip(self._offsets, self._lengths): - offsets.append(next_offset) - chunk = self._data[offset : offset + length] - seq._data[next_offset : next_offset + length] = chunk - next_offset += length - - seq._offsets = np.asarray(offsets) - seq._lengths = self._lengths.copy() - - return seq - - def __getitem__(self, idx): - """Get sequence(s) through standard or advanced numpy indexing. - - Parameters - ---------- - idx : int or slice or list or ndarray - If int, index of the element to retrieve. - If slice, use slicing to retrieve elements. - If list, indices of the elements to retrieve. - If ndarray with dtype int, indices of the elements to retrieve. - If ndarray with dtype bool, only retrieve selected elements. - - Returns - ------- - ndarray or :class:`ArraySequence` - If `idx` is an int, returns the selected sequence. - Otherwise, returns a :class:`ArraySequence` object which is a view - of the selected sequences. - """ - if isinstance(idx, (numbers.Integral, np.integer)): - start = self._offsets[idx] - return self._data[start : start + self._lengths[idx]] - - seq = self.__class__() - seq._is_view = True - if isinstance(idx, tuple): - off_idx = idx[0] - seq._data = self._data.__getitem__((slice(None),) + idx[1:]) - else: - off_idx = idx - seq._data = self._data - - if isinstance(off_idx, slice): # Standard list slicing - seq._offsets = self._offsets[off_idx] - seq._lengths = self._lengths[off_idx] - return seq - - if isinstance(off_idx, (list, range)) or is_ndarray_of_int_or_bool(off_idx): - # Fancy indexing - seq._offsets = self._offsets[off_idx] - seq._lengths = self._lengths[off_idx] - return seq - - raise TypeError( - 'Index must be either an int, a slice, a list of int' - ' or a ndarray of bool! Not ' + str(type(idx)) - ) - - def __setitem__(self, idx, elements): - """Set sequence(s) through standard or advanced numpy indexing. - - Parameters - ---------- - idx : int or slice or list or ndarray - If int, index of the element to retrieve. - If slice, use slicing to retrieve elements. - If list, indices of the elements to retrieve. - If ndarray with dtype int, indices of the elements to retrieve. - If ndarray with dtype bool, only retrieve selected elements. - elements: ndarray or :class:`ArraySequence` - Data that will overwrite selected sequences. - If `idx` is an int, `elements` is expected to be a ndarray. - Otherwise, `elements` is expected a :class:`ArraySequence` object. - """ - if isinstance(idx, (numbers.Integral, np.integer)): - start = self._offsets[idx] - self._data[start : start + self._lengths[idx]] = elements - return - - if isinstance(idx, tuple): - off_idx = idx[0] - data = self._data.__getitem__((slice(None),) + idx[1:]) - else: - off_idx = idx - data = self._data - - if isinstance(off_idx, slice): # Standard list slicing - offsets = self._offsets[off_idx] - lengths = self._lengths[off_idx] - - elif isinstance(off_idx, (list, range)) or is_ndarray_of_int_or_bool(off_idx): - # Fancy indexing - offsets = self._offsets[off_idx] - lengths = self._lengths[off_idx] - - else: - raise TypeError( - 'Index must be either an int, a slice, a list of int' - ' or a ndarray of bool! Not ' + str(type(idx)) - ) - - if is_array_sequence(elements): - if len(lengths) != len(elements): - msg = f'Trying to set {len(lengths)} sequences with {len(elements)} sequences.' - raise ValueError(msg) - - if sum(lengths) != elements.total_nb_rows: - msg = f'Trying to set {sum(lengths)} points with {elements.total_nb_rows} points.' - raise ValueError(msg) - - for o1, l1, o2, l2 in zip(offsets, lengths, elements._offsets, elements._lengths): - data[o1 : o1 + l1] = elements._data[o2 : o2 + l2] - - elif isinstance(elements, numbers.Number): - for o1, l1 in zip(offsets, lengths): - data[o1 : o1 + l1] = elements - - else: # Try to iterate over it. - for o1, l1, element in zip(offsets, lengths, elements): - data[o1 : o1 + l1] = element - - def _op(self, op, value=None, inplace=False): - """Applies some operator to this arraysequence. - - This handles both unary and binary operators with a scalar or another - array sequence. Operations are performed directly on the underlying - data, or a copy of it, which depends on the value of `inplace`. - - Parameters - ---------- - op : str - Name of the Python operator (e.g., `"__add__"`). - value : scalar or :class:`ArraySequence`, optional - If None, the operator is assumed to be unary. - Otherwise, that value is used in the binary operation. - inplace: bool, optional - If False, the operation is done on a copy of this array sequence. - Otherwise, this array sequence gets modified directly. - """ - seq = self if inplace else self.copy() - - if is_array_sequence(value) and seq._check_shape(value): - elements = zip( - seq._offsets, - seq._lengths, - self._offsets, - self._lengths, - value._offsets, - value._lengths, - ) - - # Change seq.dtype to match the operation resulting type. - o0, l0, o1, l1, o2, l2 = next(elements) - tmp = getattr(self._data[o1 : o1 + l1], op)(value._data[o2 : o2 + l2]) - seq._data = seq._data.astype(tmp.dtype) - seq._data[o0 : o0 + l0] = tmp - - for o0, l0, o1, l1, o2, l2 in elements: - seq._data[o0 : o0 + l0] = getattr(self._data[o1 : o1 + l1], op)( - value._data[o2 : o2 + l2] - ) - - else: - args = [] if value is None else [value] # Dealing with unary and binary ops. - elements = zip(seq._offsets, seq._lengths, self._offsets, self._lengths) - - # Change seq.dtype to match the operation resulting type. - o0, l0, o1, l1 = next(elements) - tmp = getattr(self._data[o1 : o1 + l1], op)(*args) - seq._data = seq._data.astype(tmp.dtype) - seq._data[o0 : o0 + l0] = tmp - - for o0, l0, o1, l1 in elements: - seq._data[o0 : o0 + l0] = getattr(self._data[o1 : o1 + l1], op)(*args) - - return seq - - def __iter__(self): - if len(self._lengths) != len(self._offsets): - raise ValueError( - 'ArraySequence object corrupted: len(self._lengths) != len(self._offsets)' - ) - - for offset, lengths in zip(self._offsets, self._lengths): - yield self._data[offset : offset + lengths] - - def __len__(self): - return len(self._offsets) - - def __repr__(self): - if len(self) > np.get_printoptions()['threshold']: - # Show only the first and last edgeitems. - edgeitems = np.get_printoptions()['edgeitems'] - data = str(list(self[:edgeitems]))[:-1] - data += ', ..., ' - data += str(list(self[-edgeitems:]))[1:] - else: - data = str(list(self)) - - return f'{self.__class__.__name__}({data})' - - def save(self, filename): - """Saves this :class:`ArraySequence` object to a .npz file.""" - np.savez(filename, data=self._data, offsets=self._offsets, lengths=self._lengths) - - @classmethod - def load(cls, filename): - """Loads a :class:`ArraySequence` object from a .npz file.""" - content = np.load(filename) - seq = cls() - seq._data = content['data'] - seq._offsets = content['offsets'] - seq._lengths = content['lengths'] - return seq - - -def create_arraysequences_from_generator(gen, n, buffer_sizes=None): - """Creates :class:`ArraySequence` objects from a generator yielding tuples - - Parameters - ---------- - gen : generator - Generator yielding a size `n` tuple containing the values to put in the - array sequences. - n : int - Number of :class:`ArraySequences` object to create. - buffer_sizes : list of float, optional - Sizes (in Mb) for each ArraySequence's buffer. - """ - if buffer_sizes is None: - buffer_sizes = [4] * n - - seqs = [ArraySequence(buffer_size=size) for size in buffer_sizes] - for data in gen: - for i, seq in enumerate(seqs): - if data[i].nbytes > 0: - seq.append(data[i], cache_build=True) - - for seq in seqs: - seq.finalize_append() - return seqs - - -def concatenate(seqs, axis): - """Concatenates multiple :class:`ArraySequence` objects along an axis. - - Parameters - ---------- - seqs: iterable of :class:`ArraySequence` objects - Sequences to concatenate. - axis : int - Axis along which the sequences will be concatenated. - - Returns - ------- - new_seq: :class:`ArraySequence` object - New :class:`ArraySequence` object which is the result of - concatenating multiple sequences along the given axis. - """ - new_seq = seqs[0].copy() - if axis == 0: - # This is the same as an extend. - for seq in seqs[1:]: - new_seq.extend(seq) - - return new_seq - - new_seq._data = np.concatenate([seq._data for seq in seqs], axis=axis) - return new_seq diff --git a/nibabel/streamlines/header.py b/nibabel/streamlines/header.py deleted file mode 100644 index a3b52b0747..0000000000 --- a/nibabel/streamlines/header.py +++ /dev/null @@ -1,22 +0,0 @@ -"""Field class defining common header fields in tractogram files""" - - -class Field: - """Header fields common to multiple streamline file formats. - - In IPython, use `nibabel.streamlines.Field??` to list them. - """ - - NB_STREAMLINES = 'nb_streamlines' - STEP_SIZE = 'step_size' - METHOD = 'method' - NB_SCALARS_PER_POINT = 'nb_scalars_per_point' - NB_PROPERTIES_PER_STREAMLINE = 'nb_properties_per_streamline' - NB_POINTS = 'nb_points' - VOXEL_SIZES = 'voxel_sizes' - DIMENSIONS = 'dimensions' - MAGIC_NUMBER = 'magic_number' - ORIGIN = 'origin' - VOXEL_TO_RASMM = 'voxel_to_rasmm' - VOXEL_ORDER = 'voxel_order' - ENDIANNESS = 'endianness' diff --git a/nibabel/streamlines/tck.py b/nibabel/streamlines/tck.py deleted file mode 100644 index 358c579362..0000000000 --- a/nibabel/streamlines/tck.py +++ /dev/null @@ -1,495 +0,0 @@ -"""Read / write access to TCK streamlines format. - -TCK format is defined at -http://mrtrix.readthedocs.io/en/latest/getting_started/image_data.html?highlight=format#tracks-file-format-tck -""" - -import os -import warnings -from contextlib import suppress - -import numpy as np - -from nibabel.openers import Opener - -from .array_sequence import ArraySequence -from .header import Field -from .tractogram import LazyTractogram, Tractogram, TractogramItem -from .tractogram_file import DataError, DataWarning, HeaderError, HeaderWarning, TractogramFile -from .utils import peek_next - -MEGABYTE = 1024 * 1024 - - -class TckFile(TractogramFile): - """Convenience class to encapsulate TCK file format. - - Notes - ----- - MRtrix (so its file format: TCK) considers streamlines coordinates - to be in world space (RAS+ and mm space). MRtrix refers to that space - as the "real" or "scanner" space [#]_. - - Moreover, when streamlines are mapped back to voxel space [#]_, a - streamline point located at an integer coordinate (i,j,k) is considered - to be at the center of the corresponding voxel. This is in contrast with - TRK's internal convention where it would have referred to a corner. - - NiBabel's streamlines internal representation follows the same - convention as MRtrix. - - .. [#] http://www.nitrc.org/pipermail/mrtrix-discussion/2014-January/000859.html - .. [#] http://nipy.org/nibabel/coordinate_systems.html#voxel-coordinates-are-in-voxel-space - """ - - # Constants - MAGIC_NUMBER = b'mrtrix tracks' - SUPPORTS_DATA_PER_POINT = False # Not yet - SUPPORTS_DATA_PER_STREAMLINE = False # Not yet - - FIBER_DELIMITER = np.array([[np.nan, np.nan, np.nan]], ' 0: - keys = ', '.join(data_for_streamline.keys()) - msg = ( - 'TCK format does not support saving additional ' - f'data alongside streamlines. Dropping: {keys}' - ) - warnings.warn(msg, DataWarning) - - data_for_points = first_item.data_for_points - if len(data_for_points) > 0: - keys = ', '.join(data_for_points.keys()) - msg = ( - 'TCK format does not support saving additional ' - f'data alongside points. Dropping: {keys}' - ) - warnings.warn(msg, DataWarning) - - for t in tractogram: - data = np.r_[t.streamline, self.FIBER_DELIMITER] - f.write(data.astype(dtype).tobytes()) - nb_streamlines += 1 - - header[Field.NB_STREAMLINES] = nb_streamlines - - # Add the EOF_DELIMITER. - f.write(self.EOF_DELIMITER.tobytes()) - self._finalize_header(f, header, offset=beginning) - - @staticmethod - def _write_header(fileobj, header): - """Write TCK header to file-like object. - - Parameters - ---------- - fileobj : file-like object - An open file-like object in binary mode pointing to TCK file (and - ready to read from the beginning of the TCK header). - """ - # Fields to exclude - exclude = [ - Field.MAGIC_NUMBER, # Handled separately. - Field.NB_STREAMLINES, # Handled separately. - Field.ENDIANNESS, # Handled separately. - Field.VOXEL_TO_RASMM, # Streamlines are always in RAS+ mm. - 'count', - 'datatype', - 'file', - ] # Fields being replaced. - - lines = [ - f'count: {header[Field.NB_STREAMLINES]:010}', - 'datatype: Float32LE', # Always Float32LE. - ] - lines.extend( - f'{k}: {v}' for k, v in header.items() if k not in exclude and not k.startswith('_') - ) - out = '\n'.join(lines) - - if out.count(':') > len(lines): - # : only one per line (except the last one which contains END). - msg = f"Key-value pairs cannot contain ':':\n{out}" - raise HeaderError(msg) - - out = header[Field.MAGIC_NUMBER] + b'\n' + out.encode('utf-8') - - # Compute data offset considering the offset string representation - # headers + "file" header + END + \n's - hdr_offset = len(out) + 8 + 3 + 3 - offset_repr = f'{hdr_offset}' - - # Adding the offset may increase one char to the offset repr - hdr_offset += len(f'{hdr_offset + len(offset_repr)}') - - # Write header to file. - fileobj.write(out) - fileobj.write(f'\nfile: . {hdr_offset}\nEND\n'.encode()) - - @classmethod - def _read_header(cls, fileobj): - """Reads a TCK header from a file. - - Parameters - ---------- - fileobj : string or file-like object - If string, a filename; otherwise an open file-like object in - binary mode pointing to TCK file (and ready to read from the - beginning of the TCK header). Note that calling this function - does not change the file position. - - Returns - ------- - header : dict - Metadata associated with this tractogram file. - """ - - # Build header dictionary from the buffer - hdr = {} - offset_data = 0 - - with Opener(fileobj) as f: - # Record start position - start_position = f.tell() - - # Make sure we are at the beginning of the file - f.seek(0, os.SEEK_SET) - - # Read magic number - magic_number = f.read(len(cls.MAGIC_NUMBER)) - - if magic_number != cls.MAGIC_NUMBER: - raise HeaderError(f'Invalid magic number: {magic_number}') - - hdr[Field.MAGIC_NUMBER] = magic_number - - f.seek(1, os.SEEK_CUR) # Skip \n - - found_end = False - key = None - tmp_hdr = {} - - # Read all key-value pairs contained in the header, stop at EOF - for n_line, line in enumerate(f, 1): - line = line.decode('utf-8').strip() - - if not line: # Skip empty lines - continue - - if line == 'END': # End of the header - found_end = True - break - - # Set new key if available, otherwise append to last known key - with suppress(ValueError): - key, line = line.split(':', 1) - key = key.strip() - - # Apparent continuation line before any keys are found - if key is None: - raise HeaderError(f'Invalid header (line {n_line}): {line}') - - tmp_hdr.setdefault(key, []).append(line.strip()) - - if not found_end: - raise HeaderError('Missing END in the header.') - - hdr.update({key: '\n'.join(val) for key, val in tmp_hdr.items()}) - - offset_data = f.tell() - - # Set the file position where it was, in case it was previously open - if start_position is not None: - f.seek(start_position, os.SEEK_SET) - - # Check integrity of TCK header. - if 'datatype' not in hdr: - msg = "Missing 'datatype' attribute in TCK header. Assuming it is Float32LE." - warnings.warn(msg, HeaderWarning) - hdr['datatype'] = 'Float32LE' - - if not hdr['datatype'].startswith('Float32'): - msg = ( - f"TCK only supports float32 dtype but 'datatype: {hdr['datatype']}' " - 'was specified in the header.' - ) - raise HeaderError(msg) - - if 'file' not in hdr: - msg = "Missing 'file' attribute in TCK header. Will try to guess it." - warnings.warn(msg, HeaderWarning) - hdr['file'] = f'. {offset_data}' - - if hdr['file'].split()[0] != '.': - msg = ( - 'TCK only supports single-file - in other words the filename part must be ' - f"specified as '.' but '{hdr['file'].split()[0]}' was specified." - ) - raise HeaderError("Missing 'file' attribute in TCK header.") - - # Set endianness and _dtype attributes in the header. - hdr[Field.ENDIANNESS] = '>' if hdr['datatype'].endswith('BE') else '<' - - hdr['_dtype'] = np.dtype(hdr[Field.ENDIANNESS] + 'f4') - - # Keep the file position where the data begin. - hdr['_offset_data'] = int(hdr['file'].split()[1]) - - return hdr - - @classmethod - def _read(cls, fileobj, header, buffer_size=4): - """Return generator that reads TCK data from `fileobj` given `header` - - Parameters - ---------- - fileobj : string or file-like object - If string, a filename; otherwise an open file-like object in - binary mode pointing to TCK file (and ready to read from the - beginning of the TCK header). Note that calling this function - does not change the file position. - header : dict - Metadata associated with this tractogram file. - buffer_size : float, optional - Size (in Mb) for buffering. - - Yields - ------ - points : ndarray of shape (n_pts, 3) - Streamline points - """ - dtype = header['_dtype'] - coordinate_size = 3 * dtype.itemsize - # Make buffer_size an integer and a multiple of coordinate_size. - buffer_size = int(buffer_size * MEGABYTE) - buffer_size += coordinate_size - (buffer_size % coordinate_size) - - with Opener(fileobj) as f: - start_position = f.tell() - - # Set the file position at the beginning of the data. - f.seek(header['_offset_data'], os.SEEK_SET) - - eof = False - leftover = np.empty((0, 3), dtype=' seq._buffer_size - - # Check generator result - check_arr_seq(seq, SEQ_DATA['data']) - check_arr_seq(seq_with_buffer, SEQ_DATA['data']) - - # Already consumed generator - check_empty_arr_seq(ArraySequence(gen_1)) - - def test_creating_arraysequence_from_arraysequence(self): - seq = ArraySequence(SEQ_DATA['data']) - check_arr_seq(ArraySequence(seq), SEQ_DATA['data']) - - # From an empty ArraySequence - seq = ArraySequence() - check_empty_arr_seq(ArraySequence(seq)) - - def test_arraysequence_iter(self): - assert_arrays_equal(SEQ_DATA['seq'], SEQ_DATA['data']) - - # Try iterating through a corrupted ArraySequence object. - seq = SEQ_DATA['seq'].copy() - seq._lengths = seq._lengths[::2] - with pytest.raises(ValueError): - list(seq) - - def test_arraysequence_copy(self): - orig = SEQ_DATA['seq'] - seq = orig.copy() - n_rows = seq.total_nb_rows - assert n_rows == orig.total_nb_rows - assert_array_equal(seq._data, orig._data[:n_rows]) - assert seq._data is not orig._data - assert_array_equal(seq._offsets, orig._offsets) - assert seq._offsets is not orig._offsets - assert_array_equal(seq._lengths, orig._lengths) - assert seq._lengths is not orig._lengths - assert seq.common_shape == orig.common_shape - - # Taking a copy of an `ArraySequence` generated by slicing. - # Only keep needed data. - seq = orig[::2].copy() - check_arr_seq(seq, SEQ_DATA['data'][::2]) - assert seq._data is not orig._data - - def test_arraysequence_append(self): - element = generate_data( - nb_arrays=1, common_shape=SEQ_DATA['seq'].common_shape, rng=SEQ_DATA['rng'] - )[0] - - # Append a new element. - seq = SEQ_DATA['seq'].copy() # Copy because of in-place modification. - seq.append(element) - check_arr_seq(seq, SEQ_DATA['data'] + [element]) - - # Append a list of list. - seq = SEQ_DATA['seq'].copy() # Copy because of in-place modification. - seq.append(element.tolist()) - check_arr_seq(seq, SEQ_DATA['data'] + [element]) - - # Append to an empty ArraySequence. - seq = ArraySequence() - seq.append(element) - check_arr_seq(seq, [element]) - - # Append an empty array. - seq = SEQ_DATA['seq'].copy() # Copy because of in-place modification. - seq.append([]) - check_arr_seq(seq, SEQ_DATA['seq']) - - # Append an element with different shape. - element = generate_data( - nb_arrays=1, common_shape=SEQ_DATA['seq'].common_shape * 2, rng=SEQ_DATA['rng'] - )[0] - with pytest.raises(ValueError): - seq.append(element) - - def test_arraysequence_extend(self): - new_data = generate_data( - nb_arrays=10, common_shape=SEQ_DATA['seq'].common_shape, rng=SEQ_DATA['rng'] - ) - - # Extend with an empty list. - seq = SEQ_DATA['seq'].copy() # Copy because of in-place modification. - seq.extend([]) - check_arr_seq(seq, SEQ_DATA['data']) - - # Extend with a list of ndarrays. - seq = SEQ_DATA['seq'].copy() # Copy because of in-place modification. - seq.extend(new_data) - check_arr_seq(seq, SEQ_DATA['data'] + new_data) - - # Extend with a generator. - seq = SEQ_DATA['seq'].copy() # Copy because of in-place modification. - seq.extend(d for d in new_data) - check_arr_seq(seq, SEQ_DATA['data'] + new_data) - - # Extend with another `ArraySequence` object. - seq = SEQ_DATA['seq'].copy() # Copy because of in-place modification. - seq.extend(ArraySequence(new_data)) - check_arr_seq(seq, SEQ_DATA['data'] + new_data) - - # Extend with an `ArraySequence` view (e.g. been sliced). - # Need to make sure we extend only the data we need. - seq = SEQ_DATA['seq'].copy() # Copy because of in-place modification. - seq.extend(ArraySequence(new_data)[::2]) - check_arr_seq(seq, SEQ_DATA['data'] + new_data[::2]) - - # Test extending an empty ArraySequence - seq = ArraySequence() - seq.extend(ArraySequence()) - check_empty_arr_seq(seq) - - seq.extend(SEQ_DATA['seq']) - check_arr_seq(seq, SEQ_DATA['data']) - - # Extend with elements of different shape. - data = generate_data( - nb_arrays=10, common_shape=SEQ_DATA['seq'].common_shape * 2, rng=SEQ_DATA['rng'] - ) - seq = SEQ_DATA['seq'].copy() # Copy because of in-place modification. - with pytest.raises(ValueError): - seq.extend(data) - - # Extend after extracting some slice - _ = seq[:2] - seq.extend(ArraySequence(new_data)) - - def test_arraysequence_getitem(self): - # Get one item - for i, e in enumerate(SEQ_DATA['seq']): - assert_array_equal(SEQ_DATA['seq'][i], e) - - # Get all items using indexing (creates a view). - indices = list(range(len(SEQ_DATA['seq']))) - seq_view = SEQ_DATA['seq'][indices] - check_arr_seq_view(seq_view, SEQ_DATA['seq']) - # We took all elements so the view should match the original. - check_arr_seq(seq_view, SEQ_DATA['seq']) - - # Get multiple items using ndarray of dtype integer. - for dtype in [np.int8, np.int16, np.int32, np.int64]: - seq_view = SEQ_DATA['seq'][np.array(indices, dtype=dtype)] - check_arr_seq_view(seq_view, SEQ_DATA['seq']) - # We took all elements so the view should match the original. - check_arr_seq(seq_view, SEQ_DATA['seq']) - - # Get multiple items out of order (creates a view). - SEQ_DATA['rng'].shuffle(indices) - seq_view = SEQ_DATA['seq'][indices] - check_arr_seq_view(seq_view, SEQ_DATA['seq']) - check_arr_seq(seq_view, [SEQ_DATA['data'][i] for i in indices]) - - # Get slice (this will create a view). - seq_view = SEQ_DATA['seq'][::2] - check_arr_seq_view(seq_view, SEQ_DATA['seq']) - check_arr_seq(seq_view, SEQ_DATA['data'][::2]) - - # Use advanced indexing with ndarray of data type bool. - selection = np.array([False, True, True, False, True]) - seq_view = SEQ_DATA['seq'][selection] - check_arr_seq_view(seq_view, SEQ_DATA['seq']) - check_arr_seq(seq_view, [SEQ_DATA['data'][i] for i, keep in enumerate(selection) if keep]) - - # Test invalid indexing - with pytest.raises(TypeError): - SEQ_DATA['seq']['abc'] - - # Get specific columns. - seq_view = SEQ_DATA['seq'][:, 2] - check_arr_seq_view(seq_view, SEQ_DATA['seq']) - check_arr_seq(seq_view, [d[:, 2] for d in SEQ_DATA['data']]) - - # Combining multiple slicing and indexing operations. - seq_view = SEQ_DATA['seq'][::-2][:, 2] - check_arr_seq_view(seq_view, SEQ_DATA['seq']) - check_arr_seq(seq_view, [d[:, 2] for d in SEQ_DATA['data'][::-2]]) - - def test_arraysequence_setitem(self): - # Set one item - seq = SEQ_DATA['seq'] * 0 - for i, e in enumerate(SEQ_DATA['seq']): - seq[i] = e - - check_arr_seq(seq, SEQ_DATA['seq']) - - # Setitem with a scalar. - seq = SEQ_DATA['seq'].copy() - seq[:] = 0 - assert seq._data.sum() == 0 - - # Setitem with a list of ndarray. - seq = SEQ_DATA['seq'] * 0 - seq[:] = SEQ_DATA['data'] - check_arr_seq(seq, SEQ_DATA['data']) - - # Setitem using tuple indexing. - seq = ArraySequence(np.arange(900).reshape((50, 6, 3))) - seq[:, 0] = 0 - assert seq._data[:, 0].sum() == 0 - - # Setitem using tuple indexing. - seq = ArraySequence(np.arange(900).reshape((50, 6, 3))) - seq[range(len(seq))] = 0 - assert seq._data.sum() == 0 - - # Setitem of a slice using another slice. - seq = ArraySequence(np.arange(900).reshape((50, 6, 3))) - seq[0:4] = seq[5:9] - check_arr_seq(seq[0:4], seq[5:9]) - - # Setitem between array sequences with different number of sequences. - seq = ArraySequence(np.arange(900).reshape((50, 6, 3))) - with pytest.raises(ValueError): - seq[0:4] = seq[5:10] - - # Setitem between array sequences with different amount of points. - seq1 = ArraySequence(np.arange(10).reshape(5, 2)) - seq2 = ArraySequence(np.arange(15).reshape(5, 3)) - with pytest.raises(ValueError): - seq1[0:5] = seq2 - - # Setitem between array sequences with different common shape. - seq1 = ArraySequence(np.arange(12).reshape(2, 2, 3)) - seq2 = ArraySequence(np.arange(8).reshape(2, 2, 2)) - - with pytest.raises(ValueError): - seq1[0:2] = seq2 - - # Invalid index. - with pytest.raises(TypeError): - seq[object()] = None - - def test_arraysequence_operators(self): - # Disable division per zero warnings. - flags = np.seterr(divide='ignore', invalid='ignore') - SCALARS = [42, 0.5, True, -3, 0] - CMP_OPS = ['__eq__', '__ne__', '__lt__', '__le__', '__gt__', '__ge__'] - - seq = SEQ_DATA['seq'].copy() - seq_int = SEQ_DATA['seq'].copy() - seq_int._data = seq_int._data.astype(int) - seq_bool = SEQ_DATA['seq'].copy() > 30 - - ARRSEQS = [seq, seq_int, seq_bool] - VIEWS = [seq[::2], seq_int[::2], seq_bool[::2]] - - def _test_unary(op, arrseq): - orig = arrseq.copy() - seq = getattr(orig, op)() - assert seq is not orig - check_arr_seq(seq, [getattr(d, op)() for d in orig]) - - def _test_binary(op, arrseq, scalars, seqs, inplace=False): - for scalar in scalars: - orig = arrseq.copy() - seq = getattr(orig, op)(scalar) - assert (seq is orig) == inplace - - check_arr_seq(seq, [getattr(e, op)(scalar) for e in arrseq]) - - # Test math operators with another ArraySequence. - for other in seqs: - orig = arrseq.copy() - seq = getattr(orig, op)(other) - assert seq is not SEQ_DATA['seq'] - check_arr_seq(seq, [getattr(e1, op)(e2) for e1, e2 in zip(arrseq, other)]) - - # Operations between array sequences of different lengths. - orig = arrseq.copy() - with pytest.raises(ValueError): - getattr(orig, op)(orig[::2]) - - # Operations between array sequences with different amount of data. - seq1 = ArraySequence(np.arange(10).reshape(5, 2)) - seq2 = ArraySequence(np.arange(15).reshape(5, 3)) - with pytest.raises(ValueError): - getattr(seq1, op)(seq2) - - # Operations between array sequences with different common shape. - seq1 = ArraySequence(np.arange(12).reshape(2, 2, 3)) - seq2 = ArraySequence(np.arange(8).reshape(2, 2, 2)) - with pytest.raises(ValueError): - getattr(seq1, op)(seq2) - - for op in [ - '__add__', - '__sub__', - '__mul__', - '__mod__', - '__floordiv__', - '__truediv__', - ] + CMP_OPS: - _test_binary(op, seq, SCALARS, ARRSEQS) - _test_binary(op, seq_int, SCALARS, ARRSEQS) - - # Test math operators with ArraySequence views. - _test_binary(op, seq[::2], SCALARS, VIEWS) - _test_binary(op, seq_int[::2], SCALARS, VIEWS) - - if op in CMP_OPS: - continue - - op = f'__i{op.strip("_")}__' - _test_binary(op, seq, SCALARS, ARRSEQS, inplace=True) - - if op == '__itruediv__': - continue # Going to deal with it separately. - - _test_binary( - op, seq_int, [42, -3, True, 0], [seq_int, seq_bool, -seq_int], inplace=True - ) # int <-- int - - with pytest.raises(TypeError): - _test_binary(op, seq_int, [0.5], [], inplace=True) # int <-- float - with pytest.raises(TypeError): - _test_binary(op, seq_int, [], [seq], inplace=True) # int <-- float - - # __pow__ : Integers to negative integer powers are not allowed. - _test_binary('__pow__', seq, [42, -3, True, 0], [seq_int, seq_bool, -seq_int]) - _test_binary( - '__ipow__', seq, [42, -3, True, 0], [seq_int, seq_bool, -seq_int], inplace=True - ) - - with pytest.raises(ValueError): - _test_binary('__pow__', seq_int, [-3], []) - with pytest.raises(ValueError): - _test_binary('__ipow__', seq_int, [-3], [], inplace=True) - - # __itruediv__ is only valid with float arrseq. - for scalar in SCALARS + ARRSEQS: - seq_int_cp = seq_int.copy() - with pytest.raises(TypeError): - seq_int_cp /= scalar - - # Bitwise operators - for op in ('__lshift__', '__rshift__', '__or__', '__and__', '__xor__'): - _test_binary(op, seq_bool, [42, -3, True, 0], [seq_int, seq_bool, -seq_int]) - - with pytest.raises(TypeError): - _test_binary(op, seq_bool, [0.5], []) - with pytest.raises(TypeError): - _test_binary(op, seq, [], [seq]) - - # Unary operators - for op in ['__neg__', '__abs__']: - _test_unary(op, seq) - _test_unary(op, -seq) - _test_unary(op, seq_int) - _test_unary(op, -seq_int) - - _test_unary('__abs__', seq_bool) - _test_unary('__invert__', seq_bool) - with pytest.raises(TypeError): - _test_unary('__invert__', seq) - - # Restore flags. - np.seterr(**flags) - - def test_arraysequence_repr(self): - # Test that calling repr on a ArraySequence object is not falling. - repr(SEQ_DATA['seq']) - - # Test calling repr when the number of arrays is bigger dans Numpy's - # print option threshold. - nb_arrays = 50 - seq = ArraySequence(generate_data(nb_arrays, common_shape=(1,), rng=SEQ_DATA['rng'])) - - bkp_threshold = np.get_printoptions()['threshold'] - np.set_printoptions(threshold=nb_arrays * 2) - txt1 = repr(seq) - np.set_printoptions(threshold=nb_arrays // 2) - txt2 = repr(seq) - assert len(txt2) < len(txt1) - np.set_printoptions(threshold=bkp_threshold) - - def test_save_and_load_arraysequence(self): - # Test saving and loading an empty ArraySequence. - with tempfile.TemporaryFile(mode='w+b', suffix='.npz') as f: - seq = ArraySequence() - seq.save(f) - f.seek(0, os.SEEK_SET) - loaded_seq = ArraySequence.load(f) - assert_array_equal(loaded_seq._data, seq._data) - assert_array_equal(loaded_seq._offsets, seq._offsets) - assert_array_equal(loaded_seq._lengths, seq._lengths) - - # Test saving and loading a ArraySequence. - with tempfile.TemporaryFile(mode='w+b', suffix='.npz') as f: - seq = SEQ_DATA['seq'] - seq.save(f) - f.seek(0, os.SEEK_SET) - loaded_seq = ArraySequence.load(f) - assert_array_equal(loaded_seq._data, seq._data) - assert_array_equal(loaded_seq._offsets, seq._offsets) - assert_array_equal(loaded_seq._lengths, seq._lengths) - - # Make sure we can add new elements to it. - loaded_seq.append(SEQ_DATA['data'][0]) - - def test_get_data(self): - seq_view = SEQ_DATA['seq'][::2] - check_arr_seq_view(seq_view, SEQ_DATA['seq']) - - # We make sure the array sequence data does not - # contain more elements than it is supposed to. - data = seq_view.get_data() - assert len(data) < len(seq_view._data) - - -def test_concatenate(): - seq = SEQ_DATA['seq'].copy() # In case there is in-place modification. - seqs = [seq[:, [i]] for i in range(seq.common_shape[0])] - new_seq = concatenate(seqs, axis=1) - seq._data += 100 # Modifying the 'seq' shouldn't change 'new_seq'. - check_arr_seq(new_seq, SEQ_DATA['data']) - assert new_seq._is_view is not True - - seq = SEQ_DATA['seq'] - seqs = [seq[:, [i]] for i in range(seq.common_shape[0])] - new_seq = concatenate(seqs, axis=0) - assert len(new_seq) == seq.common_shape[0] * len(seq) - assert_array_equal(new_seq._data, seq._data.T.reshape((-1, 1))) diff --git a/nibabel/streamlines/tests/test_streamlines.py b/nibabel/streamlines/tests/test_streamlines.py deleted file mode 100644 index 8811ddcfa0..0000000000 --- a/nibabel/streamlines/tests/test_streamlines.py +++ /dev/null @@ -1,294 +0,0 @@ -import os -import unittest -import warnings -from io import BytesIO -from os.path import join as pjoin - -import numpy as np -import pytest - -import nibabel as nib -from nibabel.testing import clear_and_catch_warnings, data_path, error_warnings -from nibabel.tmpdirs import InTemporaryDirectory - -from .. import FORMATS, trk -from ..tractogram import LazyTractogram, Tractogram -from ..tractogram_file import ExtensionWarning, TractogramFile -from .test_tractogram import assert_tractogram_equal - -DATA = {} - - -def setup_module(): - global DATA - DATA['empty_filenames'] = [pjoin(data_path, 'empty' + ext) for ext in FORMATS.keys()] - DATA['simple_filenames'] = [pjoin(data_path, 'simple' + ext) for ext in FORMATS.keys()] - DATA['complex_filenames'] = [ - pjoin(data_path, 'complex' + ext) - for ext, cls in FORMATS.items() - if (cls.SUPPORTS_DATA_PER_POINT or cls.SUPPORTS_DATA_PER_STREAMLINE) - ] - - DATA['streamlines'] = [ - np.arange(1 * 3, dtype='f4').reshape((1, 3)), - np.arange(2 * 3, dtype='f4').reshape((2, 3)), - np.arange(5 * 3, dtype='f4').reshape((5, 3)), - ] - - fa = [ - np.array([[0.2]], dtype='f4'), - np.array([[0.3], [0.4]], dtype='f4'), - np.array([[0.5], [0.6], [0.6], [0.7], [0.8]], dtype='f4'), - ] - - colors = [ - np.array([(1, 0, 0)] * 1, dtype='f4'), - np.array([(0, 1, 0)] * 2, dtype='f4'), - np.array([(0, 0, 1)] * 5, dtype='f4'), - ] - - mean_curvature = [ - np.array([1.11], dtype='f4'), - np.array([2.11], dtype='f4'), - np.array([3.11], dtype='f4'), - ] - - mean_torsion = [ - np.array([1.22], dtype='f4'), - np.array([2.22], dtype='f4'), - np.array([3.22], dtype='f4'), - ] - - mean_colors = [ - np.array([1, 0, 0], dtype='f4'), - np.array([0, 1, 0], dtype='f4'), - np.array([0, 0, 1], dtype='f4'), - ] - - DATA['data_per_point'] = {'colors': colors, 'fa': fa} - DATA['data_per_streamline'] = { - 'mean_curvature': mean_curvature, - 'mean_torsion': mean_torsion, - 'mean_colors': mean_colors, - } - - DATA['empty_tractogram'] = Tractogram(affine_to_rasmm=np.eye(4)) - DATA['simple_tractogram'] = Tractogram(DATA['streamlines'], affine_to_rasmm=np.eye(4)) - DATA['complex_tractogram'] = Tractogram( - DATA['streamlines'], - DATA['data_per_streamline'], - DATA['data_per_point'], - affine_to_rasmm=np.eye(4), - ) - - -def test_is_supported_detect_format(tmp_path): - # Test is_supported and detect_format functions - # Empty file/string - f = BytesIO() - assert not nib.streamlines.is_supported(f) - assert not nib.streamlines.is_supported('') - assert nib.streamlines.detect_format(f) is None - assert nib.streamlines.detect_format('') is None - - # Valid file without extension - for tfile_cls in FORMATS.values(): - f = BytesIO() - f.write(tfile_cls.MAGIC_NUMBER) - f.seek(0, os.SEEK_SET) - assert nib.streamlines.is_supported(f) - assert nib.streamlines.detect_format(f) is tfile_cls - - # Wrong extension but right magic number - for tfile_cls in FORMATS.values(): - fpath = tmp_path / 'test.txt' - with open(fpath, 'w+b') as f: - f.write(tfile_cls.MAGIC_NUMBER) - f.seek(0, os.SEEK_SET) - assert nib.streamlines.is_supported(f) - assert nib.streamlines.detect_format(f) is tfile_cls - - # Good extension but wrong magic number - for ext, tfile_cls in FORMATS.items(): - fpath = tmp_path / f'test{ext}' - with open(fpath, 'w+b') as f: - f.write(b'pass') - f.seek(0, os.SEEK_SET) - assert not nib.streamlines.is_supported(f) - assert nib.streamlines.detect_format(f) is None - - # Wrong extension, string only - f = 'my_tractogram.asd' - assert not nib.streamlines.is_supported(f) - assert nib.streamlines.detect_format(f) is None - - # Good extension, string only - for ext, tfile_cls in FORMATS.items(): - f = 'my_tractogram' + ext - assert nib.streamlines.is_supported(f) - assert nib.streamlines.detect_format(f) == tfile_cls - - # Extension should not be case-sensitive. - for ext, tfile_cls in FORMATS.items(): - f = 'my_tractogram' + ext.upper() - assert nib.streamlines.detect_format(f) is tfile_cls - - -class TestLoadSave(unittest.TestCase): - def test_load_empty_file(self): - for lazy_load in [False, True]: - for empty_filename in DATA['empty_filenames']: - tfile = nib.streamlines.load(empty_filename, lazy_load=lazy_load) - assert isinstance(tfile, TractogramFile) - - if lazy_load: - assert type(tfile.tractogram), Tractogram - else: - assert type(tfile.tractogram), LazyTractogram - - with pytest.warns(Warning) if lazy_load else error_warnings(): - assert_tractogram_equal(tfile.tractogram, DATA['empty_tractogram']) - - def test_load_simple_file(self): - for lazy_load in [False, True]: - for simple_filename in DATA['simple_filenames']: - tfile = nib.streamlines.load(simple_filename, lazy_load=lazy_load) - assert isinstance(tfile, TractogramFile) - - if lazy_load: - assert type(tfile.tractogram), Tractogram - else: - assert type(tfile.tractogram), LazyTractogram - - with pytest.warns(Warning) if lazy_load else error_warnings(): - assert_tractogram_equal(tfile.tractogram, DATA['simple_tractogram']) - - def test_load_complex_file(self): - for lazy_load in [False, True]: - for complex_filename in DATA['complex_filenames']: - tfile = nib.streamlines.load(complex_filename, lazy_load=lazy_load) - assert isinstance(tfile, TractogramFile) - - if lazy_load: - assert type(tfile.tractogram), Tractogram - else: - assert type(tfile.tractogram), LazyTractogram - - tractogram = Tractogram(DATA['streamlines'], affine_to_rasmm=np.eye(4)) - - if tfile.SUPPORTS_DATA_PER_POINT: - tractogram.data_per_point = DATA['data_per_point'] - - if tfile.SUPPORTS_DATA_PER_STREAMLINE: - data = DATA['data_per_streamline'] - tractogram.data_per_streamline = data - - with pytest.warns(Warning) if lazy_load else error_warnings(): - assert_tractogram_equal(tfile.tractogram, tractogram) - - def test_save_tractogram_file(self): - tractogram = Tractogram(DATA['streamlines'], affine_to_rasmm=np.eye(4)) - trk_file = trk.TrkFile(tractogram) - - # No need for keyword arguments. - with pytest.raises(ValueError): - nib.streamlines.save(trk_file, 'dummy.trk', header={}) - - # Wrong extension. - with pytest.warns(ExtensionWarning, match='extension'): - trk_file = trk.TrkFile(tractogram) - with pytest.raises(ValueError): - nib.streamlines.save(trk_file, 'dummy.tck', header={}) - - with InTemporaryDirectory(): - nib.streamlines.save(trk_file, 'dummy.trk') - tfile = nib.streamlines.load('dummy.trk', lazy_load=False) - assert_tractogram_equal(tfile.tractogram, tractogram) - - def test_save_empty_file(self): - tractogram = Tractogram(affine_to_rasmm=np.eye(4)) - for ext in FORMATS: - with InTemporaryDirectory(): - filename = 'streamlines' + ext - nib.streamlines.save(tractogram, filename) - tfile = nib.streamlines.load(filename, lazy_load=False) - assert_tractogram_equal(tfile.tractogram, tractogram) - - def test_save_simple_file(self): - tractogram = Tractogram(DATA['streamlines'], affine_to_rasmm=np.eye(4)) - for ext in FORMATS: - with InTemporaryDirectory(): - filename = 'streamlines' + ext - nib.streamlines.save(tractogram, filename) - tfile = nib.streamlines.load(filename, lazy_load=False) - assert_tractogram_equal(tfile.tractogram, tractogram) - - def test_save_complex_file(self): - complex_tractogram = Tractogram( - DATA['streamlines'], - DATA['data_per_streamline'], - DATA['data_per_point'], - affine_to_rasmm=np.eye(4), - ) - - for ext, cls in FORMATS.items(): - with InTemporaryDirectory(): - filename = 'streamlines' + ext - - # If streamlines format does not support saving data - # per point or data per streamline, warning messages - # should be issued. - nb_expected_warnings = (not cls.SUPPORTS_DATA_PER_POINT) + ( - not cls.SUPPORTS_DATA_PER_STREAMLINE - ) - - with clear_and_catch_warnings() as w: - warnings.simplefilter('always') - nib.streamlines.save(complex_tractogram, filename) - assert len(w) == nb_expected_warnings - - tractogram = Tractogram(DATA['streamlines'], affine_to_rasmm=np.eye(4)) - - if cls.SUPPORTS_DATA_PER_POINT: - tractogram.data_per_point = DATA['data_per_point'] - - if cls.SUPPORTS_DATA_PER_STREAMLINE: - data = DATA['data_per_streamline'] - tractogram.data_per_streamline = data - - tfile = nib.streamlines.load(filename, lazy_load=False) - assert_tractogram_equal(tfile.tractogram, tractogram) - - def test_save_sliced_tractogram(self): - tractogram = Tractogram(DATA['streamlines'], affine_to_rasmm=np.eye(4)) - original_tractogram = tractogram.copy() - for ext in FORMATS: - with InTemporaryDirectory(): - filename = 'streamlines' + ext - nib.streamlines.save(tractogram[::2], filename) - tfile = nib.streamlines.load(filename, lazy_load=False) - assert_tractogram_equal(tfile.tractogram, tractogram[::2]) - # Make sure original tractogram hasn't changed. - assert_tractogram_equal(tractogram, original_tractogram) - - def test_load_unknown_format(self): - with pytest.raises(ValueError): - nib.streamlines.load('') - - def test_save_unknown_format(self): - with pytest.raises(ValueError): - nib.streamlines.save(Tractogram(), '') - - def test_save_from_generator(self): - tractogram = Tractogram(DATA['streamlines'], affine_to_rasmm=np.eye(4)) - - # Just to create a generator - for ext in FORMATS: - filtered = (s for s in tractogram.streamlines if True) - lazy_tractogram = LazyTractogram(lambda: filtered, affine_to_rasmm=np.eye(4)) - - with InTemporaryDirectory(): - filename = 'streamlines' + ext - nib.streamlines.save(lazy_tractogram, filename) - tfile = nib.streamlines.load(filename, lazy_load=False) - assert_tractogram_equal(tfile.tractogram, tractogram) diff --git a/nibabel/streamlines/tests/test_tck.py b/nibabel/streamlines/tests/test_tck.py deleted file mode 100644 index 083ab8e6e9..0000000000 --- a/nibabel/streamlines/tests/test_tck.py +++ /dev/null @@ -1,264 +0,0 @@ -import os -import unittest -from io import BytesIO -from os.path import join as pjoin - -import numpy as np -import pytest -from numpy.testing import assert_array_equal - -from ...testing import data_path, error_warnings -from ..array_sequence import ArraySequence -from ..tck import TckFile -from ..tractogram import Tractogram -from ..tractogram_file import DataError, HeaderError, HeaderWarning -from .test_tractogram import assert_tractogram_equal - -DATA = {} - - -def setup_module(): - global DATA - - DATA['empty_tck_fname'] = pjoin(data_path, 'empty.tck') - DATA['no_magic_number_tck_fname'] = pjoin(data_path, 'no_magic_number.tck') - DATA['no_header_end_tck_fname'] = pjoin(data_path, 'no_header_end.tck') - DATA['no_header_end_eof_tck_fname'] = pjoin(data_path, 'no_header_end_eof.tck') - # simple.tck contains only streamlines - DATA['simple_tck_fname'] = pjoin(data_path, 'simple.tck') - DATA['simple_tck_big_endian_fname'] = pjoin(data_path, 'simple_big_endian.tck') - # standard.tck contains only streamlines - DATA['standard_tck_fname'] = pjoin(data_path, 'standard.tck') - DATA['matlab_nan_tck_fname'] = pjoin(data_path, 'matlab_nan.tck') - DATA['multiline_header_fname'] = pjoin(data_path, 'multiline_header_field.tck') - - DATA['streamlines'] = [ - np.arange(1 * 3, dtype='f4').reshape((1, 3)), - np.arange(2 * 3, dtype='f4').reshape((2, 3)), - np.arange(5 * 3, dtype='f4').reshape((5, 3)), - ] - - DATA['empty_tractogram'] = Tractogram(affine_to_rasmm=np.eye(4)) - DATA['simple_tractogram'] = Tractogram(DATA['streamlines'], affine_to_rasmm=np.eye(4)) - - -class TestTCK(unittest.TestCase): - def test_load_empty_file(self): - for lazy_load in [False, True]: - tck = TckFile.load(DATA['empty_tck_fname'], lazy_load=lazy_load) - with pytest.warns(Warning) if lazy_load else error_warnings(): - assert_tractogram_equal(tck.tractogram, DATA['empty_tractogram']) - - def test_load_no_magic_number_file(self): - for lazy_load in [False, True]: - with pytest.raises(HeaderError): - TckFile.load(DATA['no_magic_number_tck_fname'], lazy_load=lazy_load) - - def test_load_no_header_end_file(self): - for lazy_load in [False, True]: - with pytest.raises(HeaderError): - TckFile.load(DATA['no_header_end_tck_fname'], lazy_load=lazy_load) - - def test_load_no_header_end_eof_file(self): - for lazy_load in [False, True]: - with pytest.raises(HeaderError): - TckFile.load(DATA['no_header_end_eof_tck_fname'], lazy_load=lazy_load) - - def test_load_simple_file(self): - for lazy_load in [False, True]: - tck = TckFile.load(DATA['simple_tck_fname'], lazy_load=lazy_load) - with pytest.warns(Warning) if lazy_load else error_warnings(): - assert_tractogram_equal(tck.tractogram, DATA['simple_tractogram']) - - # Force TCK loading to use buffering. - buffer_size = 1.0 / 1024**2 # 1 bytes - hdr = TckFile._read_header(DATA['simple_tck_fname']) - tck_reader = TckFile._read(DATA['simple_tck_fname'], hdr, buffer_size) - streamlines = ArraySequence(tck_reader) - tractogram = Tractogram(streamlines) - tractogram.affine_to_rasmm = np.eye(4) - tck = TckFile(tractogram, header=hdr) - assert_tractogram_equal(tck.tractogram, DATA['simple_tractogram']) - - def test_load_matlab_nan_file(self): - for lazy_load in [False, True]: - tck = TckFile.load(DATA['matlab_nan_tck_fname'], lazy_load=lazy_load) - streamlines = list(tck.tractogram.streamlines) - assert len(streamlines) == 1 - assert streamlines[0].shape == (108, 3) - - def test_load_multiline_header_file(self): - for lazy_load in [False, True]: - tck = TckFile.load(DATA['multiline_header_fname'], lazy_load=lazy_load) - streamlines = list(tck.tractogram.streamlines) - assert len(tck.header['command_history'].splitlines()) == 3 - assert len(streamlines) == 1 - assert streamlines[0].shape == (253, 3) - - def test_writeable_data(self): - data = DATA['simple_tractogram'] - for key in ('simple_tck_fname', 'simple_tck_big_endian_fname'): - for lazy_load in [False, True]: - tck = TckFile.load(DATA[key], lazy_load=lazy_load) - for actual, expected_tgi in zip(tck.streamlines, data): - assert_array_equal(actual, expected_tgi.streamline) - # Test we can write to arrays - assert actual.flags.writeable - actual[0, 0] = 99 - - def test_load_simple_file_in_big_endian(self): - for lazy_load in [False, True]: - tck = TckFile.load(DATA['simple_tck_big_endian_fname'], lazy_load=lazy_load) - with pytest.warns(Warning) if lazy_load else error_warnings(): - assert_tractogram_equal(tck.tractogram, DATA['simple_tractogram']) - assert tck.header['datatype'] == 'Float32BE' - - def test_load_file_with_wrong_information(self): - tck_file = open(DATA['simple_tck_fname'], 'rb').read() - - # Simulate a TCK file where `datatype` has not the right endianness. - new_tck_file = tck_file.replace(b'Float32LE', b'Float32BE') - - with pytest.raises(DataError): - TckFile.load(BytesIO(new_tck_file)) - - # Simulate a TCK file with unsupported `datatype`. - new_tck_file = tck_file.replace(b'Float32LE', b'int32') - with pytest.raises(HeaderError): - TckFile.load(BytesIO(new_tck_file)) - - # Simulate a TCK file with no `datatype` field. - new_tck_file = tck_file.replace(b'datatype: Float32LE\n', b'') - # Need to adjust data offset. - new_tck_file = new_tck_file.replace(b'file: . 67\n', b'file: . 47\n') - with pytest.warns(HeaderWarning, match="Missing 'datatype'"): - tck = TckFile.load(BytesIO(new_tck_file)) - assert_array_equal(tck.header['datatype'], 'Float32LE') - - # Simulate a TCK file with no `file` field. - new_tck_file = tck_file.replace(b'\nfile: . 67', b'') - with pytest.warns(HeaderWarning, match="Missing 'file'"): - tck = TckFile.load(BytesIO(new_tck_file)) - assert_array_equal(tck.header['file'], '. 56') - - # Simulate a TCK file with `file` field pointing to another file. - new_tck_file = tck_file.replace(b'file: . 67\n', b'file: dummy.mat 75\n') - with pytest.raises(HeaderError): - TckFile.load(BytesIO(new_tck_file)) - - # Simulate a TCK file which is missing a streamline delimiter. - eos = TckFile.FIBER_DELIMITER.tobytes() - eof = TckFile.EOF_DELIMITER.tobytes() - new_tck_file = tck_file[: -(len(eos) + len(eof))] + tck_file[-len(eof) :] - - # Force TCK loading to use buffering. - buffer_size = 1.0 / 1024**2 # 1 bytes - hdr = TckFile._read_header(BytesIO(new_tck_file)) - tck_reader = TckFile._read(BytesIO(new_tck_file), hdr, buffer_size) - with pytest.raises(DataError): - list(tck_reader) - - # Simulate a TCK file which is missing the end-of-file delimiter. - new_tck_file = tck_file[: -len(eof)] - with pytest.raises(DataError): - TckFile.load(BytesIO(new_tck_file)) - - def test_write_empty_file(self): - tractogram = Tractogram(affine_to_rasmm=np.eye(4)) - - tck_file = BytesIO() - tck = TckFile(tractogram) - tck.save(tck_file) - tck_file.seek(0, os.SEEK_SET) - - new_tck = TckFile.load(tck_file) - assert_tractogram_equal(new_tck.tractogram, tractogram) - - new_tck_orig = TckFile.load(DATA['empty_tck_fname']) - assert_tractogram_equal(new_tck.tractogram, new_tck_orig.tractogram) - - tck_file.seek(0, os.SEEK_SET) - assert tck_file.read() == open(DATA['empty_tck_fname'], 'rb').read() - - def test_write_simple_file(self): - tractogram = Tractogram(DATA['streamlines'], affine_to_rasmm=np.eye(4)) - - tck_file = BytesIO() - tck = TckFile(tractogram) - tck.save(tck_file) - tck_file.seek(0, os.SEEK_SET) - - new_tck = TckFile.load(tck_file) - assert_tractogram_equal(new_tck.tractogram, tractogram) - - new_tck_orig = TckFile.load(DATA['simple_tck_fname']) - assert_tractogram_equal(new_tck.tractogram, new_tck_orig.tractogram) - - tck_file.seek(0, os.SEEK_SET) - assert tck_file.read() == open(DATA['simple_tck_fname'], 'rb').read() - - # TCK file containing not well formatted entries in its header. - tck_file = BytesIO() - tck = TckFile(tractogram) - tck.header['new_entry'] = 'val:ue' # : not allowed - with pytest.raises(HeaderError): - tck.save(tck_file) - - def test_write_bigheader_file(self): - tractogram = Tractogram(DATA['streamlines'], affine_to_rasmm=np.eye(4)) - - # Offset is represented by 2 characters. - tck_file = BytesIO() - tck = TckFile(tractogram) - tck.header['new_entry'] = ' ' * 20 - tck.save(tck_file) - tck_file.seek(0, os.SEEK_SET) - - new_tck = TckFile.load(tck_file) - assert_tractogram_equal(new_tck.tractogram, tractogram) - assert new_tck.header['_offset_data'] == 99 - - # We made the jump, now offset is represented by 3 characters - # and we need to adjust the offset! - tck_file = BytesIO() - tck = TckFile(tractogram) - tck.header['new_entry'] = ' ' * 21 - tck.save(tck_file) - tck_file.seek(0, os.SEEK_SET) - - new_tck = TckFile.load(tck_file) - assert_tractogram_equal(new_tck.tractogram, tractogram) - assert new_tck.header['_offset_data'] == 101 - - def test_load_write_file(self): - for fname in [DATA['empty_tck_fname'], DATA['simple_tck_fname']]: - for lazy_load in [False, True]: - tck = TckFile.load(fname, lazy_load=lazy_load) - tck_file = BytesIO() - tck.save(tck_file) - - loaded_tck = TckFile.load(fname, lazy_load=False) - assert_tractogram_equal(loaded_tck.tractogram, tck.tractogram) - - # Check that the written file is the same as the one read. - tck_file.seek(0, os.SEEK_SET) - assert tck_file.read() == open(fname, 'rb').read() - - # Save tractogram that has an affine_to_rasmm. - for lazy_load in [False, True]: - tck = TckFile.load(DATA['simple_tck_fname'], lazy_load=lazy_load) - affine = np.eye(4) - affine[0, 0] *= -1 # Flip in X - tractogram = Tractogram(tck.streamlines, affine_to_rasmm=affine) - - new_tck = TckFile(tractogram, tck.header) - tck_file = BytesIO() - new_tck.save(tck_file) - tck_file.seek(0, os.SEEK_SET) - - loaded_tck = TckFile.load(tck_file, lazy_load=False) - assert_tractogram_equal(loaded_tck.tractogram, tractogram.to_world(lazy=True)) - - def test_str(self): - tck = TckFile.load(DATA['simple_tck_fname']) - str(tck) # Simply test it's not failing when called. diff --git a/nibabel/streamlines/tests/test_tractogram.py b/nibabel/streamlines/tests/test_tractogram.py deleted file mode 100644 index 72b84fac6e..0000000000 --- a/nibabel/streamlines/tests/test_tractogram.py +++ /dev/null @@ -1,1074 +0,0 @@ -import copy -import operator -import unittest -import warnings -from collections import defaultdict - -import numpy as np -import pytest -from numpy.testing import assert_array_almost_equal, assert_array_equal - -from ...testing import assert_arrays_equal, clear_and_catch_warnings -from .. import tractogram as module_tractogram -from ..tractogram import ( - LazyDict, - LazyTractogram, - PerArrayDict, - PerArraySequenceDict, - Tractogram, - TractogramItem, - is_data_dict, - is_lazy_dict, -) - -DATA = {} - - -def make_fake_streamline( - nb_points, data_per_point_shapes={}, data_for_streamline_shapes={}, rng=None -): - """Make a single streamline according to provided requirements.""" - if rng is None: - rng = np.random.RandomState() - - streamline = rng.randn(nb_points, 3).astype('f4') - - data_per_point = {} - for k, shape in data_per_point_shapes.items(): - data_per_point[k] = rng.randn(*((nb_points,) + shape)).astype('f4') - - data_for_streamline = {} - for k, shape in data_for_streamline.items(): - data_for_streamline[k] = rng.randn(*shape).astype('f4') - - return streamline, data_per_point, data_for_streamline - - -def make_fake_tractogram( - list_nb_points, data_per_point_shapes={}, data_for_streamline_shapes={}, rng=None -): - """Make multiple streamlines according to provided requirements.""" - all_streamlines = [] - all_data_per_point = defaultdict(list) - all_data_per_streamline = defaultdict(list) - for nb_points in list_nb_points: - data = make_fake_streamline( - nb_points, data_per_point_shapes, data_for_streamline_shapes, rng - ) - streamline, data_per_point, data_for_streamline = data - - all_streamlines.append(streamline) - for k, v in data_per_point.items(): - all_data_per_point[k].append(v) - - for k, v in data_for_streamline.items(): - all_data_per_streamline[k].append(v) - - return all_streamlines, all_data_per_point, all_data_per_streamline - - -def make_dummy_streamline(nb_points): - """Make the streamlines that have been used to create test data files.""" - if nb_points == 1: - streamline = np.arange(1 * 3, dtype='f4').reshape((1, 3)) - data_per_point = { - 'fa': np.array([[0.2]], dtype='f4'), - 'colors': np.array([(1, 0, 0)] * 1, dtype='f4'), - } - data_for_streamline = { - 'mean_curvature': np.array([1.11], dtype='f4'), - 'mean_torsion': np.array([1.22], dtype='f4'), - 'mean_colors': np.array([1, 0, 0], dtype='f4'), - 'clusters_labels': np.array([0, 1], dtype='i4'), - } - - elif nb_points == 2: - streamline = np.arange(2 * 3, dtype='f4').reshape((2, 3)) - data_per_point = { - 'fa': np.array([[0.3], [0.4]], dtype='f4'), - 'colors': np.array([(0, 1, 0)] * 2, dtype='f4'), - } - data_for_streamline = { - 'mean_curvature': np.array([2.11], dtype='f4'), - 'mean_torsion': np.array([2.22], dtype='f4'), - 'mean_colors': np.array([0, 1, 0], dtype='f4'), - 'clusters_labels': np.array([2, 3, 4], dtype='i4'), - } - - elif nb_points == 5: - streamline = np.arange(5 * 3, dtype='f4').reshape((5, 3)) - data_per_point = { - 'fa': np.array([[0.5], [0.6], [0.6], [0.7], [0.8]], dtype='f4'), - 'colors': np.array([(0, 0, 1)] * 5, dtype='f4'), - } - data_for_streamline = { - 'mean_curvature': np.array([3.11], dtype='f4'), - 'mean_torsion': np.array([3.22], dtype='f4'), - 'mean_colors': np.array([0, 0, 1], dtype='f4'), - 'clusters_labels': np.array([5, 6, 7, 8], dtype='i4'), - } - - return streamline, data_per_point, data_for_streamline - - -def setup_module(): - global DATA - DATA['rng'] = np.random.RandomState(1234) - - DATA['streamlines'] = [] - DATA['fa'] = [] - DATA['colors'] = [] - DATA['mean_curvature'] = [] - DATA['mean_torsion'] = [] - DATA['mean_colors'] = [] - DATA['clusters_labels'] = [] - for nb_points in [1, 2, 5]: - data = make_dummy_streamline(nb_points) - streamline, data_per_point, data_for_streamline = data - DATA['streamlines'].append(streamline) - DATA['fa'].append(data_per_point['fa']) - DATA['colors'].append(data_per_point['colors']) - DATA['mean_curvature'].append(data_for_streamline['mean_curvature']) - DATA['mean_torsion'].append(data_for_streamline['mean_torsion']) - DATA['mean_colors'].append(data_for_streamline['mean_colors']) - DATA['clusters_labels'].append(data_for_streamline['clusters_labels']) - - DATA['data_per_point'] = {'colors': DATA['colors'], 'fa': DATA['fa']} - DATA['data_per_streamline'] = { - 'mean_curvature': DATA['mean_curvature'], - 'mean_torsion': DATA['mean_torsion'], - 'mean_colors': DATA['mean_colors'], - 'clusters_labels': DATA['clusters_labels'], - } - - DATA['empty_tractogram'] = Tractogram(affine_to_rasmm=np.eye(4)) - DATA['simple_tractogram'] = Tractogram(DATA['streamlines'], affine_to_rasmm=np.eye(4)) - DATA['tractogram'] = Tractogram( - DATA['streamlines'], - DATA['data_per_streamline'], - DATA['data_per_point'], - affine_to_rasmm=np.eye(4), - ) - - DATA['streamlines_func'] = lambda: (e for e in DATA['streamlines']) - DATA['data_per_point_func'] = { - 'colors': lambda: (e for e in DATA['colors']), - 'fa': lambda: (e for e in DATA['fa']), - } - DATA['data_per_streamline_func'] = { - 'mean_curvature': lambda: (e for e in DATA['mean_curvature']), - 'mean_torsion': lambda: (e for e in DATA['mean_torsion']), - 'mean_colors': lambda: (e for e in DATA['mean_colors']), - 'clusters_labels': lambda: (e for e in DATA['clusters_labels']), - } - - DATA['lazy_tractogram'] = LazyTractogram( - DATA['streamlines_func'], - DATA['data_per_streamline_func'], - DATA['data_per_point_func'], - affine_to_rasmm=np.eye(4), - ) - - -def check_tractogram_item(tractogram_item, streamline, data_for_streamline={}, data_for_points={}): - assert_array_equal(tractogram_item.streamline, streamline) - - assert len(tractogram_item.data_for_streamline) == len(data_for_streamline) - for key in data_for_streamline.keys(): - assert_array_equal(tractogram_item.data_for_streamline[key], data_for_streamline[key]) - - assert len(tractogram_item.data_for_points) == len(data_for_points) - for key in data_for_points.keys(): - assert_arrays_equal(tractogram_item.data_for_points[key], data_for_points[key]) - - -def assert_tractogram_item_equal(t1, t2): - check_tractogram_item(t1, t2.streamline, t2.data_for_streamline, t2.data_for_points) - - -def check_tractogram(tractogram, streamlines=[], data_per_streamline={}, data_per_point={}): - streamlines = list(streamlines) - assert len(tractogram) == len(streamlines) - assert_arrays_equal(tractogram.streamlines, streamlines) - [t for t in tractogram] # Force iteration through tractogram. - - assert len(tractogram.data_per_streamline) == len(data_per_streamline) - for key in data_per_streamline.keys(): - assert_arrays_equal(tractogram.data_per_streamline[key], data_per_streamline[key]) - - assert len(tractogram.data_per_point) == len(data_per_point) - for key in data_per_point.keys(): - assert_arrays_equal(tractogram.data_per_point[key], data_per_point[key]) - - -def assert_tractogram_equal(t1, t2): - check_tractogram(t1, t2.streamlines, t2.data_per_streamline, t2.data_per_point) - - -def extender(a, b): - a.extend(b) - return a - - -class TestPerArrayDict(unittest.TestCase): - def test_per_array_dict_creation(self): - # Create a PerArrayDict object using another - # PerArrayDict object. - nb_streamlines = len(DATA['tractogram']) - data_per_streamline = DATA['tractogram'].data_per_streamline - data_dict = PerArrayDict(nb_streamlines, data_per_streamline) - assert data_dict.keys() == data_per_streamline.keys() - for k in data_dict.keys(): - if isinstance(data_dict[k], np.ndarray) and np.all( - data_dict[k].shape[0] == data_dict[k].shape - ): - assert_array_equal(data_dict[k], data_per_streamline[k]) - - del data_dict['mean_curvature'] - assert len(data_dict) == len(data_per_streamline) - 1 - - # Create a PerArrayDict object using an existing dict object. - data_per_streamline = DATA['data_per_streamline'] - data_dict = PerArrayDict(nb_streamlines, data_per_streamline) - assert data_dict.keys() == data_per_streamline.keys() - for k in data_dict.keys(): - if isinstance(data_dict[k], np.ndarray) and np.all( - data_dict[k].shape[0] == data_dict[k].shape - ): - assert_array_equal(data_dict[k], data_per_streamline[k]) - - del data_dict['mean_curvature'] - assert len(data_dict) == len(data_per_streamline) - 1 - - # Create a PerArrayDict object using keyword arguments. - data_per_streamline = DATA['data_per_streamline'] - data_dict = PerArrayDict(nb_streamlines, **data_per_streamline) - assert data_dict.keys() == data_per_streamline.keys() - for k in data_dict.keys(): - if isinstance(data_dict[k], np.ndarray) and np.all( - data_dict[k].shape[0] == data_dict[k].shape - ): - assert_array_equal(data_dict[k], data_per_streamline[k]) - - del data_dict['mean_curvature'] - assert len(data_dict) == len(data_per_streamline) - 1 - - def test_getitem(self): - sdict = PerArrayDict(len(DATA['tractogram']), DATA['data_per_streamline']) - - with pytest.raises(KeyError): - sdict['invalid'] - - # Test slicing and advanced indexing. - for k, v in DATA['tractogram'].data_per_streamline.items(): - assert k in sdict - assert_arrays_equal(sdict[k], v) - assert_arrays_equal(sdict[::2][k], v[::2]) - assert_arrays_equal(sdict[::-1][k], v[::-1]) - assert_arrays_equal(sdict[-1][k], v[-1]) - assert_arrays_equal(sdict[[0, -1]][k], v[[0, -1]]) - - def test_extend(self): - sdict = PerArrayDict(len(DATA['tractogram']), DATA['data_per_streamline']) - - new_data = { - 'mean_curvature': 2 * np.array(DATA['mean_curvature']), - 'mean_torsion': 3 * np.array(DATA['mean_torsion']), - 'mean_colors': 4 * np.array(DATA['mean_colors']), - 'clusters_labels': 5 * np.array(DATA['clusters_labels'], dtype=object), - } - sdict2 = PerArrayDict(len(DATA['tractogram']), new_data) - - sdict.extend(sdict2) - assert len(sdict) == len(sdict2) - for k in DATA['tractogram'].data_per_streamline: - assert_arrays_equal( - sdict[k][: len(DATA['tractogram'])], DATA['tractogram'].data_per_streamline[k] - ) - assert_arrays_equal(sdict[k][len(DATA['tractogram']) :], new_data[k]) - - # Extending with an empty PerArrayDict should change nothing. - sdict_orig = copy.deepcopy(sdict) - sdict.extend(PerArrayDict()) - for k in sdict_orig.keys(): - assert_arrays_equal(sdict[k], sdict_orig[k]) - - # Test incompatible PerArrayDicts. - # Other dict has more entries. - new_data = { - 'mean_curvature': 2 * np.array(DATA['mean_curvature']), - 'mean_torsion': 3 * np.array(DATA['mean_torsion']), - 'mean_colors': 4 * np.array(DATA['mean_colors']), - 'clusters_labels': 5 * np.array(DATA['clusters_labels'], dtype=object), - 'other': 6 * np.array(DATA['mean_colors']), - } - sdict2 = PerArrayDict(len(DATA['tractogram']), new_data) - - with pytest.raises(ValueError): - sdict.extend(sdict2) - # Other dict has not the same entries (key mistmached). - new_data = { - 'mean_curvature': 2 * np.array(DATA['mean_curvature']), - 'mean_torsion': 3 * np.array(DATA['mean_torsion']), - 'other': 4 * np.array(DATA['mean_colors']), - } - sdict2 = PerArrayDict(len(DATA['tractogram']), new_data) - with pytest.raises(ValueError): - sdict.extend(sdict2) - - # Other dict has the right number of entries but wrong shape. - new_data = { - 'mean_curvature': 2 * np.array(DATA['mean_curvature']), - 'mean_torsion': 3 * np.array(DATA['mean_torsion']), - 'mean_colors': 4 * np.array(DATA['mean_torsion']), - 'clusters_labels': 5 * np.array(DATA['clusters_labels'], dtype=object), - } - sdict2 = PerArrayDict(len(DATA['tractogram']), new_data) - with pytest.raises(ValueError): - sdict.extend(sdict2) - - -class TestPerArraySequenceDict(unittest.TestCase): - def test_per_array_sequence_dict_creation(self): - # Create a PerArraySequenceDict object using another - # PerArraySequenceDict object. - total_nb_rows = DATA['tractogram'].streamlines.total_nb_rows - data_per_point = DATA['tractogram'].data_per_point - data_dict = PerArraySequenceDict(total_nb_rows, data_per_point) - assert data_dict.keys() == data_per_point.keys() - for k in data_dict.keys(): - assert_arrays_equal(data_dict[k], data_per_point[k]) - - del data_dict['fa'] - assert len(data_dict) == len(data_per_point) - 1 - - # Create a PerArraySequenceDict object using an existing dict object. - data_per_point = DATA['data_per_point'] - data_dict = PerArraySequenceDict(total_nb_rows, data_per_point) - assert data_dict.keys() == data_per_point.keys() - for k in data_dict.keys(): - assert_arrays_equal(data_dict[k], data_per_point[k]) - - del data_dict['fa'] - assert len(data_dict) == len(data_per_point) - 1 - - # Create a PerArraySequenceDict object using keyword arguments. - data_per_point = DATA['data_per_point'] - data_dict = PerArraySequenceDict(total_nb_rows, **data_per_point) - assert data_dict.keys() == data_per_point.keys() - for k in data_dict.keys(): - assert_arrays_equal(data_dict[k], data_per_point[k]) - - del data_dict['fa'] - assert len(data_dict) == len(data_per_point) - 1 - - def test_getitem(self): - total_nb_rows = DATA['tractogram'].streamlines.total_nb_rows - sdict = PerArraySequenceDict(total_nb_rows, DATA['data_per_point']) - - with pytest.raises(KeyError): - sdict['invalid'] - - # Test slicing and advanced indexing. - for k, v in DATA['tractogram'].data_per_point.items(): - assert k in sdict - assert_arrays_equal(sdict[k], v) - assert_arrays_equal(sdict[::2][k], v[::2]) - assert_arrays_equal(sdict[::-1][k], v[::-1]) - assert_arrays_equal(sdict[-1][k], v[-1]) - assert_arrays_equal(sdict[[0, -1]][k], v[[0, -1]]) - - def test_extend(self): - total_nb_rows = DATA['tractogram'].streamlines.total_nb_rows - sdict = PerArraySequenceDict(total_nb_rows, DATA['data_per_point']) - - # Test compatible PerArraySequenceDicts. - list_nb_points = [2, 7, 4] - data_per_point_shapes = { - 'colors': DATA['colors'][0].shape[1:], - 'fa': DATA['fa'][0].shape[1:], - } - _, new_data, _ = make_fake_tractogram( - list_nb_points, data_per_point_shapes, rng=DATA['rng'] - ) - sdict2 = PerArraySequenceDict(np.sum(list_nb_points), new_data) - - sdict.extend(sdict2) - assert len(sdict) == len(sdict2) - for k in DATA['tractogram'].data_per_point: - assert_arrays_equal( - sdict[k][: len(DATA['tractogram'])], DATA['tractogram'].data_per_point[k] - ) - assert_arrays_equal(sdict[k][len(DATA['tractogram']) :], new_data[k]) - - # Extending with an empty PerArraySequenceDicts should change nothing. - sdict_orig = copy.deepcopy(sdict) - sdict.extend(PerArraySequenceDict()) - for k in sdict_orig.keys(): - assert_arrays_equal(sdict[k], sdict_orig[k]) - - # Test incompatible PerArraySequenceDicts. - # Other dict has more entries. - data_per_point_shapes = { - 'colors': DATA['colors'][0].shape[1:], - 'fa': DATA['fa'][0].shape[1:], - 'other': (7,), - } - _, new_data, _ = make_fake_tractogram( - list_nb_points, data_per_point_shapes, rng=DATA['rng'] - ) - sdict2 = PerArraySequenceDict(np.sum(list_nb_points), new_data) - with pytest.raises(ValueError): - sdict.extend(sdict2) - - # Other dict has not the same entries (key mistmached). - data_per_point_shapes = { - 'colors': DATA['colors'][0].shape[1:], - 'other': DATA['fa'][0].shape[1:], - } - _, new_data, _ = make_fake_tractogram( - list_nb_points, data_per_point_shapes, rng=DATA['rng'] - ) - sdict2 = PerArraySequenceDict(np.sum(list_nb_points), new_data) - with pytest.raises(ValueError): - sdict.extend(sdict2) - - # Other dict has the right number of entries but wrong shape. - data_per_point_shapes = { - 'colors': DATA['colors'][0].shape[1:], - 'fa': DATA['fa'][0].shape[1:] + (3,), - } - _, new_data, _ = make_fake_tractogram( - list_nb_points, data_per_point_shapes, rng=DATA['rng'] - ) - sdict2 = PerArraySequenceDict(np.sum(list_nb_points), new_data) - with pytest.raises(ValueError): - sdict.extend(sdict2) - - -class TestLazyDict(unittest.TestCase): - def test_lazydict_creation(self): - # Different ways of creating LazyDict - lazy_dicts = [] - lazy_dicts += [LazyDict(DATA['data_per_streamline_func'])] - lazy_dicts += [LazyDict(**DATA['data_per_streamline_func'])] - - expected_keys = DATA['data_per_streamline_func'].keys() - for data_dict in lazy_dicts: - assert is_lazy_dict(data_dict) - assert data_dict.keys() == expected_keys - for k in data_dict.keys(): - if isinstance(data_dict[k], np.ndarray) and np.all( - data_dict[k].shape[0] == data_dict[k].shape - ): - assert_array_equal(list(data_dict[k]), list(DATA['data_per_streamline'][k])) - - assert len(data_dict) == len(DATA['data_per_streamline_func']) - - -class TestTractogramItem(unittest.TestCase): - def test_creating_tractogram_item(self): - rng = np.random.RandomState(42) - streamline = rng.rand(rng.randint(10, 50), 3) - colors = rng.rand(len(streamline), 3) - mean_curvature = 1.11 - mean_color = np.array([0, 1, 0], dtype='f4') - - data_for_streamline = {'mean_curvature': mean_curvature, 'mean_color': mean_color} - - data_for_points = {'colors': colors} - - # Create a tractogram item with a streamline, data. - t = TractogramItem(streamline, data_for_streamline, data_for_points) - assert len(t) == len(streamline) - assert_array_equal(t.streamline, streamline) - assert_array_equal(list(t), streamline) - assert_array_equal(t.data_for_streamline['mean_curvature'], mean_curvature) - assert_array_equal(t.data_for_streamline['mean_color'], mean_color) - assert_array_equal(t.data_for_points['colors'], colors) - - -class TestTractogram(unittest.TestCase): - def test_tractogram_creation(self): - # Create an empty tractogram. - tractogram = Tractogram() - check_tractogram(tractogram) - assert tractogram.affine_to_rasmm is None - - # Create a tractogram with only streamlines - tractogram = Tractogram(streamlines=DATA['streamlines']) - check_tractogram(tractogram, DATA['streamlines']) - - # Create a tractogram with a given affine_to_rasmm. - affine = np.diag([1, 2, 3, 1]) - tractogram = Tractogram(affine_to_rasmm=affine) - assert_array_equal(tractogram.affine_to_rasmm, affine) - - # Create a tractogram with streamlines and other data. - tractogram = Tractogram( - DATA['streamlines'], DATA['data_per_streamline'], DATA['data_per_point'] - ) - - check_tractogram( - tractogram, DATA['streamlines'], DATA['data_per_streamline'], DATA['data_per_point'] - ) - - assert is_data_dict(tractogram.data_per_streamline) - assert is_data_dict(tractogram.data_per_point) - - # Create a tractogram from another tractogram attributes. - tractogram2 = Tractogram( - tractogram.streamlines, tractogram.data_per_streamline, tractogram.data_per_point - ) - - assert_tractogram_equal(tractogram2, tractogram) - - # Create a tractogram from a LazyTractogram object. - tractogram = LazyTractogram( - DATA['streamlines_func'], DATA['data_per_streamline_func'], DATA['data_per_point_func'] - ) - - tractogram2 = Tractogram( - tractogram.streamlines, tractogram.data_per_streamline, tractogram.data_per_point - ) - - # Inconsistent number of scalars between streamlines - wrong_data = [[(1, 0, 0)] * 1, [(0, 1, 0), (0, 1)], [(0, 0, 1)] * 5] - - data_per_point = {'wrong_data': wrong_data} - with pytest.raises(ValueError): - Tractogram(streamlines=DATA['streamlines'], data_per_point=data_per_point) - - # Inconsistent number of scalars between streamlines - wrong_data = [[(1, 0, 0)] * 1, [(0, 1)] * 2, [(0, 0, 1)] * 5] - - data_per_point = {'wrong_data': wrong_data} - with pytest.raises(ValueError): - Tractogram(streamlines=DATA['streamlines'], data_per_point=data_per_point) - - def test_setting_affine_to_rasmm(self): - tractogram = DATA['tractogram'].copy() - affine = np.diag(range(4)) - - # Test assigning None. - tractogram.affine_to_rasmm = None - assert tractogram.affine_to_rasmm is None - - # Test assigning a valid ndarray (should make a copy). - tractogram.affine_to_rasmm = affine - assert tractogram.affine_to_rasmm is not affine - - # Test assigning a list of lists. - tractogram.affine_to_rasmm = affine.tolist() - assert_array_equal(tractogram.affine_to_rasmm, affine) - - # Test assigning a ndarray with wrong shape. - with pytest.raises(ValueError): - tractogram.affine_to_rasmm = affine[::2] - - def test_tractogram_getitem(self): - # Retrieve TractogramItem by their index. - for i, t in enumerate(DATA['tractogram']): - assert_tractogram_item_equal(DATA['tractogram'][i], t) - - # Get one TractogramItem out of two. - tractogram_view = DATA['simple_tractogram'][::2] - check_tractogram(tractogram_view, DATA['streamlines'][::2]) - - # Use slicing. - r_tractogram = DATA['tractogram'][::-1] - check_tractogram( - r_tractogram, - DATA['streamlines'][::-1], - DATA['tractogram'].data_per_streamline[::-1], - DATA['tractogram'].data_per_point[::-1], - ) - - # Make sure slicing conserves the affine_to_rasmm property. - tractogram = DATA['tractogram'].copy() - tractogram.affine_to_rasmm = DATA['rng'].rand(4, 4) - tractogram_view = tractogram[::2] - assert_array_equal(tractogram_view.affine_to_rasmm, tractogram.affine_to_rasmm) - - def test_tractogram_add_new_data(self): - # Tractogram with only streamlines - t = DATA['simple_tractogram'].copy() - t.data_per_point['fa'] = DATA['fa'] - t.data_per_point['colors'] = DATA['colors'] - t.data_per_streamline['mean_curvature'] = DATA['mean_curvature'] - t.data_per_streamline['mean_torsion'] = DATA['mean_torsion'] - t.data_per_streamline['mean_colors'] = DATA['mean_colors'] - t.data_per_streamline['clusters_labels'] = DATA['clusters_labels'] - assert_tractogram_equal(t, DATA['tractogram']) - - # Retrieve tractogram by their index. - for i, item in enumerate(t): - assert_tractogram_item_equal(t[i], item) - - # Use slicing. - r_tractogram = t[::-1] - check_tractogram( - r_tractogram, t.streamlines[::-1], t.data_per_streamline[::-1], t.data_per_point[::-1] - ) - - # Add new data to a tractogram for which its `streamlines` is a view. - t = Tractogram(DATA['streamlines'] * 2, affine_to_rasmm=np.eye(4)) - t = t[: len(DATA['streamlines'])] # Create a view of `streamlines` - t.data_per_point['fa'] = DATA['fa'] - t.data_per_point['colors'] = DATA['colors'] - t.data_per_streamline['mean_curvature'] = DATA['mean_curvature'] - t.data_per_streamline['mean_torsion'] = DATA['mean_torsion'] - t.data_per_streamline['mean_colors'] = DATA['mean_colors'] - t.data_per_streamline['clusters_labels'] = DATA['clusters_labels'] - assert_tractogram_equal(t, DATA['tractogram']) - - def test_tractogram_copy(self): - # Create a copy of a tractogram. - tractogram = DATA['tractogram'].copy() - - # Check we copied the data and not simply created new references. - assert tractogram is not DATA['tractogram'] - assert tractogram.streamlines is not DATA['tractogram'].streamlines - assert tractogram.data_per_streamline is not DATA['tractogram'].data_per_streamline - assert tractogram.data_per_point is not DATA['tractogram'].data_per_point - - for key in tractogram.data_per_streamline: - assert ( - tractogram.data_per_streamline[key] - is not DATA['tractogram'].data_per_streamline[key] - ) - - for key in tractogram.data_per_point: - assert tractogram.data_per_point[key] is not DATA['tractogram'].data_per_point[key] - - # Check the values of the data are the same. - assert_tractogram_equal(tractogram, DATA['tractogram']) - - def test_creating_invalid_tractogram(self): - # Not enough data_per_point for all the points of all streamlines. - scalars = [ - [(1, 0, 0)] * 1, - [(0, 1, 0)] * 2, - [(0, 0, 1)] * 3, - ] # Last streamlines has 5 points. - - with pytest.raises(ValueError): - Tractogram(streamlines=DATA['streamlines'], data_per_point={'scalars': scalars}) - - # Not enough data_per_streamline for all streamlines. - properties = [np.array([1.11, 1.22], dtype='f4'), np.array([3.11, 3.22], dtype='f4')] - - with pytest.raises(ValueError): - Tractogram( - streamlines=DATA['streamlines'], data_per_streamline={'properties': properties} - ) - - # Inconsistent dimension for a data_per_point. - scalars = [[(1, 0, 0)] * 1, [(0, 1)] * 2, [(0, 0, 1)] * 5] - - with pytest.raises(ValueError): - Tractogram(streamlines=DATA['streamlines'], data_per_point={'scalars': scalars}) - - # Too many dimension for a data_per_streamline. - properties = [ - np.array([[1.11], [1.22]], dtype='f4'), - np.array([[2.11], [2.22]], dtype='f4'), - np.array([[3.11], [3.22]], dtype='f4'), - ] - - with pytest.raises(ValueError): - Tractogram( - streamlines=DATA['streamlines'], data_per_streamline={'properties': properties} - ) - - def test_tractogram_apply_affine(self): - tractogram = DATA['tractogram'].copy() - affine = np.eye(4) - scaling = np.array((1, 2, 3), dtype=float) - affine[range(3), range(3)] = scaling - - # Apply the affine to the streamline in a lazy manner. - transformed_tractogram = tractogram.apply_affine(affine, lazy=True) - assert type(transformed_tractogram) is LazyTractogram - check_tractogram( - transformed_tractogram, - streamlines=[s * scaling for s in DATA['streamlines']], - data_per_streamline=DATA['data_per_streamline'], - data_per_point=DATA['data_per_point'], - ) - assert_array_equal( - transformed_tractogram.affine_to_rasmm, np.dot(np.eye(4), np.linalg.inv(affine)) - ) - # Make sure streamlines of the original tractogram have not been - # modified. - assert_arrays_equal(tractogram.streamlines, DATA['streamlines']) - - # Apply the affine to the streamlines in-place. - transformed_tractogram = tractogram.apply_affine(affine) - assert transformed_tractogram is tractogram - check_tractogram( - tractogram, - streamlines=[s * scaling for s in DATA['streamlines']], - data_per_streamline=DATA['data_per_streamline'], - data_per_point=DATA['data_per_point'], - ) - - # Apply affine again and check the affine_to_rasmm. - transformed_tractogram = tractogram.apply_affine(affine) - assert_array_equal( - transformed_tractogram.affine_to_rasmm, - np.dot(np.eye(4), np.dot(np.linalg.inv(affine), np.linalg.inv(affine))), - ) - - # Applying the affine to a tractogram that has been indexed or sliced - # shouldn't affect the remaining streamlines. - tractogram = DATA['tractogram'].copy() - transformed_tractogram = tractogram[::2].apply_affine(affine) - assert transformed_tractogram is not tractogram - check_tractogram( - tractogram[::2], - streamlines=[s * scaling for s in DATA['streamlines'][::2]], - data_per_streamline=DATA['tractogram'].data_per_streamline[::2], - data_per_point=DATA['tractogram'].data_per_point[::2], - ) - - # Remaining streamlines should match the original ones. - check_tractogram( - tractogram[1::2], - streamlines=DATA['streamlines'][1::2], - data_per_streamline=DATA['tractogram'].data_per_streamline[1::2], - data_per_point=DATA['tractogram'].data_per_point[1::2], - ) - - # Check that applying an affine and its inverse give us back the - # original streamlines. - tractogram = DATA['tractogram'].copy() - affine = np.random.RandomState(1234).randn(4, 4) - affine[-1] = [0, 0, 0, 1] # Remove perspective projection. - - tractogram.apply_affine(affine) - tractogram.apply_affine(np.linalg.inv(affine)) - assert_array_almost_equal(tractogram.affine_to_rasmm, np.eye(4)) - for s1, s2 in zip(tractogram.streamlines, DATA['streamlines']): - assert_array_almost_equal(s1, s2) - - # Test applying the identity transformation. - tractogram = DATA['tractogram'].copy() - tractogram.apply_affine(np.eye(4)) - for s1, s2 in zip(tractogram.streamlines, DATA['streamlines']): - assert_array_almost_equal(s1, s2) - - # Test removing affine_to_rasmm - tractogram = DATA['tractogram'].copy() - tractogram.affine_to_rasmm = None - tractogram.apply_affine(affine) - assert tractogram.affine_to_rasmm is None - - def test_tractogram_to_world(self): - tractogram = DATA['tractogram'].copy() - affine = np.random.RandomState(1234).randn(4, 4) - affine[-1] = [0, 0, 0, 1] # Remove perspective projection. - - # Apply the affine to the streamlines, then bring them back - # to world space in a lazy manner. - transformed_tractogram = tractogram.apply_affine(affine) - assert_array_equal(transformed_tractogram.affine_to_rasmm, np.linalg.inv(affine)) - - tractogram_world = transformed_tractogram.to_world(lazy=True) - assert type(tractogram_world) is LazyTractogram - assert_array_almost_equal(tractogram_world.affine_to_rasmm, np.eye(4)) - for s1, s2 in zip(tractogram_world.streamlines, DATA['streamlines']): - assert_array_almost_equal(s1, s2) - - # Bring them back streamlines to world space in a in-place manner. - tractogram_world = transformed_tractogram.to_world() - assert tractogram_world is tractogram - assert_array_almost_equal(tractogram.affine_to_rasmm, np.eye(4)) - for s1, s2 in zip(tractogram.streamlines, DATA['streamlines']): - assert_array_almost_equal(s1, s2) - - # Calling to_world twice should do nothing. - tractogram_world2 = transformed_tractogram.to_world() - assert tractogram_world2 is tractogram - assert_array_almost_equal(tractogram.affine_to_rasmm, np.eye(4)) - for s1, s2 in zip(tractogram.streamlines, DATA['streamlines']): - assert_array_almost_equal(s1, s2) - - # Calling to_world when affine_to_rasmm is None should fail. - tractogram = DATA['tractogram'].copy() - tractogram.affine_to_rasmm = None - with pytest.raises(ValueError): - tractogram.to_world() - - def test_tractogram_extend(self): - # Load tractogram that contains some metadata. - t = DATA['tractogram'].copy() - - for op, in_place in ((operator.add, False), (operator.iadd, True), (extender, True)): - first_arg = t.copy() - new_t = op(first_arg, t) - assert (new_t is first_arg) == in_place - assert_tractogram_equal(new_t[: len(t)], DATA['tractogram']) - assert_tractogram_equal(new_t[len(t) :], DATA['tractogram']) - - # Test extending an empty Tractogram. - t = Tractogram() - t += DATA['tractogram'] - assert_tractogram_equal(t, DATA['tractogram']) - - # and the other way around. - t = DATA['tractogram'].copy() - t += Tractogram() - assert_tractogram_equal(t, DATA['tractogram']) - - -class TestLazyTractogram(unittest.TestCase): - def test_lazy_tractogram_creation(self): - # To create tractogram from arrays use `Tractogram`. - with pytest.raises(TypeError): - LazyTractogram(streamlines=DATA['streamlines']) - - # Streamlines and other data as generators - streamlines = (x for x in DATA['streamlines']) - data_per_point = {'colors': (x for x in DATA['colors'])} - data_per_streamline = { - 'torsion': (x for x in DATA['mean_torsion']), - 'colors': (x for x in DATA['mean_colors']), - } - - # Creating LazyTractogram with generators is not allowed as - # generators get exhausted and are not reusable unlike generator - # function. - with pytest.raises(TypeError): - LazyTractogram(streamlines=streamlines) - with pytest.raises(TypeError): - LazyTractogram(data_per_point={'none': None}) - with pytest.raises(TypeError): - LazyTractogram(data_per_streamline=data_per_streamline) - with pytest.raises(TypeError): - LazyTractogram(streamlines=DATA['streamlines'], data_per_point=data_per_point) - - # Empty `LazyTractogram` - tractogram = LazyTractogram() - with pytest.warns(Warning, match='Number of streamlines will be determined manually'): - check_tractogram(tractogram) - assert tractogram.affine_to_rasmm is None - - # Create tractogram with streamlines and other data - tractogram = LazyTractogram( - DATA['streamlines_func'], DATA['data_per_streamline_func'], DATA['data_per_point_func'] - ) - - assert is_lazy_dict(tractogram.data_per_streamline) - assert is_lazy_dict(tractogram.data_per_point) - - [t for t in tractogram] # Force iteration through tractogram. - assert len(tractogram) == len(DATA['streamlines']) - - # Generator functions get re-called and creates new iterators. - for i in range(2): - assert_tractogram_equal(tractogram, DATA['tractogram']) - - def test_lazy_tractogram_from_data_func(self): - # Create an empty `LazyTractogram` yielding nothing. - tractogram = LazyTractogram.from_data_func(lambda: iter([])) - with pytest.warns(Warning, match='Number of streamlines will be determined manually'): - check_tractogram(tractogram) - - # Create `LazyTractogram` from a generator function yielding - # TractogramItem. - data = [ - DATA['streamlines'], - DATA['fa'], - DATA['colors'], - DATA['mean_curvature'], - DATA['mean_torsion'], - DATA['mean_colors'], - DATA['clusters_labels'], - ] - - def _data_gen(): - for d in zip(*data): - data_for_points = {'fa': d[1], 'colors': d[2]} - data_for_streamline = { - 'mean_curvature': d[3], - 'mean_torsion': d[4], - 'mean_colors': d[5], - 'clusters_labels': d[6], - } - yield TractogramItem(d[0], data_for_streamline, data_for_points) - - tractogram = LazyTractogram.from_data_func(_data_gen) - with pytest.warns(Warning, match='Number of streamlines will be determined manually'): - assert_tractogram_equal(tractogram, DATA['tractogram']) - - # Creating a LazyTractogram from not a corouting should raise an error. - with pytest.raises(TypeError): - LazyTractogram.from_data_func(_data_gen()) - - def test_lazy_tractogram_getitem(self): - with pytest.raises(NotImplementedError): - DATA['lazy_tractogram'][0] - - def test_lazy_tractogram_extend(self): - t = DATA['lazy_tractogram'].copy() - new_t = DATA['lazy_tractogram'].copy() - - for op in (operator.add, operator.iadd, extender): - with pytest.raises(NotImplementedError): - op(new_t, t) - - def test_lazy_tractogram_len(self): - modules = [module_tractogram] # Modules for which to catch warnings. - with clear_and_catch_warnings(record=True, modules=modules) as w: - warnings.simplefilter('always') # Always trigger warnings. - - # Calling `len` will create new generators each time. - tractogram = LazyTractogram(DATA['streamlines_func']) - assert tractogram._nb_streamlines is None - - # This should produce a warning message. - assert len(tractogram) == len(DATA['streamlines']) - assert tractogram._nb_streamlines == len(DATA['streamlines']) - assert len(w) == 1 - - tractogram = LazyTractogram(DATA['streamlines_func']) - - # New instances should still produce a warning message. - assert len(tractogram) == len(DATA['streamlines']) - assert len(w) == 2 - assert issubclass(w[-1].category, Warning) is True - - # Calling again 'len' again should *not* produce a warning. - assert len(tractogram) == len(DATA['streamlines']) - assert len(w) == 2 - - with clear_and_catch_warnings(record=True, modules=modules) as w: - # Once we iterated through the tractogram, we know the length. - - tractogram = LazyTractogram(DATA['streamlines_func']) - - assert tractogram._nb_streamlines is None - [t for t in tractogram] # Force iteration through tractogram. - assert tractogram._nb_streamlines == len(DATA['streamlines']) - # This should *not* produce a warning. - assert len(tractogram) == len(DATA['streamlines']) - assert len(w) == 0 - - def test_lazy_tractogram_apply_affine(self): - affine = np.eye(4) - scaling = np.array((1, 2, 3), dtype=float) - affine[range(3), range(3)] = scaling - - tractogram = DATA['lazy_tractogram'].copy() - - transformed_tractogram = tractogram.apply_affine(affine) - assert transformed_tractogram is not tractogram - assert_array_equal(tractogram._affine_to_apply, np.eye(4)) - assert_array_equal(tractogram.affine_to_rasmm, np.eye(4)) - assert_array_equal(transformed_tractogram._affine_to_apply, affine) - assert_array_equal( - transformed_tractogram.affine_to_rasmm, np.dot(np.eye(4), np.linalg.inv(affine)) - ) - with pytest.warns(Warning, match='Number of streamlines will be determined manually'): - check_tractogram( - transformed_tractogram, - streamlines=[s * scaling for s in DATA['streamlines']], - data_per_streamline=DATA['data_per_streamline'], - data_per_point=DATA['data_per_point'], - ) - - # Apply affine again and check the affine_to_rasmm. - transformed_tractogram = transformed_tractogram.apply_affine(affine) - assert_array_equal(transformed_tractogram._affine_to_apply, np.dot(affine, affine)) - assert_array_equal( - transformed_tractogram.affine_to_rasmm, - np.dot(np.eye(4), np.dot(np.linalg.inv(affine), np.linalg.inv(affine))), - ) - - # Calling to_world when affine_to_rasmm is None should fail. - tractogram = DATA['lazy_tractogram'].copy() - tractogram.affine_to_rasmm = None - with pytest.raises(ValueError): - tractogram.to_world() - - # But calling apply_affine when affine_to_rasmm is None should work. - tractogram = DATA['lazy_tractogram'].copy() - tractogram.affine_to_rasmm = None - transformed_tractogram = tractogram.apply_affine(affine) - assert_array_equal(transformed_tractogram._affine_to_apply, affine) - assert transformed_tractogram.affine_to_rasmm is None - with pytest.warns(Warning, match='Number of streamlines will be determined manually'): - check_tractogram( - transformed_tractogram, - streamlines=[s * scaling for s in DATA['streamlines']], - data_per_streamline=DATA['data_per_streamline'], - data_per_point=DATA['data_per_point'], - ) - - # Calling apply_affine with lazy=False should fail for LazyTractogram. - tractogram = DATA['lazy_tractogram'].copy() - with pytest.raises(ValueError): - tractogram.apply_affine(affine=np.eye(4), lazy=False) - - def test_tractogram_to_world(self): - tractogram = DATA['lazy_tractogram'].copy() - affine = np.random.RandomState(1234).randn(4, 4) - affine[-1] = [0, 0, 0, 1] # Remove perspective projection. - - # Apply the affine to the streamlines, then bring them back - # to world space in a lazy manner. - transformed_tractogram = tractogram.apply_affine(affine) - assert_array_equal(transformed_tractogram.affine_to_rasmm, np.linalg.inv(affine)) - - tractogram_world = transformed_tractogram.to_world() - assert tractogram_world is not transformed_tractogram - assert_array_almost_equal(tractogram_world.affine_to_rasmm, np.eye(4)) - for s1, s2 in zip(tractogram_world.streamlines, DATA['streamlines']): - assert_array_almost_equal(s1, s2) - - # Calling to_world twice should do nothing. - tractogram_world = tractogram_world.to_world() - assert_array_almost_equal(tractogram_world.affine_to_rasmm, np.eye(4)) - for s1, s2 in zip(tractogram_world.streamlines, DATA['streamlines']): - assert_array_almost_equal(s1, s2) - - # Calling to_world when affine_to_rasmm is None should fail. - tractogram = DATA['lazy_tractogram'].copy() - tractogram.affine_to_rasmm = None - with pytest.raises(ValueError): - tractogram.to_world() - - def test_lazy_tractogram_copy(self): - # Create a copy of the lazy tractogram. - tractogram = DATA['lazy_tractogram'].copy() - - # Check we copied the data and not simply created new references. - assert tractogram is not DATA['lazy_tractogram'] - - # When copying LazyTractogram, the generator function yielding - # streamlines should stay the same. - assert tractogram._streamlines is DATA['lazy_tractogram']._streamlines - - # Copying LazyTractogram, creates new internal LazyDict objects, - # but generator functions contained in it should stay the same. - assert tractogram._data_per_streamline is not DATA['lazy_tractogram']._data_per_streamline - assert tractogram._data_per_point is not DATA['lazy_tractogram']._data_per_point - - for key in tractogram.data_per_streamline: - data = tractogram.data_per_streamline.store[key] - expected = DATA['lazy_tractogram'].data_per_streamline.store[key] - assert data is expected - - for key in tractogram.data_per_point: - data = tractogram.data_per_point.store[key] - expected = DATA['lazy_tractogram'].data_per_point.store[key] - assert data is expected - - # The affine should be a copy. - assert tractogram._affine_to_apply is not DATA['lazy_tractogram']._affine_to_apply - assert_array_equal(tractogram._affine_to_apply, DATA['lazy_tractogram']._affine_to_apply) - - # Check the data are the equivalent. - with pytest.warns(Warning, match='Number of streamlines will be determined manually'): - assert_tractogram_equal(tractogram, DATA['tractogram']) diff --git a/nibabel/streamlines/tests/test_tractogram_file.py b/nibabel/streamlines/tests/test_tractogram_file.py deleted file mode 100644 index 6f764009f1..0000000000 --- a/nibabel/streamlines/tests/test_tractogram_file.py +++ /dev/null @@ -1,84 +0,0 @@ -"""Test tractogramFile base class""" - -import pytest - -from ..tractogram import Tractogram -from ..tractogram_file import TractogramFile - - -def test_subclassing_tractogram_file(): - # Missing 'save' method - class DummyTractogramFile(TractogramFile): - @classmethod - def is_correct_format(cls, fileobj): - return False - - @classmethod - def load(cls, fileobj, lazy_load=True): - return None - - @classmethod - def create_empty_header(cls): - return None - - with pytest.raises(TypeError): - DummyTractogramFile(Tractogram()) - - # Missing 'load' method - class DummyTractogramFile(TractogramFile): - @classmethod - def is_correct_format(cls, fileobj): - return False - - def save(self, fileobj): - pass - - @classmethod - def create_empty_header(cls): - return None - - with pytest.raises(TypeError): - DummyTractogramFile(Tractogram()) - - # Now we have everything required. - class DummyTractogramFile(TractogramFile): - @classmethod - def is_correct_format(cls, fileobj): - return False - - @classmethod - def load(cls, fileobj, lazy_load=True): - return None - - def save(self, fileobj): - pass - - # No error - dtf = DummyTractogramFile(Tractogram()) - - # Default create_empty_header is empty dict - assert dtf.header == {} - - -def test_tractogram_file(): - with pytest.raises(NotImplementedError): - TractogramFile.is_correct_format('') - with pytest.raises(NotImplementedError): - TractogramFile.load('') - - # Testing calling the 'save' method of `TractogramFile` object. - class DummyTractogramFile(TractogramFile): - @classmethod - def is_correct_format(cls, fileobj): - return False - - @classmethod - def load(cls, fileobj, lazy_load=True): - return None - - @classmethod - def save(self, fileobj): - pass - - with pytest.raises(NotImplementedError): - super(DummyTractogramFile, DummyTractogramFile(Tractogram)).save('') diff --git a/nibabel/streamlines/tests/test_trk.py b/nibabel/streamlines/tests/test_trk.py deleted file mode 100644 index 4cb6032c25..0000000000 --- a/nibabel/streamlines/tests/test_trk.py +++ /dev/null @@ -1,550 +0,0 @@ -import copy -import os -import sys -import unittest -from io import BytesIO -from os.path import join as pjoin - -import numpy as np -import pytest -from numpy.testing import assert_array_equal - -from ...testing import assert_arr_dict_equal, clear_and_catch_warnings, data_path, error_warnings -from .. import trk as trk_module -from ..header import Field -from ..tractogram import Tractogram -from ..tractogram_file import HeaderError, HeaderWarning -from ..trk import ( - TrkFile, - decode_value_from_name, - encode_value_in_name, - get_affine_trackvis_to_rasmm, -) -from .test_tractogram import assert_tractogram_equal - -DATA = {} - - -def setup_module(): - global DATA - - DATA['empty_trk_fname'] = pjoin(data_path, 'empty.trk') - # simple.trk contains only streamlines - DATA['simple_trk_fname'] = pjoin(data_path, 'simple.trk') - # standard.trk contains only streamlines - DATA['standard_trk_fname'] = pjoin(data_path, 'standard.trk') - # standard.LPS.trk contains only streamlines - DATA['standard_LPS_trk_fname'] = pjoin(data_path, 'standard.LPS.trk') - - # complex.trk contains streamlines, scalars and properties - DATA['complex_trk_fname'] = pjoin(data_path, 'complex.trk') - DATA['complex_trk_big_endian_fname'] = pjoin(data_path, 'complex_big_endian.trk') - - DATA['streamlines'] = [ - np.arange(1 * 3, dtype='f4').reshape((1, 3)), - np.arange(2 * 3, dtype='f4').reshape((2, 3)), - np.arange(5 * 3, dtype='f4').reshape((5, 3)), - ] - - DATA['fa'] = [ - np.array([[0.2]], dtype='f4'), - np.array([[0.3], [0.4]], dtype='f4'), - np.array([[0.5], [0.6], [0.6], [0.7], [0.8]], dtype='f4'), - ] - - DATA['colors'] = [ - np.array([(1, 0, 0)] * 1, dtype='f4'), - np.array([(0, 1, 0)] * 2, dtype='f4'), - np.array([(0, 0, 1)] * 5, dtype='f4'), - ] - - DATA['mean_curvature'] = [ - np.array([1.11], dtype='f4'), - np.array([2.11], dtype='f4'), - np.array([3.11], dtype='f4'), - ] - - DATA['mean_torsion'] = [ - np.array([1.22], dtype='f4'), - np.array([2.22], dtype='f4'), - np.array([3.22], dtype='f4'), - ] - - DATA['mean_colors'] = [ - np.array([1, 0, 0], dtype='f4'), - np.array([0, 1, 0], dtype='f4'), - np.array([0, 0, 1], dtype='f4'), - ] - - DATA['data_per_point'] = {'colors': DATA['colors'], 'fa': DATA['fa']} - DATA['data_per_streamline'] = { - 'mean_curvature': DATA['mean_curvature'], - 'mean_torsion': DATA['mean_torsion'], - 'mean_colors': DATA['mean_colors'], - } - - DATA['empty_tractogram'] = Tractogram(affine_to_rasmm=np.eye(4)) - DATA['simple_tractogram'] = Tractogram(DATA['streamlines'], affine_to_rasmm=np.eye(4)) - DATA['complex_tractogram'] = Tractogram( - DATA['streamlines'], - DATA['data_per_streamline'], - DATA['data_per_point'], - affine_to_rasmm=np.eye(4), - ) - - -class TestTRK(unittest.TestCase): - def test_load_empty_file(self): - for lazy_load in [False, True]: - trk = TrkFile.load(DATA['empty_trk_fname'], lazy_load=lazy_load) - with pytest.warns(Warning) if lazy_load else error_warnings(): - assert_tractogram_equal(trk.tractogram, DATA['empty_tractogram']) - - def test_load_simple_file(self): - for lazy_load in [False, True]: - trk = TrkFile.load(DATA['simple_trk_fname'], lazy_load=lazy_load) - with pytest.warns(Warning) if lazy_load else error_warnings(): - assert_tractogram_equal(trk.tractogram, DATA['simple_tractogram']) - - def test_load_complex_file(self): - for lazy_load in [False, True]: - trk = TrkFile.load(DATA['complex_trk_fname'], lazy_load=lazy_load) - with pytest.warns(Warning) if lazy_load else error_warnings(): - assert_tractogram_equal(trk.tractogram, DATA['complex_tractogram']) - - def trk_with_bytes(self, trk_key='simple_trk_fname', endian='<'): - """Return example trk file bytes and struct view onto bytes""" - with open(DATA[trk_key], 'rb') as fobj: - trk_bytes = bytearray(fobj.read()) - dt = trk_module.header_2_dtype.newbyteorder(endian) - trk_struct = np.ndarray((1,), dt, buffer=trk_bytes) - trk_struct.flags.writeable = True - return trk_struct, trk_bytes - - def test_load_file_with_wrong_information(self): - # Simulate a TRK file where `voxel_order` is lowercase. - trk_struct1, trk_bytes1 = self.trk_with_bytes() - trk_struct1[Field.VOXEL_ORDER] = b'LAS' - trk1 = TrkFile.load(BytesIO(trk_bytes1)) - trk_struct2, trk_bytes2 = self.trk_with_bytes() - trk_struct2[Field.VOXEL_ORDER] = b'las' - trk2 = TrkFile.load(BytesIO(trk_bytes2)) - trk1_aff2rasmm = get_affine_trackvis_to_rasmm(trk1.header) - trk2_aff2rasmm = get_affine_trackvis_to_rasmm(trk2.header) - assert_array_equal(trk1_aff2rasmm, trk2_aff2rasmm) - - # Simulate a TRK file where `count` was not provided. - trk_struct, trk_bytes = self.trk_with_bytes() - trk_struct[Field.NB_STREAMLINES] = 0 - trk = TrkFile.load(BytesIO(trk_bytes), lazy_load=False) - assert_tractogram_equal(trk.tractogram, DATA['simple_tractogram']) - - # Simulate a TRK where `vox_to_ras` is not recorded (i.e. all zeros). - trk_struct, trk_bytes = self.trk_with_bytes() - trk_struct[Field.VOXEL_TO_RASMM] = np.zeros((4, 4)) - with pytest.warns(HeaderWarning, match='identity'): - trk = TrkFile.load(BytesIO(trk_bytes)) - assert_array_equal(trk.affine, np.eye(4)) - - # Simulate a TRK where `vox_to_ras` is invalid. - trk_struct, trk_bytes = self.trk_with_bytes() - trk_struct[Field.VOXEL_TO_RASMM] = np.diag([0, 0, 0, 1]) - with clear_and_catch_warnings(modules=[trk_module]): - with pytest.raises(HeaderError): - TrkFile.load(BytesIO(trk_bytes)) - - # Simulate a TRK file where `voxel_order` was not provided. - trk_struct, trk_bytes = self.trk_with_bytes() - trk_struct[Field.VOXEL_ORDER] = b'' - with pytest.warns(HeaderWarning, match='LPS'): - TrkFile.load(BytesIO(trk_bytes)) - - # Simulate a TRK file with an unsupported version. - trk_struct, trk_bytes = self.trk_with_bytes() - trk_struct['version'] = 123 - with pytest.raises(HeaderError): - TrkFile.load(BytesIO(trk_bytes)) - - # Simulate a TRK file with a wrong hdr_size. - trk_struct, trk_bytes = self.trk_with_bytes() - trk_struct['hdr_size'] = 1234 - with pytest.raises(HeaderError): - TrkFile.load(BytesIO(trk_bytes)) - - # Simulate a TRK file with a wrong scalar_name. - trk_struct, trk_bytes = self.trk_with_bytes('complex_trk_fname') - trk_struct['scalar_name'][0, 0] = b'colors\x003\x004' - with pytest.raises(HeaderError): - TrkFile.load(BytesIO(trk_bytes)) - - # Simulate a TRK file with a wrong property_name. - trk_struct, trk_bytes = self.trk_with_bytes('complex_trk_fname') - trk_struct['property_name'][0, 0] = b'colors\x003\x004' - with pytest.raises(HeaderError): - TrkFile.load(BytesIO(trk_bytes)) - - def test_load_trk_version_1(self): - # Simulate and test a TRK (version 1). - # First check that setting the RAS affine works in version 2. - trk_struct, trk_bytes = self.trk_with_bytes() - trk_struct[Field.VOXEL_TO_RASMM] = np.diag([2, 3, 4, 1]) - trk = TrkFile.load(BytesIO(trk_bytes)) - assert_array_equal(trk.affine, np.diag([2, 3, 4, 1])) - # Next check that affine assumed identity if version 1. - trk_struct['version'] = 1 - with pytest.warns(HeaderWarning, match='identity'): - trk = TrkFile.load(BytesIO(trk_bytes)) - assert_array_equal(trk.affine, np.eye(4)) - assert_array_equal(trk.header['version'], 1) - - def test_load_complex_file_in_big_endian(self): - trk_struct, trk_bytes = self.trk_with_bytes('complex_trk_big_endian_fname', endian='>') - # We use hdr_size as an indicator of little vs big endian. - good_orders = '>' if sys.byteorder == 'little' else '>=' - hdr_size = trk_struct['hdr_size'] - assert hdr_size.dtype.byteorder in good_orders - assert hdr_size == 1000 - - for lazy_load in [False, True]: - trk = TrkFile.load(DATA['complex_trk_big_endian_fname'], lazy_load=lazy_load) - with pytest.warns(Warning) if lazy_load else error_warnings(): - assert_tractogram_equal(trk.tractogram, DATA['complex_tractogram']) - - def test_tractogram_file_properties(self): - trk = TrkFile.load(DATA['simple_trk_fname']) - assert trk.streamlines == trk.tractogram.streamlines - assert_array_equal(trk.affine, trk.header[Field.VOXEL_TO_RASMM]) - - def test_write_empty_file(self): - tractogram = Tractogram(affine_to_rasmm=np.eye(4)) - - trk_file = BytesIO() - trk = TrkFile(tractogram) - trk.save(trk_file) - trk_file.seek(0, os.SEEK_SET) - - new_trk = TrkFile.load(trk_file) - assert_tractogram_equal(new_trk.tractogram, tractogram) - - new_trk_orig = TrkFile.load(DATA['empty_trk_fname']) - assert_tractogram_equal(new_trk.tractogram, new_trk_orig.tractogram) - - trk_file.seek(0, os.SEEK_SET) - assert trk_file.read() == open(DATA['empty_trk_fname'], 'rb').read() - - def test_write_simple_file(self): - tractogram = Tractogram(DATA['streamlines'], affine_to_rasmm=np.eye(4)) - - trk_file = BytesIO() - trk = TrkFile(tractogram) - trk.save(trk_file) - trk_file.seek(0, os.SEEK_SET) - - new_trk = TrkFile.load(trk_file) - assert_tractogram_equal(new_trk.tractogram, tractogram) - - new_trk_orig = TrkFile.load(DATA['simple_trk_fname']) - assert_tractogram_equal(new_trk.tractogram, new_trk_orig.tractogram) - - trk_file.seek(0, os.SEEK_SET) - assert trk_file.read() == open(DATA['simple_trk_fname'], 'rb').read() - - def test_write_complex_file(self): - # With scalars - tractogram = Tractogram( - DATA['streamlines'], data_per_point=DATA['data_per_point'], affine_to_rasmm=np.eye(4) - ) - - trk_file = BytesIO() - trk = TrkFile(tractogram) - trk.save(trk_file) - trk_file.seek(0, os.SEEK_SET) - - new_trk = TrkFile.load(trk_file, lazy_load=False) - assert_tractogram_equal(new_trk.tractogram, tractogram) - - # With properties - data_per_streamline = DATA['data_per_streamline'] - tractogram = Tractogram( - DATA['streamlines'], data_per_streamline=data_per_streamline, affine_to_rasmm=np.eye(4) - ) - - trk = TrkFile(tractogram) - trk_file = BytesIO() - trk.save(trk_file) - trk_file.seek(0, os.SEEK_SET) - - new_trk = TrkFile.load(trk_file, lazy_load=False) - assert_tractogram_equal(new_trk.tractogram, tractogram) - - # With scalars and properties - data_per_streamline = DATA['data_per_streamline'] - tractogram = Tractogram( - DATA['streamlines'], - data_per_point=DATA['data_per_point'], - data_per_streamline=data_per_streamline, - affine_to_rasmm=np.eye(4), - ) - - trk_file = BytesIO() - trk = TrkFile(tractogram) - trk.save(trk_file) - trk_file.seek(0, os.SEEK_SET) - - new_trk = TrkFile.load(trk_file, lazy_load=False) - assert_tractogram_equal(new_trk.tractogram, tractogram) - - new_trk_orig = TrkFile.load(DATA['complex_trk_fname']) - assert_tractogram_equal(new_trk.tractogram, new_trk_orig.tractogram) - - trk_file.seek(0, os.SEEK_SET) - assert trk_file.read() == open(DATA['complex_trk_fname'], 'rb').read() - - def test_load_write_file(self): - for fname in [ - DATA['empty_trk_fname'], - DATA['simple_trk_fname'], - DATA['complex_trk_fname'], - ]: - for lazy_load in [False, True]: - trk = TrkFile.load(fname, lazy_load=lazy_load) - trk_file = BytesIO() - trk.save(trk_file) - - new_trk = TrkFile.load(fname, lazy_load=False) - assert_tractogram_equal(new_trk.tractogram, trk.tractogram) - - def test_load_write_LPS_file(self): - # Load the RAS and LPS version of the standard. - trk_RAS = TrkFile.load(DATA['standard_trk_fname'], lazy_load=False) - trk_LPS = TrkFile.load(DATA['standard_LPS_trk_fname'], lazy_load=False) - assert_tractogram_equal(trk_LPS.tractogram, trk_RAS.tractogram) - - # Write back the standard. - trk_file = BytesIO() - trk = TrkFile(trk_LPS.tractogram, trk_LPS.header) - trk.save(trk_file) - trk_file.seek(0, os.SEEK_SET) - - new_trk = TrkFile.load(trk_file) - - assert_arr_dict_equal(new_trk.header, trk.header) - assert_tractogram_equal(new_trk.tractogram, trk.tractogram) - - new_trk_orig = TrkFile.load(DATA['standard_LPS_trk_fname']) - assert_tractogram_equal(new_trk.tractogram, new_trk_orig.tractogram) - - trk_file.seek(0, os.SEEK_SET) - assert trk_file.read() == open(DATA['standard_LPS_trk_fname'], 'rb').read() - - # Test writing a file where the header is missing the - # Field.VOXEL_ORDER. - trk_file = BytesIO() - - # For TRK file format, the default voxel order is LPS. - header = copy.deepcopy(trk_LPS.header) - header[Field.VOXEL_ORDER] = b'' - - trk = TrkFile(trk_LPS.tractogram, header) - trk.save(trk_file) - trk_file.seek(0, os.SEEK_SET) - - new_trk = TrkFile.load(trk_file) - - assert_arr_dict_equal(new_trk.header, trk_LPS.header) - assert_tractogram_equal(new_trk.tractogram, trk.tractogram) - - new_trk_orig = TrkFile.load(DATA['standard_LPS_trk_fname']) - assert_tractogram_equal(new_trk.tractogram, new_trk_orig.tractogram) - - trk_file.seek(0, os.SEEK_SET) - assert trk_file.read() == open(DATA['standard_LPS_trk_fname'], 'rb').read() - - def test_write_optional_header_fields(self): - # The TRK file format doesn't support additional header fields. - # If provided, they will be ignored. - tractogram = Tractogram(affine_to_rasmm=np.eye(4)) - - trk_file = BytesIO() - header = {'extra': 1234} - trk = TrkFile(tractogram, header) - trk.save(trk_file) - trk_file.seek(0, os.SEEK_SET) - - new_trk = TrkFile.load(trk_file) - assert 'extra' not in new_trk.header - - def test_write_too_many_scalars_and_properties(self): - # TRK supports up to 10 data_per_point. - data_per_point = {} - for i in range(10): - data_per_point[f'#{i}'] = DATA['fa'] - - tractogram = Tractogram( - DATA['streamlines'], data_per_point=data_per_point, affine_to_rasmm=np.eye(4) - ) - - trk_file = BytesIO() - trk = TrkFile(tractogram) - trk.save(trk_file) - trk_file.seek(0, os.SEEK_SET) - - new_trk = TrkFile.load(trk_file, lazy_load=False) - assert_tractogram_equal(new_trk.tractogram, tractogram) - - # More than 10 data_per_point should raise an error. - data_per_point[f'#{i + 1}'] = DATA['fa'] - - tractogram = Tractogram( - DATA['streamlines'], data_per_point=data_per_point, affine_to_rasmm=np.eye(4) - ) - - trk = TrkFile(tractogram) - with pytest.raises(ValueError): - trk.save(BytesIO()) - - # TRK supports up to 10 data_per_streamline. - data_per_streamline = {} - for i in range(10): - data_per_streamline[f'#{i}'] = DATA['mean_torsion'] - - tractogram = Tractogram( - DATA['streamlines'], - data_per_streamline=data_per_streamline, - affine_to_rasmm=np.eye(4), - ) - - trk_file = BytesIO() - trk = TrkFile(tractogram) - trk.save(trk_file) - trk_file.seek(0, os.SEEK_SET) - - new_trk = TrkFile.load(trk_file, lazy_load=False) - assert_tractogram_equal(new_trk.tractogram, tractogram) - - # More than 10 data_per_streamline should raise an error. - data_per_streamline[f'#{i + 1}'] = DATA['mean_torsion'] - - tractogram = Tractogram(DATA['streamlines'], data_per_streamline=data_per_streamline) - - trk = TrkFile(tractogram) - with pytest.raises(ValueError): - trk.save(BytesIO()) - - def test_write_scalars_and_properties_name_too_long(self): - # TRK supports data_per_point name up to 20 characters. - # However, we reserve the last two characters to store - # the number of values associated to each data_per_point. - # So in reality we allow name of 18 characters, otherwise - # the name is truncated and warning is issue. - for nb_chars in range(22): - data_per_point = {'A' * nb_chars: DATA['colors']} - tractogram = Tractogram( - DATA['streamlines'], data_per_point=data_per_point, affine_to_rasmm=np.eye(4) - ) - - trk = TrkFile(tractogram) - if nb_chars > 18: - with pytest.raises(ValueError): - trk.save(BytesIO()) - else: - trk.save(BytesIO()) - - data_per_point = {'A' * nb_chars: DATA['fa']} - tractogram = Tractogram( - DATA['streamlines'], data_per_point=data_per_point, affine_to_rasmm=np.eye(4) - ) - - trk = TrkFile(tractogram) - if nb_chars > 20: - with pytest.raises(ValueError): - trk.save(BytesIO()) - else: - trk.save(BytesIO()) - - # TRK supports data_per_streamline name up to 20 characters. - # However, we reserve the last two characters to store - # the number of values associated to each data_per_streamline. - # So in reality we allow name of 18 characters, otherwise - # the name is truncated and warning is issue. - for nb_chars in range(22): - data_per_streamline = {'A' * nb_chars: DATA['mean_colors']} - tractogram = Tractogram( - DATA['streamlines'], - data_per_streamline=data_per_streamline, - affine_to_rasmm=np.eye(4), - ) - - trk = TrkFile(tractogram) - if nb_chars > 18: - with pytest.raises(ValueError): - trk.save(BytesIO()) - else: - trk.save(BytesIO()) - - data_per_streamline = {'A' * nb_chars: DATA['mean_torsion']} - tractogram = Tractogram( - DATA['streamlines'], - data_per_streamline=data_per_streamline, - affine_to_rasmm=np.eye(4), - ) - - trk = TrkFile(tractogram) - if nb_chars > 20: - with pytest.raises(ValueError): - trk.save(BytesIO()) - else: - trk.save(BytesIO()) - - def test_str(self): - trk = TrkFile.load(DATA['complex_trk_fname']) - str(trk) # Simply test it's not failing when called. - - def test_header_read_restore(self): - # Test that reading a header restores the file position - trk_fname = DATA['simple_trk_fname'] - bio = BytesIO() - bio.write(b'Along my very merry way') - hdr_pos = bio.tell() - hdr_from_fname = TrkFile._read_header(trk_fname) - with open(trk_fname, 'rb') as fobj: - bio.write(fobj.read()) - bio.seek(hdr_pos) - # Check header is as expected - hdr_from_fname['_offset_data'] += hdr_pos # Correct for start position - assert_arr_dict_equal(TrkFile._read_header(bio), hdr_from_fname) - # Check fileobject file position has not changed - assert bio.tell() == hdr_pos - - -def test_encode_names(): - # Test function for encoding numbers into property names - b0 = b'\x00' - assert encode_value_in_name(0, 'foo', 10) == b'foo' + b0 * 7 - assert encode_value_in_name(1, 'foo', 10) == b'foo' + b0 * 7 - assert encode_value_in_name(8, 'foo', 10) == b'foo' + b0 + b'8' + b0 * 5 - assert encode_value_in_name(40, 'foobar', 10) == b'foobar' + b0 + b'40' + b0 - assert encode_value_in_name(1, 'foobarbazz', 10) == b'foobarbazz' - - with pytest.raises(ValueError): - encode_value_in_name(1, 'foobarbazzz', 10) - - with pytest.raises(ValueError): - encode_value_in_name(2, 'foobarbazzz', 10) - - assert encode_value_in_name(2, 'foobarba', 10) == b'foobarba\x002' - - -def test_decode_names(): - # Test function for decoding name string into name, number - b0 = b'\x00' - assert decode_value_from_name(b'') == ('', 0) - assert decode_value_from_name(b'foo' + b0 * 7) == ('foo', 1) - assert decode_value_from_name(b'foo\x008' + b0 * 5) == ('foo', 8) - assert decode_value_from_name(b'foobar\x0010\x00') == ('foobar', 10) - - with pytest.raises(ValueError): - decode_value_from_name(b'foobar\x0010\x01') - - with pytest.raises(HeaderError): - decode_value_from_name(b'foo\x0010\x00111') diff --git a/nibabel/streamlines/tests/test_utils.py b/nibabel/streamlines/tests/test_utils.py deleted file mode 100644 index 7836d45eb5..0000000000 --- a/nibabel/streamlines/tests/test_utils.py +++ /dev/null @@ -1,28 +0,0 @@ -import os - -import numpy as np -import pytest -from numpy.testing import assert_array_equal - -import nibabel as nib -from nibabel.testing import data_path - -from ..utils import get_affine_from_reference - - -def test_get_affine_from_reference(): - filename = os.path.join(data_path, 'example_nifti2.nii.gz') - img = nib.load(filename) - affine = img.affine - - # Get affine from an numpy array. - assert_array_equal(get_affine_from_reference(affine), affine) - wrong_ref = np.array([[1, 2, 3], [4, 5, 6]]) - with pytest.raises(ValueError): - get_affine_from_reference(wrong_ref) - - # Get affine from a `SpatialImage`. - assert_array_equal(get_affine_from_reference(img), affine) - - # Get affine from a `SpatialImage` using by its filename. - assert_array_equal(get_affine_from_reference(filename), affine) diff --git a/nibabel/streamlines/tractogram.py b/nibabel/streamlines/tractogram.py deleted file mode 100644 index 5a39b415a6..0000000000 --- a/nibabel/streamlines/tractogram.py +++ /dev/null @@ -1,883 +0,0 @@ -import copy -import numbers -import types -from collections.abc import Iterable, MutableMapping -from warnings import warn - -import numpy as np - -from nibabel.affines import apply_affine - -from .array_sequence import ArraySequence - - -def is_data_dict(obj): - """True if `obj` seems to implement the :class:`DataDict` API""" - return hasattr(obj, 'store') - - -def is_lazy_dict(obj): - """True if `obj` seems to implement the :class:`LazyDict` API""" - return is_data_dict(obj) and callable(list(obj.store.values())[0]) - - -class SliceableDataDict(MutableMapping): - r"""Dictionary for which key access can do slicing on the values. - - This container behaves like a standard dictionary but extends key access to - allow keys for key access to be indices slicing into the contained ndarray - values. - - Parameters - ---------- - \*args : - \*\*kwargs : - Positional and keyword arguments, passed straight through the ``dict`` - constructor. - """ - - def __init__(self, *args, **kwargs): - self.store = dict() - self.update(dict(*args, **kwargs)) - - def __getitem__(self, key): - try: - return self.store[key] - except (KeyError, TypeError, IndexError): - pass # Maybe it is an integer or a slicing object - - # Try to interpret key as an index/slice for every data element, in - # which case we perform (maybe advanced) indexing on every element of - # the dictionary. - idx = key - new_dict = type(self)() - try: - for k, v in self.items(): - new_dict[k] = v[idx] - except (TypeError, ValueError, IndexError): - pass - else: - return new_dict - - # Key was not a valid index/slice after all. - return self.store[key] # Will raise the proper error. - - def __contains__(self, key): - return key in self.store - - def __delitem__(self, key): - del self.store[key] - - def __iter__(self): - return iter(self.store) - - def __len__(self): - return len(self.store) - - -class PerArrayDict(SliceableDataDict): - r"""Dictionary for which key access can do slicing on the values. - - This container behaves like a standard dictionary but extends key access to - allow keys for key access to be indices slicing into the contained ndarray - values. The elements must also be ndarrays. - - In addition, it makes sure the amount of data contained in those ndarrays - matches the number of streamlines given at the instantiation of this - instance. - - Parameters - ---------- - n_rows : None or int, optional - Number of rows per value in each key, value pair or None for not - specified. - \*args : - \*\*kwargs : - Positional and keyword arguments, passed straight through the ``dict`` - constructor. - """ - - def __init__(self, n_rows=0, *args, **kwargs): - self.n_rows = n_rows - super().__init__(*args, **kwargs) - - def __setitem__(self, key, value): - dtype = np.float64 - - if isinstance(value, types.GeneratorType): - value = list(value) - - if isinstance(value, np.ndarray): - dtype = value.dtype - elif not all(len(v) == len(value[0]) for v in value[1:]): - dtype = object - - value = np.asarray(value, dtype=dtype) - - if value.ndim == 1 and value.dtype != object: - # Reshape without copy - value.shape = (len(value), 1) - - if value.ndim != 2 and value.dtype != object: - raise ValueError('data_per_streamline must be a 2D array.') - - if value.dtype == object and not all(isinstance(v, Iterable) for v in value): - raise ValueError('data_per_streamline must be a 2D array') - - # We make sure there is the right amount of values - if 0 < self.n_rows != len(value): - msg = f'The number of values ({len(value)}) should match n_elements ({self.n_rows}).' - raise ValueError(msg) - - self.store[key] = value - - def _extend_entry(self, key, value): - """Appends the `value` to the entry specified by `key`.""" - self[key] = np.concatenate([self[key], value]) - - def extend(self, other): - """Appends the elements of another :class:`PerArrayDict`. - - That is, for each entry in this dictionary, we append the elements - coming from the other dictionary at the corresponding entry. - - Parameters - ---------- - other : :class:`PerArrayDict` object - Its data will be appended to the data of this dictionary. - - Returns - ------- - None - - Notes - ----- - The keys in both dictionaries must be the same. - """ - if len(self) > 0 and len(other) > 0 and sorted(self.keys()) != sorted(other.keys()): - msg = ( - 'Entry mismatched between the two PerArrayDict objects. ' - f"This PerArrayDict contains '{sorted(self.keys())}' " - f"whereas the other contains '{sorted(other.keys())}'." - ) - raise ValueError(msg) - - self.n_rows += other.n_rows - for key in other.keys(): - if key not in self: - self[key] = other[key] - else: - self._extend_entry(key, other[key]) - - -class PerArraySequenceDict(PerArrayDict): - """Dictionary for which key access can do slicing on the values. - - This container behaves like a standard dictionary but extends key access to - allow keys for key access to be indices slicing into the contained ndarray - values. The elements must also be :class:`ArraySequence`. - - In addition, it makes sure the amount of data contained in those array - sequences matches the number of elements given at the instantiation - of the instance. - """ - - def __setitem__(self, key, value): - value = ArraySequence(value) - - # We make sure there is the right amount of data. - if 0 < self.n_rows != value.total_nb_rows: - msg = f'The number of values ({value.total_nb_rows}) should match ({self.n_rows}).' - raise ValueError(msg) - - self.store[key] = value - - def _extend_entry(self, key, value): - """Appends the `value` to the entry specified by `key`.""" - self[key].extend(value) - - -class LazyDict(MutableMapping): - """Dictionary of generator functions. - - This container behaves like a dictionary but it makes sure its elements are - callable objects that it assumes are generator functions yielding values. - When getting the element associated with a given key, the element (i.e. a - generator function) is first called before being returned. - """ - - def __init__(self, *args, **kwargs): - self.store = dict() - # Use the 'update' method to set the keys. - if len(args) == 1: - if args[0] is None: - return - - if isinstance(args[0], LazyDict): - self.update(**args[0].store) # Copy the generator functions. - return - - self.update(dict(*args, **kwargs)) - - def __getitem__(self, key): - return self.store[key]() - - def __setitem__(self, key, value): - if not callable(value): - msg = ( - 'Values in a `LazyDict` must be generator functions.' - ' These are functions which, when called, return an' - ' instantiated generator.' - ) - raise TypeError(msg) - self.store[key] = value - - def __delitem__(self, key): - del self.store[key] - - def __iter__(self): - return iter(self.store) - - def __len__(self): - return len(self.store) - - -class TractogramItem: - """Class containing information about one streamline. - - :class:`TractogramItem` objects have three public attributes: `streamline`, - `data_for_streamline`, and `data_for_points`. - - Parameters - ---------- - streamline : ndarray shape (N, 3) - Points of this streamline represented as an ndarray of shape (N, 3) - where N is the number of points. - data_for_streamline : dict - Dictionary containing some data associated with this particular - streamline. Each key ``k`` is mapped to a ndarray of shape (Pt,), where - ``Pt`` is the dimension of the data associated with key ``k``. - data_for_points : dict - Dictionary containing some data associated to each point of this - particular streamline. Each key ``k`` is mapped to a ndarray of shape - (Nt, Mk), where ``Nt`` is the number of points of this streamline and - ``Mk`` is the dimension of the data associated with key ``k``. - """ - - def __init__(self, streamline, data_for_streamline, data_for_points): - self.streamline = np.asarray(streamline) - self.data_for_streamline = data_for_streamline - self.data_for_points = data_for_points - - def __iter__(self): - return iter(self.streamline) - - def __len__(self): - return len(self.streamline) - - -class Tractogram: - """Container for streamlines and their data information. - - Streamlines of a tractogram can be in any coordinate system of your - choice as long as you provide the correct `affine_to_rasmm` matrix, at - construction time. When applied to streamlines coordinates, that - transformation matrix should bring the streamlines back to world space - (RAS+ and mm space) [#]_. - - Moreover, when streamlines are mapped back to voxel space [#]_, a - streamline point located at an integer coordinate (i,j,k) is considered - to be at the center of the corresponding voxel. This is in contrast with - other conventions where it might have referred to a corner. - - Attributes - ---------- - streamlines : :class:`ArraySequence` object - Sequence of $T$ streamlines. Each streamline is an ndarray of - shape ($N_t$, 3) where $N_t$ is the number of points of - streamline $t$. - data_per_streamline : :class:`PerArrayDict` object - Dictionary where the items are (str, 2D array). Each key represents a - piece of information $i$ to be kept alongside every streamline, and its - associated value is a 2D array of shape ($T$, $P_i$) where $T$ is the - number of streamlines and $P_i$ is the number of values to store for - that particular piece of information $i$. - data_per_point : :class:`PerArraySequenceDict` object - Dictionary where the items are (str, :class:`ArraySequence`). Each key - represents a piece of information $i$ to be kept alongside every point - of every streamline, and its associated value is an iterable of - ndarrays of shape ($N_t$, $M_i$) where $N_t$ is the number of points - for a particular streamline $t$ and $M_i$ is the number values to store - for that particular piece of information $i$. - - References - ---------- - .. [#] http://nipy.org/nibabel/coordinate_systems.html#naming-reference-spaces - .. [#] http://nipy.org/nibabel/coordinate_systems.html#voxel-coordinates-are-in-voxel-space - """ - - def __init__( - self, streamlines=None, data_per_streamline=None, data_per_point=None, affine_to_rasmm=None - ): - """ - Parameters - ---------- - streamlines : iterable of ndarrays or :class:`ArraySequence`, optional - Sequence of $T$ streamlines. Each streamline is an ndarray of - shape ($N_t$, 3) where $N_t$ is the number of points of - streamline $t$. - data_per_streamline : dict of iterable of ndarrays, optional - Dictionary where the items are (str, iterable). - Each key represents an information $i$ to be kept alongside every - streamline, and its associated value is an iterable of ndarrays of - shape ($P_i$,) where $P_i$ is the number of scalar values to store - for that particular information $i$. - data_per_point : dict of iterable of ndarrays, optional - Dictionary where the items are (str, iterable). - Each key represents an information $i$ to be kept alongside every - point of every streamline, and its associated value is an iterable - of ndarrays of shape ($N_t$, $M_i$) where $N_t$ is the number of - points for a particular streamline $t$ and $M_i$ is the number - scalar values to store for that particular information $i$. - affine_to_rasmm : ndarray of shape (4, 4) or None, optional - Transformation matrix that brings the streamlines contained in - this tractogram to *RAS+* and *mm* space where coordinate (0,0,0) - refers to the center of the voxel. By default, the streamlines - are in an unknown space, i.e. affine_to_rasmm is None. - """ - self._set_streamlines(streamlines) - self.data_per_streamline = data_per_streamline - self.data_per_point = data_per_point - self.affine_to_rasmm = affine_to_rasmm - - @property - def streamlines(self): - return self._streamlines - - def _set_streamlines(self, value): - self._streamlines = ArraySequence(value) - - @property - def data_per_streamline(self): - return self._data_per_streamline - - @data_per_streamline.setter - def data_per_streamline(self, value): - self._data_per_streamline = PerArrayDict( - len(self.streamlines), {} if value is None else value - ) - - @property - def data_per_point(self): - return self._data_per_point - - @data_per_point.setter - def data_per_point(self, value): - self._data_per_point = PerArraySequenceDict( - self.streamlines.total_nb_rows, {} if value is None else value - ) - - @property - def affine_to_rasmm(self): - """Affine bringing streamlines in this tractogram to RAS+mm.""" - return copy.deepcopy(self._affine_to_rasmm) - - @affine_to_rasmm.setter - def affine_to_rasmm(self, value): - if value is not None: - value = np.array(value) - if value.shape != (4, 4): - msg = ( - 'Affine matrix has a shape of (4, 4) but a ndarray with ' - f'shape {value.shape} was provided instead.' - ) - raise ValueError(msg) - - self._affine_to_rasmm = value - - def __iter__(self): - for i in range(len(self.streamlines)): - yield self[i] - - def __getitem__(self, idx): - pts = self.streamlines[idx] - - data_per_streamline = {} - for key in self.data_per_streamline: - data_per_streamline[key] = self.data_per_streamline[key][idx] - - data_per_point = {} - for key in self.data_per_point: - data_per_point[key] = self.data_per_point[key][idx] - - if isinstance(idx, (numbers.Integral, np.integer)): - return TractogramItem(pts, data_per_streamline, data_per_point) - - return Tractogram( - pts, data_per_streamline, data_per_point, affine_to_rasmm=self.affine_to_rasmm - ) - - def __len__(self): - return len(self.streamlines) - - def copy(self): - """Returns a copy of this :class:`Tractogram` object.""" - return copy.deepcopy(self) - - def apply_affine(self, affine, lazy=False): - """Applies an affine transformation on the points of each streamline. - - If `lazy` is not specified, this is performed *in-place*. - - Parameters - ---------- - affine : ndarray of shape (4, 4) - Transformation that will be applied to every streamline. - lazy : {False, True}, optional - If True, streamlines are *not* transformed in-place and a - :class:`LazyTractogram` object is returned. Otherwise, streamlines - are modified in-place. - - Returns - ------- - tractogram : :class:`Tractogram` or :class:`LazyTractogram` object - Tractogram where the streamlines have been transformed according - to the given affine transformation. If the `lazy` option is true, - it returns a :class:`LazyTractogram` object, otherwise it returns a - reference to this :class:`Tractogram` object with updated - streamlines. - """ - if lazy: - lazy_tractogram = LazyTractogram.from_tractogram(self) - return lazy_tractogram.apply_affine(affine) - - if len(self.streamlines) == 0: - return self - - if np.all(affine == np.eye(4)): - return self # No transformation. - - if self.streamlines.is_sliced_view: - # Apply affine only on the selected streamlines. - for i in range(len(self.streamlines)): - self.streamlines[i] = apply_affine(affine, self.streamlines[i]) - else: - self.streamlines._data = apply_affine(affine, self.streamlines._data, inplace=True) - - if self.affine_to_rasmm is not None: - # Update the affine that brings back the streamlines to RASmm. - self.affine_to_rasmm = np.dot(self.affine_to_rasmm, np.linalg.inv(affine)) - - return self - - def to_world(self, lazy=False): - """Brings the streamlines to world space (i.e. RAS+ and mm). - - If `lazy` is not specified, this is performed *in-place*. - - Parameters - ---------- - lazy : {False, True}, optional - If True, streamlines are *not* transformed in-place and a - :class:`LazyTractogram` object is returned. Otherwise, streamlines - are modified in-place. - - Returns - ------- - tractogram : :class:`Tractogram` or :class:`LazyTractogram` object - Tractogram where the streamlines have been sent to world space. - If the `lazy` option is true, it returns a :class:`LazyTractogram` - object, otherwise it returns a reference to this - :class:`Tractogram` object with updated streamlines. - """ - if self.affine_to_rasmm is None: - msg = ( - 'Streamlines are in a unknown space. This error can be' - " avoided by setting the 'affine_to_rasmm' property." - ) - raise ValueError(msg) - - return self.apply_affine(self.affine_to_rasmm, lazy=lazy) - - def extend(self, other): - """Appends the data of another :class:`Tractogram`. - - Data that will be appended includes the streamlines and the content - of both dictionaries `data_per_streamline` and `data_per_point`. - - Parameters - ---------- - other : :class:`Tractogram` object - Its data will be appended to the data of this tractogram. - - Returns - ------- - None - - Notes - ----- - The entries in both dictionaries `self.data_per_streamline` and - `self.data_per_point` must match respectively those contained in - the other tractogram. - """ - self.streamlines.extend(other.streamlines) - self.data_per_streamline.extend(other.data_per_streamline) - self.data_per_point.extend(other.data_per_point) - - def __iadd__(self, other): - self.extend(other) - return self - - def __add__(self, other): - tractogram = self.copy() - tractogram += other - return tractogram - - -class LazyTractogram(Tractogram): - """Lazy container for streamlines and their data information. - - This container behaves lazily as it uses generator functions to manage - streamlines and their data information. This container is thus memory - friendly since it doesn't require having all this data loaded in memory. - - Streamlines of a tractogram can be in any coordinate system of your - choice as long as you provide the correct `affine_to_rasmm` matrix, at - construction time. When applied to streamlines coordinates, that - transformation matrix should bring the streamlines back to world space - (RAS+ and mm space) [#]_. - - Moreover, when streamlines are mapped back to voxel space [#]_, a - streamline point located at an integer coordinate (i,j,k) is considered - to be at the center of the corresponding voxel. This is in contrast with - other conventions where it might have referred to a corner. - - Attributes - ---------- - streamlines : generator function - Generator function yielding streamlines. Each streamline is an - ndarray of shape ($N_t$, 3) where $N_t$ is the number of points of - streamline $t$. - data_per_streamline : instance of :class:`LazyDict` - Dictionary where the items are (str, instantiated generator). - Each key represents a piece of information $i$ to be kept alongside - every streamline, and its associated value is a generator function - yielding that information via ndarrays of shape ($P_i$,) where $P_i$ is - the number of values to store for that particular piece of information - $i$. - data_per_point : :class:`LazyDict` object - Dictionary where the items are (str, instantiated generator). Each key - represents a piece of information $i$ to be kept alongside every point - of every streamline, and its associated value is a generator function - yielding that information via ndarrays of shape ($N_t$, $M_i$) where - $N_t$ is the number of points for a particular streamline $t$ and $M_i$ - is the number of values to store for that particular piece of - information $i$. - - Notes - ----- - LazyTractogram objects do not support indexing currently. - LazyTractogram objects are suited for operations that can be linearized - such as applying an affine transformation or converting streamlines from - one file format to another. - - References - ---------- - .. [#] http://nipy.org/nibabel/coordinate_systems.html#naming-reference-spaces - .. [#] http://nipy.org/nibabel/coordinate_systems.html#voxel-coordinates-are-in-voxel-space - """ - - def __init__( - self, streamlines=None, data_per_streamline=None, data_per_point=None, affine_to_rasmm=None - ): - """ - Parameters - ---------- - streamlines : generator function, optional - Generator function yielding streamlines. Each streamline is an - ndarray of shape ($N_t$, 3) where $N_t$ is the number of points of - streamline $t$. - data_per_streamline : dict of generator functions, optional - Dictionary where the items are (str, generator function). - Each key represents an information $i$ to be kept alongside every - streamline, and its associated value is a generator function - yielding that information via ndarrays of shape ($P_i$,) where - $P_i$ is the number of values to store for that particular - information $i$. - data_per_point : dict of generator functions, optional - Dictionary where the items are (str, generator function). - Each key represents an information $i$ to be kept alongside every - point of every streamline, and its associated value is a generator - function yielding that information via ndarrays of shape - ($N_t$, $M_i$) where $N_t$ is the number of points for a particular - streamline $t$ and $M_i$ is the number of values to store for - that particular information $i$. - affine_to_rasmm : ndarray of shape (4, 4) or None, optional - Transformation matrix that brings the streamlines contained in - this tractogram to *RAS+* and *mm* space where coordinate (0,0,0) - refers to the center of the voxel. By default, the streamlines - are in an unknown space, i.e. affine_to_rasmm is None. - """ - super().__init__(streamlines, data_per_streamline, data_per_point, affine_to_rasmm) - self._nb_streamlines = None - self._data = None - self._affine_to_apply = np.eye(4) - - @classmethod - def from_tractogram(cls, tractogram): - """Creates a :class:`LazyTractogram` object from a :class:`Tractogram` object. - - Parameters - ---------- - tractogram : :class:`Tractgogram` object - Tractogram from which to create a :class:`LazyTractogram` object. - - Returns - ------- - lazy_tractogram : :class:`LazyTractogram` object - New lazy tractogram. - """ - lazy_tractogram = cls(lambda: tractogram.streamlines.copy()) - - # Set data_per_streamline using data_func - def _gen(key): - return lambda: iter(tractogram.data_per_streamline[key]) - - for k in tractogram.data_per_streamline: - lazy_tractogram._data_per_streamline[k] = _gen(k) - - # Set data_per_point using data_func - def _gen(key): - return lambda: iter(tractogram.data_per_point[key]) - - for k in tractogram.data_per_point: - lazy_tractogram._data_per_point[k] = _gen(k) - - lazy_tractogram._nb_streamlines = len(tractogram) - lazy_tractogram.affine_to_rasmm = tractogram.affine_to_rasmm - return lazy_tractogram - - @classmethod - def from_data_func(cls, data_func): - """Creates an instance from a generator function. - - The generator function must yield :class:`TractogramItem` objects. - - Parameters - ---------- - data_func : generator function yielding :class:`TractogramItem` objects - Generator function that whenever is called starts yielding - :class:`TractogramItem` objects that will be used to instantiate a - :class:`LazyTractogram`. - - Returns - ------- - lazy_tractogram : :class:`LazyTractogram` object - New lazy tractogram. - """ - if not callable(data_func): - raise TypeError('`data_func` must be a generator function.') - - lazy_tractogram = cls() - lazy_tractogram._data = data_func - - try: - first_item = next(data_func()) - - # Set data_per_streamline using data_func - def _gen(key): - return lambda: (t.data_for_streamline[key] for t in data_func()) - - data_per_streamline_keys = first_item.data_for_streamline.keys() - for k in data_per_streamline_keys: - lazy_tractogram._data_per_streamline[k] = _gen(k) - - # Set data_per_point using data_func - def _gen(key): - return lambda: (t.data_for_points[key] for t in data_func()) - - data_per_point_keys = first_item.data_for_points.keys() - for k in data_per_point_keys: - lazy_tractogram._data_per_point[k] = _gen(k) - - except StopIteration: - pass - - return lazy_tractogram - - @property - def streamlines(self): - streamlines_gen = iter([]) - if self._streamlines is not None: - streamlines_gen = self._streamlines() - elif self._data is not None: - streamlines_gen = (t.streamline for t in self._data()) - - # Check if we need to apply an affine. - if not np.allclose(self._affine_to_apply, np.eye(4)): - - def _apply_affine(): - for s in streamlines_gen: - yield apply_affine(self._affine_to_apply, s) - - return _apply_affine() - - return streamlines_gen - - def _set_streamlines(self, value): - if value is not None and not callable(value): - msg = ( - '`streamlines` must be a generator function. That is a' - ' function which, when called, returns an instantiated' - ' generator.' - ) - raise TypeError(msg) - self._streamlines = value - - @property - def data_per_streamline(self): - return self._data_per_streamline - - @data_per_streamline.setter - def data_per_streamline(self, value): - self._data_per_streamline = LazyDict(value) - - @property - def data_per_point(self): - return self._data_per_point - - @data_per_point.setter - def data_per_point(self, value): - self._data_per_point = LazyDict(value) - - @property - def data(self): - if self._data is not None: - return self._data() - - def _gen_data(): - data_per_streamline_generators = {} - for k, v in self.data_per_streamline.items(): - data_per_streamline_generators[k] = iter(v) - - data_per_point_generators = {} - for k, v in self.data_per_point.items(): - data_per_point_generators[k] = iter(v) - - for s in self.streamlines: - data_for_streamline = {} - for k, v in data_per_streamline_generators.items(): - data_for_streamline[k] = next(v) - - data_for_points = {} - for k, v in data_per_point_generators.items(): - data_for_points[k] = next(v) - - yield TractogramItem(s, data_for_streamline, data_for_points) - - return _gen_data() - - def __getitem__(self, idx): - raise NotImplementedError('LazyTractogram does not support indexing.') - - def extend(self, other): - msg = 'LazyTractogram does not support concatenation.' - raise NotImplementedError(msg) - - def __iter__(self): - count = 0 - for tractogram_item in self.data: - yield tractogram_item - count += 1 - - # Keep how many streamlines there are in this tractogram. - self._nb_streamlines = count - - def __len__(self): - # Check if we know how many streamlines there are. - if self._nb_streamlines is None: - warn( - 'Number of streamlines will be determined manually by looping' - ' through the streamlines. If you know the actual number of' - ' streamlines, you might want to set it beforehand via' - ' `self.header.nb_streamlines`.', - Warning, - ) - # Count the number of streamlines. - self._nb_streamlines = sum(1 for _ in self.streamlines) - - return self._nb_streamlines - - def copy(self): - """Returns a copy of this :class:`LazyTractogram` object.""" - tractogram = LazyTractogram( - self._streamlines, - self._data_per_streamline, - self._data_per_point, - self.affine_to_rasmm, - ) - tractogram._nb_streamlines = self._nb_streamlines - tractogram._data = self._data - tractogram._affine_to_apply = self._affine_to_apply.copy() - return tractogram - - def apply_affine(self, affine, lazy=True): - """Applies an affine transformation to the streamlines. - - The transformation given by the `affine` matrix is applied after any - other pending transformations to the streamline points. - - Parameters - ---------- - affine : 2D array (4,4) - Transformation matrix that will be applied on each streamline. - lazy : True, optional - Should always be True for :class:`LazyTractogram` object. Doing - otherwise will raise a ValueError. - - Returns - ------- - lazy_tractogram : :class:`LazyTractogram` object - A copy of this :class:`LazyTractogram` instance but with a - transformation to be applied on the streamlines. - """ - if not lazy: - msg = 'LazyTractogram only supports lazy transformations.' - raise ValueError(msg) - - tractogram = self.copy() # New instance. - - # Update the affine that will be applied when returning streamlines. - tractogram._affine_to_apply = np.dot(affine, self._affine_to_apply) - - if tractogram.affine_to_rasmm is not None: - # Update the affine that brings back the streamlines to RASmm. - tractogram.affine_to_rasmm = np.dot(self.affine_to_rasmm, np.linalg.inv(affine)) - return tractogram - - def to_world(self, lazy=True): - """Brings the streamlines to world space (i.e. RAS+ and mm). - - The transformation is applied after any other pending transformations - to the streamline points. - - Parameters - ---------- - lazy : True, optional - Should always be True for :class:`LazyTractogram` object. Doing - otherwise will raise a ValueError. - - Returns - ------- - lazy_tractogram : :class:`LazyTractogram` object - A copy of this :class:`LazyTractogram` instance but with a - transformation to be applied on the streamlines. - """ - if self.affine_to_rasmm is None: - msg = ( - 'Streamlines are in a unknown space. This error can be' - " avoided by setting the 'affine_to_rasmm' property." - ) - raise ValueError(msg) - - return self.apply_affine(self.affine_to_rasmm, lazy=lazy) diff --git a/nibabel/streamlines/tractogram_file.py b/nibabel/streamlines/tractogram_file.py deleted file mode 100644 index 65add3e2f2..0000000000 --- a/nibabel/streamlines/tractogram_file.py +++ /dev/null @@ -1,116 +0,0 @@ -"""Define abstract interface for Tractogram file classes""" - -from abc import ABC, abstractmethod - -from .header import Field - - -class ExtensionWarning(Warning): - """Base class for warnings about tractogram file extension.""" - - -class HeaderWarning(Warning): - """Base class for warnings about tractogram file header.""" - - -class DataWarning(Warning): - """Base class for warnings about tractogram file data.""" - - -class HeaderError(Exception): - """Raised when a tractogram file header contains invalid information.""" - - -class DataError(Exception): - """Raised when data is missing or inconsistent in a tractogram file.""" - - -class abstractclassmethod(classmethod): - __isabstractmethod__ = True - - def __init__(self, callable): - callable.__isabstractmethod__ = True - super().__init__(callable) - - -class TractogramFile(ABC): - """Convenience class to encapsulate tractogram file format.""" - - def __init__(self, tractogram, header=None): - self._tractogram = tractogram - self._header = self.create_empty_header() if header is None else header - - @property - def tractogram(self): - return self._tractogram - - @property - def streamlines(self): - return self.tractogram.streamlines - - @property - def header(self): - return self._header - - @property - def affine(self): - """voxmm -> rasmm affine.""" - return self.header.get(Field.VOXEL_TO_RASMM) - - @abstractclassmethod - def is_correct_format(cls, fileobj): - """Checks if the file has the right streamlines file format. - - Parameters - ---------- - fileobj : string or file-like object - If string, a filename; otherwise an open file-like object - pointing to a streamlines file (and ready to read from the - beginning of the header). - - Returns - ------- - is_correct_format : {True, False} - Returns True if `fileobj` is in the right streamlines file format, - otherwise returns False. - """ - raise NotImplementedError - - @classmethod - def create_empty_header(cls): - """Returns an empty header for this streamlines file format.""" - return {} - - @abstractclassmethod - def load(cls, fileobj, lazy_load=True): - """Loads streamlines from a filename or file-like object. - - Parameters - ---------- - fileobj : string or file-like object - If string, a filename; otherwise an open file-like object - pointing to a streamlines file (and ready to read from the - beginning of the header). - lazy_load : {False, True}, optional - If True, load streamlines in a lazy manner i.e. they will not be - kept in memory. Otherwise, load all streamlines in memory. - - Returns - ------- - tractogram_file : :class:`TractogramFile` object - Returns an object containing tractogram data and header - information. - """ - raise NotImplementedError - - @abstractmethod - def save(self, fileobj): - """Saves streamlines to a filename or file-like object. - - Parameters - ---------- - fileobj : string or file-like object - If string, a filename; otherwise an open file-like object - opened and ready to write. - """ - raise NotImplementedError diff --git a/nibabel/streamlines/trk.py b/nibabel/streamlines/trk.py deleted file mode 100644 index c434619d63..0000000000 --- a/nibabel/streamlines/trk.py +++ /dev/null @@ -1,775 +0,0 @@ -# Definition of trackvis header structure: -# http://www.trackvis.org/docs/?subsect=fileformat - -import os -import string -import struct -import warnings - -import numpy as np - -import nibabel as nib -from nibabel.openers import Opener -from nibabel.orientations import aff2axcodes, axcodes2ornt -from nibabel.volumeutils import endian_codes, native_code, swapped_code - -from .array_sequence import create_arraysequences_from_generator -from .header import Field -from .tractogram import LazyTractogram, Tractogram, TractogramItem -from .tractogram_file import DataError, HeaderError, HeaderWarning, TractogramFile -from .utils import peek_next - -MAX_NB_NAMED_SCALARS_PER_POINT = 10 -MAX_NB_NAMED_PROPERTIES_PER_STREAMLINE = 10 - -# Version 2 adds a 4x4 matrix giving the affine transformation going -# from voxel coordinates in the referenced 3D voxel matrix, to xyz -# coordinates (axes L->R, P->A, I->S). If (0 based) value [3, 3] from -# this matrix is 0, this means the matrix is not recorded. -# See http://docs.scipy.org/doc/numpy/reference/arrays.dtypes.html -header_2_dtd = [ - (Field.MAGIC_NUMBER, 'S6'), - (Field.DIMENSIONS, 'h', 3), - (Field.VOXEL_SIZES, 'f4', 3), - (Field.ORIGIN, 'f4', 3), - (Field.NB_SCALARS_PER_POINT, 'h'), - ('scalar_name', 'S20', MAX_NB_NAMED_SCALARS_PER_POINT), - (Field.NB_PROPERTIES_PER_STREAMLINE, 'h'), - ('property_name', 'S20', MAX_NB_NAMED_PROPERTIES_PER_STREAMLINE), - (Field.VOXEL_TO_RASMM, 'f4', (4, 4)), # New in version 2. - ('reserved', 'S444'), - (Field.VOXEL_ORDER, 'S4'), - ('pad2', 'S4'), - ('image_orientation_patient', 'f4', 6), - ('pad1', 'S2'), - ('invert_x', 'S1'), - ('invert_y', 'S1'), - ('invert_z', 'S1'), - ('swap_xy', 'S1'), - ('swap_yz', 'S1'), - ('swap_zx', 'S1'), - (Field.NB_STREAMLINES, 'i4'), - ('version', 'i4'), - ('hdr_size', 'i4'), -] - -# Full header numpy dtypes -header_2_dtype = np.dtype(header_2_dtd) - - -def get_affine_trackvis_to_rasmm(header): - """Get affine mapping trackvis voxelmm space to RAS+ mm space - - The streamlines in a trackvis file are in 'voxelmm' space, where the - coordinates refer to the corner of the voxel. - - Compute the affine matrix that will bring them back to RAS+ mm space, where - the coordinates refer to the center of the voxel. - - Parameters - ---------- - header : dict or ndarray - Dict or numpy structured array containing trackvis header. - - Returns - ------- - aff_tv2ras : shape (4, 4) array - Affine array mapping coordinates in 'voxelmm' space to RAS+ mm space. - """ - # TRK's streamlines are in 'voxelmm' space, we will compute the - # affine matrix that will bring them back to RAS+ and mm space. - affine = np.eye(4) - - # The affine matrix found in the TRK header requires the points to - # be in the voxel space. - # voxelmm -> voxel - scale = np.eye(4) - scale[range(3), range(3)] /= header[Field.VOXEL_SIZES] - affine = np.dot(scale, affine) - - # TrackVis considers coordinate (0,0,0) to be the corner of the - # voxel whereas streamlines returned assumes (0,0,0) to be the - # center of the voxel. Thus, streamlines are shifted by half a voxel. - offset = np.eye(4) - offset[:-1, -1] -= 0.5 - affine = np.dot(offset, affine) - - # If the voxel order implied by the affine does not match the voxel - # order in the TRK header, change the orientation. - # voxel (header) -> voxel (affine) - vox_order = header[Field.VOXEL_ORDER] - # Input header can be dict or structured array - if hasattr(vox_order, 'item'): # structured array - vox_order = header[Field.VOXEL_ORDER].item() - affine_ornt = ''.join(aff2axcodes(header[Field.VOXEL_TO_RASMM])) - header_ornt = axcodes2ornt(vox_order.decode('latin1').upper()) - affine_ornt = axcodes2ornt(affine_ornt) - ornt = nib.orientations.ornt_transform(header_ornt, affine_ornt) - M = nib.orientations.inv_ornt_aff(ornt, header[Field.DIMENSIONS]) - affine = np.dot(M, affine) - - # Applied the affine found in the TRK header. - # voxel -> rasmm - voxel_to_rasmm = header[Field.VOXEL_TO_RASMM] - affine_voxmm_to_rasmm = np.dot(voxel_to_rasmm, affine) - return affine_voxmm_to_rasmm.astype(np.float32) - - -def get_affine_rasmm_to_trackvis(header): - return np.linalg.inv(get_affine_trackvis_to_rasmm(header)) - - -def encode_value_in_name(value, name, max_name_len=20): - """Return `name` as fixed-length string, appending `value` as string. - - Form output from `name` if `value <= 1` else `name` + ``\x00`` + - str(value). - - Return output as fixed length string length `max_name_len`, padded with - ``\x00``. - - This function also verifies that the modified length of name is less than - `max_name_len`. - - Parameters - ---------- - value : int - Integer value to encode. - name : str - Name to which we may append an ascii / latin-1 representation of - `value`. - max_name_len : int, optional - Maximum length of byte string that output can have. - - Returns - ------- - encoded_name : bytes - Name maybe followed by ``\x00`` and ascii / latin-1 representation of - `value`, padded with ``\x00`` bytes. - """ - if len(name) > max_name_len: - msg = f"Data information named '{name}' is too long (max {max_name_len} characters.)" - raise ValueError(msg) - encoded_name = name if value <= 1 else name + '\x00' + str(value) - if len(encoded_name) > max_name_len: - msg = ( - f"Data information named '{name}' is too long (need to be less" - f' than {max_name_len - (len(str(value)) + 1)} characters ' - 'when storing more than one value for a given data information.' - ) - raise ValueError(msg) - # Fill to the end with zeros - return encoded_name.ljust(max_name_len, '\x00').encode('latin1') - - -def decode_value_from_name(encoded_name): - """Decodes a value that has been encoded in the last bytes of a string. - - Check :func:`encode_value_in_name` to see how the value has been encoded. - - Parameters - ---------- - encoded_name : bytes - Name in which a value has been encoded or not. - - Returns - ------- - name : bytes - Name without the encoded value. - value : int - Value decoded from the name. - """ - encoded_name = encoded_name.decode('latin1') - if len(encoded_name) == 0: - return encoded_name, 0 - - splits = encoded_name.rstrip('\x00').split('\x00') - name = splits[0] - value = 1 - - if len(splits) == 2: - value = int(splits[1]) # Decode value. - elif len(splits) > 2: - # The remaining bytes are not \x00, raising. - msg = ( - f"Wrong scalar_name or property_name: '{encoded_name}'. " - 'Unused characters should be \\x00.' - ) - raise HeaderError(msg) - - return name, value - - -class TrkFile(TractogramFile): - """Convenience class to encapsulate TRK file format. - - Notes - ----- - TrackVis (so its file format: TRK) considers the streamline coordinate - (0,0,0) to be in the corner of the voxel whereas NiBabel's streamlines - internal representation (Voxel space) assumes (0,0,0) to be in the - center of the voxel. - - Thus, streamlines are shifted by half a voxel on load and are shifted - back on save. - """ - - # Constants - MAGIC_NUMBER = b'TRACK' - HEADER_SIZE = 1000 - SUPPORTS_DATA_PER_POINT = True - SUPPORTS_DATA_PER_STREAMLINE = True - - def __init__(self, tractogram, header=None): - """ - Parameters - ---------- - tractogram : :class:`Tractogram` object - Tractogram that will be contained in this :class:`TrkFile`. - - header : dict, optional - Metadata associated to this tractogram file. - - Notes - ----- - Streamlines of the tractogram are assumed to be in *RAS+* - and *mm* space where coordinate (0,0,0) refers to the center - of the voxel. - """ - super().__init__(tractogram, header) - - @classmethod - def is_correct_format(cls, fileobj): - """Check if the file is in TRK format. - - Parameters - ---------- - fileobj : string or file-like object - If string, a filename; otherwise an open file-like object - pointing to TRK file (and ready to read from the beginning - of the TRK header data). Note that calling this function - does not change the file position. - - Returns - ------- - is_correct_format : {True, False} - Returns True if `fileobj` is compatible with TRK format, - otherwise returns False. - """ - with Opener(fileobj) as f: - magic_len = len(cls.MAGIC_NUMBER) - magic_number = f.read(magic_len) - f.seek(-magic_len, os.SEEK_CUR) - return magic_number == cls.MAGIC_NUMBER - - @classmethod - def _default_structarr(cls, endianness=None): - """Return an empty compliant TRK header as numpy structured array""" - dt = header_2_dtype - if endianness is not None: - endianness = endian_codes[endianness] - dt = dt.newbyteorder(endianness) - st_arr = np.zeros((), dtype=dt) - - # Default values - st_arr[Field.MAGIC_NUMBER] = cls.MAGIC_NUMBER - st_arr[Field.VOXEL_SIZES] = np.array((1, 1, 1), dtype='f4') - st_arr[Field.DIMENSIONS] = np.array((1, 1, 1), dtype='h') - st_arr[Field.VOXEL_TO_RASMM] = np.eye(4, dtype='f4') - st_arr[Field.VOXEL_ORDER] = b'RAS' - st_arr['version'] = 2 - st_arr['hdr_size'] = cls.HEADER_SIZE - - return st_arr - - @classmethod - def create_empty_header(cls, endianness=None): - """Return an empty compliant TRK header as dict""" - st_arr = cls._default_structarr(endianness) - return dict(zip(st_arr.dtype.names, st_arr.tolist())) - - @classmethod - def load(cls, fileobj, lazy_load=False): - """Loads streamlines from a filename or file-like object. - - Parameters - ---------- - fileobj : string or file-like object - If string, a filename; otherwise an open file-like object - pointing to TRK file (and ready to read from the beginning - of the TRK header). Note that calling this function - does not change the file position. - lazy_load : {False, True}, optional - If True, load streamlines in a lazy manner i.e. they will not be - kept in memory. Otherwise, load all streamlines in memory. - - Returns - ------- - trk_file : :class:`TrkFile` object - Returns an object containing tractogram data and header - information. - - Notes - ----- - Streamlines of the returned tractogram are assumed to be in *RAS* - and *mm* space where coordinate (0,0,0) refers to the center of the - voxel. - """ - hdr = cls._read_header(fileobj) - - # Find scalars and properties name - data_per_point_slice = {} - if hdr[Field.NB_SCALARS_PER_POINT] > 0: - cpt = 0 - for scalar_field in hdr['scalar_name']: - scalar_name, nb_scalars = decode_value_from_name(scalar_field) - - if nb_scalars == 0: - continue - - slice_obj = slice(cpt, cpt + nb_scalars) - data_per_point_slice[scalar_name] = slice_obj - cpt += nb_scalars - - if cpt < hdr[Field.NB_SCALARS_PER_POINT]: - slice_obj = slice(cpt, hdr[Field.NB_SCALARS_PER_POINT]) - data_per_point_slice['scalars'] = slice_obj - - data_per_streamline_slice = {} - if hdr[Field.NB_PROPERTIES_PER_STREAMLINE] > 0: - cpt = 0 - for property_field in hdr['property_name']: - results = decode_value_from_name(property_field) - property_name, nb_properties = results - - if nb_properties == 0: - continue - - slice_obj = slice(cpt, cpt + nb_properties) - data_per_streamline_slice[property_name] = slice_obj - cpt += nb_properties - - if cpt < hdr[Field.NB_PROPERTIES_PER_STREAMLINE]: - slice_obj = slice(cpt, hdr[Field.NB_PROPERTIES_PER_STREAMLINE]) - data_per_streamline_slice['properties'] = slice_obj - - if lazy_load: - - def _read(): - for pts, scals, props in cls._read(fileobj, hdr): - items = data_per_point_slice.items() - data_for_points = {k: scals[:, v] for k, v in items} - items = data_per_streamline_slice.items() - data_for_streamline = {k: props[v] for k, v in items} - yield TractogramItem(pts, data_for_streamline, data_for_points) - - tractogram = LazyTractogram.from_data_func(_read) - - else: - # Speed up loading by guessing a suitable buffer size. - with Opener(fileobj) as f: - old_file_position = f.tell() - f.seek(0, os.SEEK_END) - size = f.tell() - f.seek(old_file_position, os.SEEK_SET) - - # Buffer size is in mega bytes. - mbytes = size // (1024 * 1024) - sizes = [mbytes, 4, 4] - if hdr['nb_scalars_per_point'] > 0: - sizes = [mbytes // 2, mbytes // 2, 4] - - trk_reader = cls._read(fileobj, hdr) - arr_seqs = create_arraysequences_from_generator(trk_reader, n=3, buffer_sizes=sizes) - streamlines, scalars, properties = arr_seqs - properties = np.asarray(properties) # Actually a 2d array. - tractogram = Tractogram(streamlines) - - for name, slice_ in data_per_point_slice.items(): - tractogram.data_per_point[name] = scalars[:, slice_] - - for name, slice_ in data_per_streamline_slice.items(): - tractogram.data_per_streamline[name] = properties[:, slice_] - - tractogram.affine_to_rasmm = get_affine_trackvis_to_rasmm(hdr) - tractogram = tractogram.to_world() - - return cls(tractogram, header=hdr) - - def save(self, fileobj): - """Save tractogram to a filename or file-like object using TRK format. - - Parameters - ---------- - fileobj : string or file-like object - If string, a filename; otherwise an open file-like object - pointing to TRK file (and ready to write from the beginning - of the TRK header data). - """ - # Enforce little-endian byte order for header - header = self._default_structarr(endianness='little') - - # Override hdr's fields by those contained in `header`. - for k, v in self.header.items(): - if k in header_2_dtype.fields.keys(): - header[k] = v - - # By default, the voxel order is LPS. - # http://trackvis.org/blog/forum/diffusion-toolkit-usage/interpretation-of-track-point-coordinates - if header[Field.VOXEL_ORDER] == b'': - header[Field.VOXEL_ORDER] = b'LPS' - - # Keep counts for correcting incoherent fields or warn. - nb_streamlines = 0 - nb_points = 0 - nb_scalars = 0 - nb_properties = 0 - - with Opener(fileobj, mode='wb') as f: - # Keep track of the beginning of the header. - beginning = f.tell() - - # Write temporary header that we will update at the end - f.write(header.tobytes()) - - i4_dtype = np.dtype(' MAX_NB_NAMED_PROPERTIES_PER_STREAMLINE: - msg = ( - f'Can only store {MAX_NB_NAMED_SCALARS_PER_POINT} named ' - "data_per_streamline (also known as 'properties' in the " - 'TRK format).' - ) - raise ValueError(msg) - - data_for_streamline_keys = sorted(data_for_streamline.keys()) - property_name = np.zeros(MAX_NB_NAMED_PROPERTIES_PER_STREAMLINE, dtype='S20') - for i, name in enumerate(data_for_streamline_keys): - # Append number of values as ascii to zero-terminated name - # to encode number of values into trackvis name. - nb_values = data_for_streamline[name].shape[-1] - property_name[i] = encode_value_in_name(nb_values, name) - header['property_name'][:] = property_name - - # Update field 'scalar_name' using 'tractogram.data_per_point'. - data_for_points = first_item.data_for_points - if len(data_for_points) > MAX_NB_NAMED_SCALARS_PER_POINT: - msg = ( - f'Can only store {MAX_NB_NAMED_SCALARS_PER_POINT} ' - "named data_per_point (also known as 'scalars' in " - 'the TRK format).' - ) - raise ValueError(msg) - - data_for_points_keys = sorted(data_for_points.keys()) - scalar_name = np.zeros(MAX_NB_NAMED_SCALARS_PER_POINT, dtype='S20') - for i, name in enumerate(data_for_points_keys): - # Append number of values as ascii to zero-terminated name - # to encode number of values into trackvis name. - nb_values = data_for_points[name].shape[-1] - scalar_name[i] = encode_value_in_name(nb_values, name) - header['scalar_name'][:] = scalar_name - - for t in tractogram: - if any(len(d) != len(t.streamline) for d in t.data_for_points.values()): - raise DataError('Missing scalars for some points!') - - points = np.asarray(t.streamline) - scalars = [np.asarray(t.data_for_points[k]) for k in data_for_points_keys] - scalars = np.concatenate([np.ndarray((len(points), 0))] + scalars, axis=1) - properties = [ - np.asarray(t.data_for_streamline[k]) for k in data_for_streamline_keys - ] - properties = np.concatenate([np.array([])] + properties).astype(f4_dtype) - - data = struct.pack(i4_dtype.str[:-1], len(points)) - pts_scalars = np.concatenate([points, scalars], axis=1).astype(f4_dtype) - data += pts_scalars.tobytes() - data += properties.tobytes() - f.write(data) - - nb_streamlines += 1 - nb_points += len(points) - nb_scalars += scalars.size - nb_properties += len(properties) - - # Use those values to update the header. - nb_scalars_per_point = nb_scalars / nb_points - nb_properties_per_streamline = nb_properties / nb_streamlines - - # Check for errors - if nb_scalars_per_point != int(nb_scalars_per_point): - msg = 'Nb. of scalars differs from one point to another!' - raise DataError(msg) - - if nb_properties_per_streamline != int(nb_properties_per_streamline): - msg = 'Nb. of properties differs from one streamline to another!' - raise DataError(msg) - - header[Field.NB_STREAMLINES] = nb_streamlines - header[Field.NB_SCALARS_PER_POINT] = nb_scalars_per_point - header[Field.NB_PROPERTIES_PER_STREAMLINE] = nb_properties_per_streamline - - # Overwrite header with updated one. - f.seek(beginning, os.SEEK_SET) - f.write(header.tobytes()) - - @staticmethod - def _read_header(fileobj): - """Reads a TRK header from a file. - - Parameters - ---------- - fileobj : string or file-like object - If string, a filename; otherwise an open file-like object - pointing to TRK file (and ready to read from the beginning - of the TRK header). Note that calling this function - does not change the file position. - - Returns - ------- - header : dict - Metadata associated with this tractogram file. - """ - # Record start position if this is a file-like object - start_position = fileobj.tell() if hasattr(fileobj, 'tell') else None - - with Opener(fileobj) as f: - # Reading directly from a file into a (mutable) bytearray enables a zero-copy - # cast to a mutable numpy object with frombuffer - header_buf = bytearray(header_2_dtype.itemsize) - f.readinto(header_buf) - header_rec = np.frombuffer(buffer=header_buf, dtype=header_2_dtype) - # Check endianness - endianness = native_code - if header_rec['hdr_size'] != TrkFile.HEADER_SIZE: - endianness = swapped_code - - # Swap byte order - header_rec = header_rec.view(header_rec.dtype.newbyteorder()) - if header_rec['hdr_size'] != TrkFile.HEADER_SIZE: - msg = ( - f'Invalid hdr_size: {header_rec["hdr_size"]} ' - f'instead of {TrkFile.HEADER_SIZE}' - ) - raise HeaderError(msg) - - if header_rec['version'] == 1: - # There is no 4x4 matrix for voxel to RAS transformation. - header_rec[Field.VOXEL_TO_RASMM] = np.zeros((4, 4)) - elif header_rec['version'] == 3: - warnings.warn( - 'Parsing a TRK v3 file as v2. Some features may not be handled correctly.', - HeaderWarning, - ) - elif header_rec['version'] in (2, 3): - pass # Nothing more to do. - else: - raise HeaderError( - 'NiBabel only supports versions 1 and 2 of the Trackvis file format' - ) - - # Convert the first record of `header_rec` into a dictionary - header = dict(zip(header_rec.dtype.names, header_rec[0])) - header[Field.ENDIANNESS] = endianness - - # If vox_to_ras[3][3] is 0, it means the matrix is not recorded. - if header[Field.VOXEL_TO_RASMM][3][3] == 0: - header[Field.VOXEL_TO_RASMM] = np.eye(4, dtype=np.float32) - warnings.warn( - "Field 'vox_to_ras' in the TRK's header was not recorded. " - "Will continue assuming it's the identity.", - HeaderWarning, - ) - - # Check that the 'vox_to_ras' affine is valid, i.e. should be - # able to determine the axis directions. - axcodes = aff2axcodes(header[Field.VOXEL_TO_RASMM]) - if None in axcodes: - msg = ( - "The 'vox_to_ras' affine is invalid! Could not" - ' determine the axis directions from it.\n' - f'{header[Field.VOXEL_TO_RASMM]}' - ) - raise HeaderError(msg) - - # By default, the voxel order is LPS. - # http://trackvis.org/blog/forum/diffusion-toolkit-usage/interpretation-of-track-point-coordinates - if header[Field.VOXEL_ORDER] == b'': - msg = ( - "Voxel order is not specified, will assume 'LPS' since" - " it is Trackvis software's default." - ) - warnings.warn(msg, HeaderWarning) - header[Field.VOXEL_ORDER] = b'LPS' - - # Keep the file position where the data begin. - header['_offset_data'] = f.tell() - - # Set the file position where it was, if it was previously open. - if start_position is not None: - fileobj.seek(start_position, os.SEEK_SET) - - return header - - @staticmethod - def _read(fileobj, header): - """Return generator that reads TRK data from `fileobj` given `header` - - Parameters - ---------- - fileobj : string or file-like object - If string, a filename; otherwise an open file-like object - pointing to TRK file (and ready to read from the beginning - of the TRK header). Note that calling this function - does not change the file position. - header : dict - Metadata associated with this tractogram file. - - Yields - ------ - data : tuple of ndarrays - Length 3 tuple of streamline data of form (points, scalars, - properties), where: - - * points: ndarray of shape (n_pts, 3) - * scalars: ndarray of shape (n_pts, nb_scalars_per_point) - * properties: ndarray of shape (nb_properties_per_point,) - """ - i4_dtype = np.dtype(header[Field.ENDIANNESS] + 'i4') - f4_dtype = np.dtype(header[Field.ENDIANNESS] + 'f4') - - with Opener(fileobj) as f: - start_position = f.tell() - - nb_pts_and_scalars = int(3 + header[Field.NB_SCALARS_PER_POINT]) - pts_and_scalars_size = int(nb_pts_and_scalars * f4_dtype.itemsize) - nb_properties = header[Field.NB_PROPERTIES_PER_STREAMLINE] - properties_size = int(nb_properties * f4_dtype.itemsize) - - # Set the file position at the beginning of the data. - f.seek(header['_offset_data'], os.SEEK_SET) - - # If 'count' field is 0, i.e. not provided, we have to loop - # until the EOF. - nb_streamlines = header[Field.NB_STREAMLINES] - if nb_streamlines == 0: - nb_streamlines = np.inf - - count = 0 - nb_pts_dtype = i4_dtype.str[:-1] - while count < nb_streamlines: - nb_pts_str = f.read(i4_dtype.itemsize) - - # Check if we reached EOF - if len(nb_pts_str) == 0: - break - - # Read number of points of the next streamline. - nb_pts = struct.unpack(nb_pts_dtype, nb_pts_str)[0] - - # Read streamline's data - points_and_scalars = np.ndarray( - shape=(nb_pts, nb_pts_and_scalars), - dtype=f4_dtype, - buffer=f.read(nb_pts * pts_and_scalars_size), - ) - - points = points_and_scalars[:, :3] - scalars = points_and_scalars[:, 3:] - - # Read properties - properties = np.ndarray( - shape=(nb_properties,), dtype=f4_dtype, buffer=f.read(properties_size) - ) - - yield points, scalars, properties - count += 1 - - # In case the 'count' field was not provided. - header[Field.NB_STREAMLINES] = count - - # Set the file position where it was (in case it was already open). - f.seek(start_position, os.SEEK_CUR) - - def __str__(self): - """Gets a formatted string of the header of a TRK file. - - Returns - ------- - info : string - Header information relevant to the TRK format. - """ - vars = self.header.copy() - for attr in dir(Field): - if attr[0] in string.ascii_uppercase: - hdr_field = getattr(Field, attr) - if hdr_field in vars: - vars[attr] = vars[hdr_field] - - nb_scalars = self.header[Field.NB_SCALARS_PER_POINT] - scalar_names = [ - s.decode('latin-1') for s in vars['scalar_name'][:nb_scalars] if len(s) > 0 - ] - vars['scalar_names'] = '\n '.join(scalar_names) - nb_properties = self.header[Field.NB_PROPERTIES_PER_STREAMLINE] - property_names = [ - s.decode('latin-1') for s in vars['property_name'][:nb_properties] if len(s) > 0 - ] - vars['property_names'] = '\n '.join(property_names) - # Make all byte strings into strings - # Fixes recursion error on Python 3.3 - vars = {k: v.decode('latin-1') if hasattr(v, 'decode') else v for k, v in vars.items()} - return """\ -MAGIC NUMBER: {MAGIC_NUMBER} -v.{version} -dim: {DIMENSIONS} -voxel_sizes: {VOXEL_SIZES} -origin: {ORIGIN} -nb_scalars: {NB_SCALARS_PER_POINT} -scalar_names:\n {scalar_names} -nb_properties: {NB_PROPERTIES_PER_STREAMLINE} -property_names:\n {property_names} -vox_to_world:\n{VOXEL_TO_RASMM} -voxel_order: {VOXEL_ORDER} -image_orientation_patient: {image_orientation_patient} -pad1: {pad1} -pad2: {pad2} -invert_x: {invert_x} -invert_y: {invert_y} -invert_z: {invert_z} -swap_xy: {swap_xy} -swap_yz: {swap_yz} -swap_zx: {swap_zx} -n_count: {NB_STREAMLINES} -hdr_size: {hdr_size}""".format(**vars) diff --git a/nibabel/streamlines/utils.py b/nibabel/streamlines/utils.py deleted file mode 100644 index 80764700f2..0000000000 --- a/nibabel/streamlines/utils.py +++ /dev/null @@ -1,52 +0,0 @@ -import itertools - -import nibabel - - -def get_affine_from_reference(ref): - """Returns the affine defining the reference space. - - Parameters - ---------- - ref : str or :class:`Nifti1Image` object or ndarray shape (4, 4) - If str then it's the filename of reference file that will be loaded - using :func:`nibabel.load` in order to obtain the affine. - If :class:`Nifti1Image` object then the affine is obtained from it. - If ndarray shape (4, 4) then it's the affine. - - Returns - ------- - affine : ndarray (4, 4) - Transformation matrix mapping voxel space to RAS+mm space. - """ - if hasattr(ref, 'affine'): - return ref.affine - - if hasattr(ref, 'shape'): - if ref.shape != (4, 4): - msg = '`ref` needs to be a numpy array with shape (4, 4)!' - raise ValueError(msg) - - return ref - - # Assume `ref` is the name of a neuroimaging file. - return nibabel.load(ref).affine - - -def peek_next(iterable): - """Peek next element of iterable. - - Parameters - ---------- - iterable - Iterable to peek the next element from. - - Returns - ------- - next_item - Element peeked from `iterable`. - new_iterable - Iterable behaving like if the original `iterable` was untouched. - """ - next_item = next(iterable) - return next_item, itertools.chain([next_item], iterable) diff --git a/nibabel/testing/__init__.py b/nibabel/testing/__init__.py deleted file mode 100644 index b42baf2955..0000000000 --- a/nibabel/testing/__init__.py +++ /dev/null @@ -1,246 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Utilities for testing""" - -from __future__ import annotations - -import os -import re -import sys -import typing as ty -import unittest -import warnings -from contextlib import nullcontext -from importlib.resources import as_file, files -from itertools import zip_longest - -import numpy as np -import pytest -from numpy.testing import assert_array_equal - -from .helpers import assert_data_similar, bytesio_filemap, bytesio_round_trip -from .np_features import memmap_after_ufunc - -if ty.TYPE_CHECKING: - from importlib.resources.abc import Traversable - - -def get_test_data( - subdir: ty.Literal['gifti', 'nicom', 'externals'] | None = None, - fname: str | None = None, -) -> Traversable: - parts: tuple[str, ...] - if subdir is None: - parts = ('tests', 'data') - elif subdir in ('gifti', 'nicom', 'externals'): - parts = (subdir, 'tests', 'data') - else: - raise ValueError(f'Unknown test data directory: {subdir}') - - if fname is not None: - parts += (fname,) - - return files('nibabel').joinpath(*parts) - - -# set path to example data -data_path = get_test_data() - - -def assert_dt_equal(a, b): - """Assert two numpy dtype specifiers are equal - - Avoids failed comparison between int32 / int64 and intp - """ - assert np.dtype(a).str == np.dtype(b).str - - -def assert_allclose_safely(a, b, match_nans=True, rtol=1e-5, atol=1e-8): - """Allclose in integers go all wrong for large integers""" - a = np.atleast_1d(a) # 0d arrays cannot be indexed - a, b = np.broadcast_arrays(a, b) - if match_nans: - nans = np.isnan(a) - assert_array_equal(nans, np.isnan(b)) - to_test = ~nans - else: - to_test = np.ones(a.shape, dtype=bool) - # Deal with float128 inf comparisons (bug in numpy 1.9.2) - # np.allclose(np.float128(np.inf), np.float128(np.inf)) == False - to_test = to_test & (a != b) - a = a[to_test] - b = b[to_test] - if a.dtype.kind in 'ui': - a = a.astype(float) - if b.dtype.kind in 'ui': - b = b.astype(float) - assert np.allclose(a, b, rtol=rtol, atol=atol) - - -def assert_arrays_equal(arrays1, arrays2): - """Check two iterables yield the same sequence of arrays.""" - for arr1, arr2 in zip_longest(arrays1, arrays2, fillvalue=None): - assert arr1 is not None and arr2 is not None - assert_array_equal(arr1, arr2) - - -def assert_re_in(regex, c, flags=0): - """Assert that container (list, str, etc) contains entry matching the regex""" - if not isinstance(c, (list, tuple)): - c = [c] - for e in c: - if re.match(regex, e, flags=flags): - return - raise AssertionError(f'Not a single entry matched {regex!r} in {c!r}') - - -def get_fresh_mod(mod_name=__name__): - # Get this module, with warning registry empty - my_mod = sys.modules[mod_name] - try: - my_mod.__warningregistry__.clear() - except AttributeError: - pass - return my_mod - - -class clear_and_catch_warnings(warnings.catch_warnings): - """Context manager that resets warning registry for catching warnings - - Warnings can be slippery, because, whenever a warning is triggered, Python - adds a ``__warningregistry__`` member to the *calling* module. This makes - it impossible to retrigger the warning in this module, whatever you put in - the warnings filters. This context manager accepts a sequence of `modules` - as a keyword argument to its constructor and: - - * stores and removes any ``__warningregistry__`` entries in given `modules` - on entry; - * resets ``__warningregistry__`` to its previous state on exit. - - This makes it possible to trigger any warning afresh inside the context - manager without disturbing the state of warnings outside. - - For compatibility with Python 3.0, please consider all arguments to be - keyword-only. - - Parameters - ---------- - record : bool, optional - Specifies whether warnings should be captured by a custom - implementation of ``warnings.showwarning()`` and be appended to a list - returned by the context manager. Otherwise None is returned by the - context manager. The objects appended to the list are arguments whose - attributes mirror the arguments to ``showwarning()``. - - NOTE: nibabel difference from numpy: default is True - - modules : sequence, optional - Sequence of modules for which to reset warnings registry on entry and - restore on exit - - Examples - -------- - >>> import warnings - >>> with clear_and_catch_warnings(modules=[np.lib.scimath]): - ... warnings.simplefilter('always') - ... # do something that raises a warning in np.lib.scimath - ... _ = np.arccos(90) - """ - - class_modules = () - - def __init__(self, record=True, modules=()): - self.modules = set(modules).union(self.class_modules) - self._warnreg_copies = {} - super().__init__(record=record) - - def __enter__(self): - for mod in self.modules: - if hasattr(mod, '__warningregistry__'): - mod_reg = mod.__warningregistry__ - self._warnreg_copies[mod] = mod_reg.copy() - mod_reg.clear() - return super().__enter__() - - def __exit__(self, *exc_info): - super().__exit__(*exc_info) - for mod in self.modules: - if hasattr(mod, '__warningregistry__'): - mod.__warningregistry__.clear() - if mod in self._warnreg_copies: - mod.__warningregistry__.update(self._warnreg_copies[mod]) - - -class error_warnings(clear_and_catch_warnings): - """Context manager to check for warnings as errors. Usually used with - ``assert_raises`` in the with block - - Examples - -------- - >>> with error_warnings(): - ... try: - ... warnings.warn('Message', UserWarning) - ... except UserWarning: - ... print('I consider myself warned') - I consider myself warned - """ - - filter = 'error' - - def __enter__(self): - mgr = super().__enter__() - warnings.simplefilter(self.filter) - return mgr - - -class suppress_warnings(error_warnings): - """Version of ``catch_warnings`` class that suppresses warnings""" - - filter = 'ignore' - - -EXTRA_SET = os.environ.get('NIPY_EXTRA_TESTS', '').split(',') - - -def runif_extra_has(test_str): - """Decorator checks to see if NIPY_EXTRA_TESTS env var contains test_str""" - return unittest.skipUnless(test_str in EXTRA_SET, f'Skip {test_str} tests.') - - -def assert_arr_dict_equal(dict1, dict2): - """Assert that two dicts are equal, where dicts contain arrays""" - assert set(dict1) == set(dict2) - for key, value1 in dict1.items(): - value2 = dict2[key] - assert_array_equal(value1, value2) - - -def expires(version): - """Decorator to mark a test as xfail with ExpiredDeprecationError after version""" - from packaging.version import Version - - from nibabel import __version__ as nbver - from nibabel.deprecator import ExpiredDeprecationError - - if Version(nbver) < Version(version): - return lambda x: x - - return pytest.mark.xfail(raises=ExpiredDeprecationError) - - -def deprecated_to(version): - """Context manager to expect DeprecationWarnings until a given version""" - from packaging.version import Version - - from nibabel import __version__ as nbver - - if Version(nbver) < Version(version): - return pytest.deprecated_call() - - return nullcontext() diff --git a/nibabel/testing/helpers.py b/nibabel/testing/helpers.py deleted file mode 100644 index ad4bf258cd..0000000000 --- a/nibabel/testing/helpers.py +++ /dev/null @@ -1,54 +0,0 @@ -"""Helper functions for tests""" - -from io import BytesIO - -import numpy as np - -from ..optpkg import optional_package - -have_scipy = optional_package('scipy.io')[1] - -from numpy.testing import assert_array_equal - - -def bytesio_filemap(klass): - """Return bytes io filemap for this image class `klass`""" - file_map = klass.make_file_map() - for fileholder in file_map.values(): - fileholder.fileobj = BytesIO() - fileholder.pos = 0 - return file_map - - -def bytesio_round_trip(img): - """Save then load image from bytesio""" - klass = img.__class__ - bytes_map = bytesio_filemap(klass) - img.to_file_map(bytes_map) - return klass.from_file_map(bytes_map) - - -def assert_data_similar(arr, params): - """Check data is the same if recorded, otherwise check summaries - - Helper function to test image array data `arr` against record in `params`, - where record can be the array itself, or summary values from the array. - - Parameters - ---------- - arr : array-like - Something that results in an array after ``np.asarry(arr)`` - params : mapping - Mapping that has either key ``data`` with value that is array-like, or - key ``data_summary`` with value a dict having keys ``min``, ``max``, - ``mean`` - """ - if 'data' in params: - assert_array_equal(arr, params['data']) - return - summary = params['data_summary'] - real_arr = np.asarray(arr) - assert np.allclose( - (real_arr.min(), real_arr.max(), real_arr.mean()), - (summary['min'], summary['max'], summary['mean']), - ) diff --git a/nibabel/testing/np_features.py b/nibabel/testing/np_features.py deleted file mode 100644 index dd21aac2c0..0000000000 --- a/nibabel/testing/np_features.py +++ /dev/null @@ -1,16 +0,0 @@ -"""Look for changes in numpy behavior over versions""" - -from functools import cache - -import numpy as np - - -@cache -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. - """ - with open(__file__, 'rb') as fobj: - mm_arr = np.memmap(fobj, mode='r', shape=(10,), dtype=np.uint8) - return isinstance(mm_arr + 1, np.memmap) diff --git a/nibabel/tests/__init__.py b/nibabel/tests/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/nibabel/tests/conftest.py b/nibabel/tests/conftest.py deleted file mode 100644 index fb13708450..0000000000 --- a/nibabel/tests/conftest.py +++ /dev/null @@ -1,18 +0,0 @@ -import pytest - -from ..spatialimages import supported_np_types - - -# Generate dynamic fixtures -def pytest_generate_tests(metafunc): - if 'supported_dtype' in metafunc.fixturenames: - if metafunc.cls is None or not metafunc.cls.image_class: - raise pytest.UsageError( - 'Attempting to use supported_dtype fixture outside an image test case' - ) - # xdist needs a consistent ordering, so sort by class name - supported_dtypes = sorted( - supported_np_types(metafunc.cls.image_class.header_class()), - key=lambda x: x.__name__, - ) - metafunc.parametrize('supported_dtype', supported_dtypes) diff --git a/nibabel/tests/data/.gitignore b/nibabel/tests/data/.gitignore deleted file mode 100644 index 215e61ce01..0000000000 --- a/nibabel/tests/data/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -anat_moved.nii -resampled_functional.nii diff --git a/nibabel/tests/data/0.dcm b/nibabel/tests/data/0.dcm deleted file mode 100644 index 05d7c875d7..0000000000 Binary files a/nibabel/tests/data/0.dcm and /dev/null differ diff --git a/nibabel/tests/data/1.dcm b/nibabel/tests/data/1.dcm deleted file mode 100644 index 0920b60eaf..0000000000 Binary files a/nibabel/tests/data/1.dcm and /dev/null differ diff --git a/nibabel/tests/data/ADC_Map.PAR b/nibabel/tests/data/ADC_Map.PAR deleted file mode 100644 index c1728a3d7a..0000000000 --- a/nibabel/tests/data/ADC_Map.PAR +++ /dev/null @@ -1,124 +0,0 @@ -# === DATA DESCRIPTION FILE ====================================================== -# -# CAUTION - Investigational device. -# Limited by Federal Law to investigational use. -# -# Dataset name: e:\dicom\\ADC_Map -# -# CLINICAL TRYOUT Research image export tool V4.2 -# -# === GENERAL INFORMATION ======================================================== -# -. Patient name : ADC_Map -. Examination name : ADC_Map -. Protocol name : ADC_Map -. Examination date/time : 2018.01.01 / 01:01:01 -. Series Type : Image MRSeries -. Acquisition nr : 8 -. Reconstruction nr : 3 -. Scan Duration [sec] : 276 -. Max. number of cardiac phases : 1 -. Max. number of echoes : 1 -. Max. number of slices/locations : 22 -. Max. number of dynamics : 1 -. Max. number of mixes : 1 -. Patient position : Feet First Supine -. Preparation direction : Anterior-Posterior -. Technique : DwiSE -. Scan resolution (x, y) : 132 134 -. Scan mode : MS -. Repetition time [ms] : 4600.000 -. FOV (ap,fh,rl) [mm] : 80.000 78.500 80.000 -. Water Fat shift [pixels] : 63.247 -. Angulation midslice(ap,fh,rl)[degr]: 0.109 -18.122 13.705 -. Off Centre midslice(ap,fh,rl) [mm] : 25.981 -8.163 16.342 -. Flow compensation <0=no 1=yes> ? : 0 -. Presaturation <0=no 1=yes> ? : 0 -. Phase encoding velocity [cm/sec] : 0.000000 0.000000 0.000000 -. MTC <0=no 1=yes> ? : 0 -. SPIR <0=no 1=yes> ? : 1 -. EPI factor <0,1=no EPI> : 151 -. Dynamic scan <0=no 1=yes> ? : 0 -. Diffusion <0=no 1=yes> ? : 1 -. Diffusion echo time [ms] : 0.0000 -. Max. number of diffusion values : 3 -. Max. number of gradient orients : 4 -. Number of label types <0=no ASL> : 0 -# -# === PIXEL VALUES ============================================================= -# PV = pixel value in REC file, FP = floating point value, DV = displayed value on console -# RS = rescale slope, RI = rescale intercept, SS = scale slope -# DV = PV * RS + RI FP = DV / (RS * SS) -# -# === IMAGE INFORMATION DEFINITION ============================================= -# The rest of this file contains ONE line per image, this line contains the following information: -# -# slice number (integer) -# echo number (integer) -# dynamic scan number (integer) -# cardiac phase number (integer) -# image_type_mr (integer) -# scanning sequence (integer) -# index in REC file (in images) (integer) -# image pixel size (in bits) (integer) -# scan percentage (integer) -# recon resolution (x y) (2*integer) -# rescale intercept (float) -# rescale slope (float) -# scale slope (float) -# window center (integer) -# window width (integer) -# image angulation (ap,fh,rl in degrees ) (3*float) -# image offcentre (ap,fh,rl in mm ) (3*float) -# slice thickness (in mm ) (float) -# slice gap (in mm ) (float) -# image_display_orientation (integer) -# slice orientation ( TRA/SAG/COR ) (integer) -# fmri_status_indication (integer) -# image_type_ed_es (end diast/end syst) (integer) -# pixel spacing (x,y) (in mm) (2*float) -# echo_time (float) -# dyn_scan_begin_time (float) -# trigger_time (float) -# diffusion_b_factor (float) -# number of averages (integer) -# image_flip_angle (in degrees) (float) -# cardiac frequency (bpm) (integer) -# minimum RR-interval (in ms) (integer) -# maximum RR-interval (in ms) (integer) -# TURBO factor <0=no turbo> (integer) -# Inversion delay (in ms) (float) -# diffusion b value number (imagekey!) (integer) -# gradient orientation number (imagekey!) (integer) -# contrast type (string) -# diffusion anisotropy type (string) -# diffusion (ap, fh, rl) (3*float) -# label type (ASL) (imagekey!) (integer) -# -# === IMAGE INFORMATION ========================================================== -# sl ec dyn ph ty idx pix scan% rec size (re)scale window angulation offcentre thick gap info spacing echo dtime ttime diff avg flip freq RR-int turbo delay b grad cont anis diffusion L.ty - - 1 1 1 1 11 5 0 16 101 144 144 0.00000 0.00067 1.46578e-001 1 1 0.11 -18.12 13.70 34.69 -43.87 16.27 5.000 -1.500 0 1 0 2 0.556 0.556 134.09 0.00 0.00 0.00 1 90.00 0 0 0 151 0.0 1 4 0 0 0.000 0.000 0.000 1 - 2 1 1 1 11 5 1 16 101 144 144 0.00000 0.00067 1.46578e-001 1 1 0.11 -18.12 13.70 33.86 -40.47 16.28 5.000 -1.500 0 1 0 2 0.556 0.556 134.09 0.00 0.00 0.00 1 90.00 0 0 0 151 0.0 1 4 0 0 0.000 0.000 0.000 1 - 3 1 1 1 11 5 2 16 101 144 144 0.00000 0.00067 1.46578e-001 1 1 0.11 -18.12 13.70 33.03 -37.07 16.29 5.000 -1.500 0 1 0 2 0.556 0.556 134.09 0.00 0.00 0.00 1 90.00 0 0 0 151 0.0 1 4 0 0 0.000 0.000 0.000 1 - 4 1 1 1 11 5 3 16 101 144 144 0.00000 0.00067 1.46578e-001 1 1 0.11 -18.12 13.70 32.20 -33.67 16.29 5.000 -1.500 0 1 0 2 0.556 0.556 134.09 0.00 0.00 0.00 1 90.00 0 0 0 151 0.0 1 4 0 0 0.000 0.000 0.000 1 - 5 1 1 1 11 5 4 16 101 144 144 0.00000 0.00067 1.46578e-001 1 1 0.11 -18.12 13.70 31.37 -30.27 16.30 5.000 -1.500 0 1 0 2 0.556 0.556 134.09 0.00 0.00 0.00 1 90.00 0 0 0 151 0.0 1 4 0 0 0.000 0.000 0.000 1 - 6 1 1 1 11 5 5 16 101 144 144 0.00000 0.00067 1.46578e-001 1 1 0.11 -18.12 13.70 30.54 -26.86 16.31 5.000 -1.500 0 1 0 2 0.556 0.556 134.09 0.00 0.00 0.00 1 90.00 0 0 0 151 0.0 1 4 0 0 0.000 0.000 0.000 1 - 7 1 1 1 11 5 6 16 101 144 144 0.00000 0.00067 1.46578e-001 1 1 0.11 -18.12 13.70 29.71 -23.46 16.31 5.000 -1.500 0 1 0 2 0.556 0.556 134.09 0.00 0.00 0.00 1 90.00 0 0 0 151 0.0 1 4 0 0 0.000 0.000 0.000 1 - 8 1 1 1 11 5 7 16 101 144 144 0.00000 0.00067 1.46578e-001 1 1 0.11 -18.12 13.70 28.88 -20.06 16.32 5.000 -1.500 0 1 0 2 0.556 0.556 134.09 0.00 0.00 0.00 1 90.00 0 0 0 151 0.0 1 4 0 0 0.000 0.000 0.000 1 - 9 1 1 1 11 5 8 16 101 144 144 0.00000 0.00067 1.46578e-001 1 1 0.11 -18.12 13.70 28.05 -16.66 16.33 5.000 -1.500 0 1 0 2 0.556 0.556 134.09 0.00 0.00 0.00 1 90.00 0 0 0 151 0.0 1 4 0 0 0.000 0.000 0.000 1 - 10 1 1 1 11 5 9 16 101 144 144 0.00000 0.00067 1.46578e-001 1 1 0.11 -18.12 13.70 27.22 -13.26 16.33 5.000 -1.500 0 1 0 2 0.556 0.556 134.09 0.00 0.00 0.00 1 90.00 0 0 0 151 0.0 1 4 0 0 0.000 0.000 0.000 1 - 11 1 1 1 11 5 10 16 101 144 144 0.00000 0.00067 1.46578e-001 1 1 0.11 -18.12 13.70 26.40 -9.86 16.34 5.000 -1.500 0 1 0 2 0.556 0.556 134.09 0.00 0.00 0.00 1 90.00 0 0 0 151 0.0 1 4 0 0 0.000 0.000 0.000 1 - 12 1 1 1 11 5 11 16 101 144 144 0.00000 0.00067 1.46578e-001 1 1 0.11 -18.12 13.70 25.57 -6.46 16.35 5.000 -1.500 0 1 0 2 0.556 0.556 134.09 0.00 0.00 0.00 1 90.00 0 0 0 151 0.0 1 4 0 0 0.000 0.000 0.000 1 - 13 1 1 1 11 5 12 16 101 144 144 0.00000 0.00067 1.46578e-001 1 1 0.11 -18.12 13.70 24.74 -3.06 16.35 5.000 -1.500 0 1 0 2 0.556 0.556 134.09 0.00 0.00 0.00 1 90.00 0 0 0 151 0.0 1 4 0 0 0.000 0.000 0.000 1 - 14 1 1 1 11 5 13 16 101 144 144 0.00000 0.00067 1.46578e-001 1 1 0.11 -18.12 13.70 23.91 0.34 16.36 5.000 -1.500 0 1 0 2 0.556 0.556 134.09 0.00 0.00 0.00 1 90.00 0 0 0 151 0.0 1 4 0 0 0.000 0.000 0.000 1 - 15 1 1 1 11 5 14 16 101 144 144 0.00000 0.00067 1.46578e-001 1 1 0.11 -18.12 13.70 23.08 3.74 16.37 5.000 -1.500 0 1 0 2 0.556 0.556 134.09 0.00 0.00 0.00 1 90.00 0 0 0 151 0.0 1 4 0 0 0.000 0.000 0.000 1 - 16 1 1 1 11 5 15 16 101 144 144 0.00000 0.00067 1.46578e-001 1 1 0.11 -18.12 13.70 22.25 7.14 16.37 5.000 -1.500 0 1 0 2 0.556 0.556 134.09 0.00 0.00 0.00 1 90.00 0 0 0 151 0.0 1 4 0 0 0.000 0.000 0.000 1 - 17 1 1 1 11 5 16 16 101 144 144 0.00000 0.00067 1.46578e-001 1 1 0.11 -18.12 13.70 21.42 10.54 16.38 5.000 -1.500 0 1 0 2 0.556 0.556 134.09 0.00 0.00 0.00 1 90.00 0 0 0 151 0.0 1 4 0 0 0.000 0.000 0.000 1 - 18 1 1 1 11 5 17 16 101 144 144 0.00000 0.00067 1.46578e-001 1 1 0.11 -18.12 13.70 20.59 13.94 16.39 5.000 -1.500 0 1 0 2 0.556 0.556 134.09 0.00 0.00 0.00 1 90.00 0 0 0 151 0.0 1 4 0 0 0.000 0.000 0.000 1 - 19 1 1 1 11 5 18 16 101 144 144 0.00000 0.00067 1.46578e-001 1 1 0.11 -18.12 13.70 19.76 17.34 16.39 5.000 -1.500 0 1 0 2 0.556 0.556 134.09 0.00 0.00 0.00 1 90.00 0 0 0 151 0.0 1 4 0 0 0.000 0.000 0.000 1 - 20 1 1 1 11 5 19 16 101 144 144 0.00000 0.00067 1.46578e-001 1 1 0.11 -18.12 13.70 18.93 20.74 16.40 5.000 -1.500 0 1 0 2 0.556 0.556 134.09 0.00 0.00 0.00 1 90.00 0 0 0 151 0.0 1 4 0 0 0.000 0.000 0.000 1 - 21 1 1 1 11 5 20 16 101 144 144 0.00000 0.00067 1.46578e-001 1 1 0.11 -18.12 13.70 18.10 24.14 16.41 5.000 -1.500 0 1 0 2 0.556 0.556 134.09 0.00 0.00 0.00 1 90.00 0 0 0 151 0.0 1 4 0 0 0.000 0.000 0.000 1 - 22 1 1 1 11 5 21 16 101 144 144 0.00000 0.00067 1.46578e-001 1 1 0.11 -18.12 13.70 17.27 27.54 16.41 5.000 -1.500 0 1 0 2 0.556 0.556 134.09 0.00 0.00 0.00 1 90.00 0 0 0 151 0.0 1 4 0 0 0.000 0.000 0.000 1 - -# === END OF DATA DESCRIPTION FILE =============================================== diff --git a/nibabel/tests/data/ASL_3D_Multiecho.PAR b/nibabel/tests/data/ASL_3D_Multiecho.PAR deleted file mode 100644 index 74575fbf58..0000000000 --- a/nibabel/tests/data/ASL_3D_Multiecho.PAR +++ /dev/null @@ -1,198 +0,0 @@ -# === DATA DESCRIPTION FILE ====================================================== -# -# CAUTION - Investigational device. -# Limited by Federal Law to investigational use. -# -# Dataset name: E:\\Export\\ASL_3D_Multiecho -# -# CLINICAL TRYOUT Research image export tool V4.2 -# -# === GENERAL INFORMATION ======================================================== -# -. Patient name : anon -. Examination name : anon -. Protocol name : anon -. Examination date/time : anon -. Series Type : Image MRSERIES -. Acquisition nr : 5 -. Reconstruction nr : 3 -. Scan Duration [sec] : 143 -. Max. number of cardiac phases : 1 -. Max. number of echoes : 3 -. Max. number of slices/locations : 8 -. Max. number of dynamics : 2 -. Max. number of mixes : 1 -. Patient position : Head First Supine -. Preparation direction : Anterior-Posterior -. Technique : T1TFE -. Scan resolution (x, y) : 76 120 -. Scan mode : 3D -. Repetition time [ms] : 7.093 -. FOV (ap,fh,rl) [mm] : 224.000 24.000 224.000 -. Water Fat shift [pixels] : 0.701 -. Angulation midslice(ap,fh,rl)[degr]: 5.000 15.000 30.000 -. Off Centre midslice(ap,fh,rl) [mm] : -4.810 3.500 -3.607 -. Flow compensation <0=no 1=yes> ? : 0 -. Presaturation <0=no 1=yes> ? : 0 -. Phase encoding velocity [cm/sec] : 0.000000 0.000000 0.000000 -. MTC <0=no 1=yes> ? : 0 -. SPIR <0=no 1=yes> ? : 0 -. EPI factor <0,1=no EPI> : 1 -. Dynamic scan <0=no 1=yes> ? : 1 -. Diffusion <0=no 1=yes> ? : 0 -. Diffusion echo time [ms] : 0.0000 -. Max. number of diffusion values : 1 -. Max. number of gradient orients : 1 -. Number of label types <0=no ASL> : 2 -# -# === PIXEL VALUES ============================================================= -# PV = pixel value in REC file, FP = floating point value, DV = displayed value on console -# RS = rescale slope, RI = rescale intercept, SS = scale slope -# DV = PV * RS + RI FP = DV / (RS * SS) -# -# === IMAGE INFORMATION DEFINITION ============================================= -# The rest of this file contains ONE line per image, this line contains the following information: -# -# slice number (integer) -# echo number (integer) -# dynamic scan number (integer) -# cardiac phase number (integer) -# image_type_mr (integer) -# scanning sequence (integer) -# index in REC file (in images) (integer) -# image pixel size (in bits) (integer) -# scan percentage (integer) -# recon resolution (x y) (2*integer) -# rescale intercept (float) -# rescale slope (float) -# scale slope (float) -# window center (integer) -# window width (integer) -# image angulation (ap,fh,rl in degrees ) (3*float) -# image offcentre (ap,fh,rl in mm ) (3*float) -# slice thickness (in mm ) (float) -# slice gap (in mm ) (float) -# image_display_orientation (integer) -# slice orientation ( TRA/SAG/COR ) (integer) -# fmri_status_indication (integer) -# image_type_ed_es (end diast/end syst) (integer) -# pixel spacing (x,y) (in mm) (2*float) -# echo_time (float) -# dyn_scan_begin_time (float) -# trigger_time (float) -# diffusion_b_factor (float) -# number of averages (integer) -# image_flip_angle (in degrees) (float) -# cardiac frequency (bpm) (integer) -# minimum RR-interval (in ms) (integer) -# maximum RR-interval (in ms) (integer) -# TURBO factor <0=no turbo> (integer) -# Inversion delay (in ms) (float) -# diffusion b value number (imagekey!) (integer) -# gradient orientation number (imagekey!) (integer) -# contrast type (string) -# diffusion anisotropy type (string) -# diffusion (ap, fh, rl) (3*float) -# label type (ASL) (imagekey!) (integer) -# -# === IMAGE INFORMATION ========================================================== -# sl ec dyn ph ty idx pix scan% rec size (re)scale window angulation offcentre thick gap info spacing echo dtime ttime diff avg flip freq RR-int turbo delay b grad cont anis diffusion L.ty - - 1 1 1 1 0 2 90 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 16.86 -34.03 -7.40 3.000 0.000 0 1 0 2 2.800 2.800 1.42 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 1 2 1 1 0 2 91 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 16.86 -34.03 -7.40 3.000 0.000 0 1 0 2 2.800 2.800 3.45 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 1 3 1 1 0 2 92 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 16.86 -34.03 -7.40 3.000 0.000 0 1 0 2 2.800 2.800 5.49 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 1 1 1 1 0 2 93 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 16.86 -34.03 -7.40 3.000 0.000 0 1 0 2 2.800 2.800 1.42 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 1 2 1 1 0 2 94 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 16.86 -34.03 -7.40 3.000 0.000 0 1 0 2 2.800 2.800 3.45 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 1 3 1 1 0 2 95 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 16.86 -34.03 -7.40 3.000 0.000 0 1 0 2 2.800 2.800 5.49 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 2 1 1 1 0 2 96 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 15.36 -31.44 -7.14 3.000 0.000 0 1 0 2 2.800 2.800 1.42 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 2 2 1 1 0 2 97 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 15.36 -31.44 -7.14 3.000 0.000 0 1 0 2 2.800 2.800 3.45 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 2 3 1 1 0 2 98 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 15.36 -31.44 -7.14 3.000 0.000 0 1 0 2 2.800 2.800 5.49 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 2 1 1 1 0 2 99 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 15.36 -31.44 -7.14 3.000 0.000 0 1 0 2 2.800 2.800 1.42 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 2 2 1 1 0 2 100 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 15.36 -31.44 -7.14 3.000 0.000 0 1 0 2 2.800 2.800 3.45 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 2 3 1 1 0 2 101 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 15.36 -31.44 -7.14 3.000 0.000 0 1 0 2 2.800 2.800 5.49 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 3 1 1 1 0 2 102 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 13.87 -28.85 -6.88 3.000 0.000 0 1 0 2 2.800 2.800 1.42 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 3 2 1 1 0 2 103 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 13.87 -28.85 -6.88 3.000 0.000 0 1 0 2 2.800 2.800 3.45 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 3 3 1 1 0 2 104 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 13.87 -28.85 -6.88 3.000 0.000 0 1 0 2 2.800 2.800 5.49 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 3 1 1 1 0 2 105 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 13.87 -28.85 -6.88 3.000 0.000 0 1 0 2 2.800 2.800 1.42 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 3 2 1 1 0 2 106 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 13.87 -28.85 -6.88 3.000 0.000 0 1 0 2 2.800 2.800 3.45 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 3 3 1 1 0 2 107 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 13.87 -28.85 -6.88 3.000 0.000 0 1 0 2 2.800 2.800 5.49 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 4 1 1 1 0 2 108 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 12.37 -26.26 -6.61 3.000 0.000 0 1 0 2 2.800 2.800 1.42 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 4 2 1 1 0 2 109 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 12.37 -26.26 -6.61 3.000 0.000 0 1 0 2 2.800 2.800 3.45 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 4 3 1 1 0 2 110 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 12.37 -26.26 -6.61 3.000 0.000 0 1 0 2 2.800 2.800 5.49 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 4 1 1 1 0 2 111 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 12.37 -26.26 -6.61 3.000 0.000 0 1 0 2 2.800 2.800 1.42 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 4 2 1 1 0 2 112 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 12.37 -26.26 -6.61 3.000 0.000 0 1 0 2 2.800 2.800 3.45 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 4 3 1 1 0 2 113 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 12.37 -26.26 -6.61 3.000 0.000 0 1 0 2 2.800 2.800 5.49 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 5 1 1 1 0 2 114 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 10.88 -23.68 -6.35 3.000 0.000 0 1 0 2 2.800 2.800 1.42 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 5 2 1 1 0 2 115 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 10.88 -23.68 -6.35 3.000 0.000 0 1 0 2 2.800 2.800 3.45 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 5 3 1 1 0 2 116 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 10.88 -23.68 -6.35 3.000 0.000 0 1 0 2 2.800 2.800 5.49 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 5 1 1 1 0 2 117 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 10.88 -23.68 -6.35 3.000 0.000 0 1 0 2 2.800 2.800 1.42 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 5 2 1 1 0 2 118 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 10.88 -23.68 -6.35 3.000 0.000 0 1 0 2 2.800 2.800 3.45 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 5 3 1 1 0 2 119 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 10.88 -23.68 -6.35 3.000 0.000 0 1 0 2 2.800 2.800 5.49 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 6 1 1 1 0 2 120 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 9.39 -21.09 -6.09 3.000 0.000 0 1 0 2 2.800 2.800 1.42 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 6 2 1 1 0 2 121 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 9.39 -21.09 -6.09 3.000 0.000 0 1 0 2 2.800 2.800 3.45 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 6 3 1 1 0 2 122 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 9.39 -21.09 -6.09 3.000 0.000 0 1 0 2 2.800 2.800 5.49 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 6 1 1 1 0 2 123 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 9.39 -21.09 -6.09 3.000 0.000 0 1 0 2 2.800 2.800 1.42 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 6 2 1 1 0 2 124 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 9.39 -21.09 -6.09 3.000 0.000 0 1 0 2 2.800 2.800 3.45 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 6 3 1 1 0 2 125 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 9.39 -21.09 -6.09 3.000 0.000 0 1 0 2 2.800 2.800 5.49 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 7 1 1 1 0 2 126 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 7.89 -18.50 -5.83 3.000 0.000 0 1 0 2 2.800 2.800 1.42 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 7 2 1 1 0 2 127 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 7.89 -18.50 -5.83 3.000 0.000 0 1 0 2 2.800 2.800 3.45 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 7 3 1 1 0 2 128 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 7.89 -18.50 -5.83 3.000 0.000 0 1 0 2 2.800 2.800 5.49 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 7 1 1 1 0 2 129 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 7.89 -18.50 -5.83 3.000 0.000 0 1 0 2 2.800 2.800 1.42 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 7 2 1 1 0 2 130 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 7.89 -18.50 -5.83 3.000 0.000 0 1 0 2 2.800 2.800 3.45 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 7 3 1 1 0 2 131 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 7.89 -18.50 -5.83 3.000 0.000 0 1 0 2 2.800 2.800 5.49 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 8 1 1 1 0 2 132 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 6.40 -15.91 -5.57 3.000 0.000 0 1 0 2 2.800 2.800 1.42 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 8 2 1 1 0 2 133 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 6.40 -15.91 -5.57 3.000 0.000 0 1 0 2 2.800 2.800 3.45 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 8 3 1 1 0 2 134 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 6.40 -15.91 -5.57 3.000 0.000 0 1 0 2 2.800 2.800 5.49 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 8 1 1 1 0 2 135 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 6.40 -15.91 -5.57 3.000 0.000 0 1 0 2 2.800 2.800 1.42 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 8 2 1 1 0 2 136 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 6.40 -15.91 -5.57 3.000 0.000 0 1 0 2 2.800 2.800 3.45 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 8 3 1 1 0 2 137 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 6.40 -15.91 -5.57 3.000 0.000 0 1 0 2 2.800 2.800 5.49 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 1 1 2 1 0 2 270 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 16.86 -34.03 -7.40 3.000 0.000 0 1 0 2 2.800 2.800 1.42 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 1 2 2 1 0 2 271 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 16.86 -34.03 -7.40 3.000 0.000 0 1 0 2 2.800 2.800 3.45 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 1 3 2 1 0 2 272 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 16.86 -34.03 -7.40 3.000 0.000 0 1 0 2 2.800 2.800 5.49 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 1 1 2 1 0 2 273 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 16.86 -34.03 -7.40 3.000 0.000 0 1 0 2 2.800 2.800 1.42 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 1 2 2 1 0 2 274 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 16.86 -34.03 -7.40 3.000 0.000 0 1 0 2 2.800 2.800 3.45 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 1 3 2 1 0 2 275 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 16.86 -34.03 -7.40 3.000 0.000 0 1 0 2 2.800 2.800 5.49 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 2 1 2 1 0 2 276 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 15.36 -31.44 -7.14 3.000 0.000 0 1 0 2 2.800 2.800 1.42 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 2 2 2 1 0 2 277 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 15.36 -31.44 -7.14 3.000 0.000 0 1 0 2 2.800 2.800 3.45 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 2 3 2 1 0 2 278 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 15.36 -31.44 -7.14 3.000 0.000 0 1 0 2 2.800 2.800 5.49 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 2 1 2 1 0 2 279 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 15.36 -31.44 -7.14 3.000 0.000 0 1 0 2 2.800 2.800 1.42 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 2 2 2 1 0 2 280 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 15.36 -31.44 -7.14 3.000 0.000 0 1 0 2 2.800 2.800 3.45 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 2 3 2 1 0 2 281 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 15.36 -31.44 -7.14 3.000 0.000 0 1 0 2 2.800 2.800 5.49 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 3 1 2 1 0 2 282 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 13.87 -28.85 -6.88 3.000 0.000 0 1 0 2 2.800 2.800 1.42 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 3 2 2 1 0 2 283 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 13.87 -28.85 -6.88 3.000 0.000 0 1 0 2 2.800 2.800 3.45 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 3 3 2 1 0 2 284 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 13.87 -28.85 -6.88 3.000 0.000 0 1 0 2 2.800 2.800 5.49 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 3 1 2 1 0 2 285 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 13.87 -28.85 -6.88 3.000 0.000 0 1 0 2 2.800 2.800 1.42 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 3 2 2 1 0 2 286 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 13.87 -28.85 -6.88 3.000 0.000 0 1 0 2 2.800 2.800 3.45 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 3 3 2 1 0 2 287 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 13.87 -28.85 -6.88 3.000 0.000 0 1 0 2 2.800 2.800 5.49 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 4 1 2 1 0 2 288 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 12.37 -26.26 -6.61 3.000 0.000 0 1 0 2 2.800 2.800 1.42 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 4 2 2 1 0 2 289 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 12.37 -26.26 -6.61 3.000 0.000 0 1 0 2 2.800 2.800 3.45 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 4 3 2 1 0 2 290 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 12.37 -26.26 -6.61 3.000 0.000 0 1 0 2 2.800 2.800 5.49 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 4 1 2 1 0 2 291 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 12.37 -26.26 -6.61 3.000 0.000 0 1 0 2 2.800 2.800 1.42 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 4 2 2 1 0 2 292 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 12.37 -26.26 -6.61 3.000 0.000 0 1 0 2 2.800 2.800 3.45 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 4 3 2 1 0 2 293 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 12.37 -26.26 -6.61 3.000 0.000 0 1 0 2 2.800 2.800 5.49 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 5 1 2 1 0 2 294 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 10.88 -23.68 -6.35 3.000 0.000 0 1 0 2 2.800 2.800 1.42 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 5 2 2 1 0 2 295 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 10.88 -23.68 -6.35 3.000 0.000 0 1 0 2 2.800 2.800 3.45 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 5 3 2 1 0 2 296 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 10.88 -23.68 -6.35 3.000 0.000 0 1 0 2 2.800 2.800 5.49 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 5 1 2 1 0 2 297 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 10.88 -23.68 -6.35 3.000 0.000 0 1 0 2 2.800 2.800 1.42 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 5 2 2 1 0 2 298 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 10.88 -23.68 -6.35 3.000 0.000 0 1 0 2 2.800 2.800 3.45 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 5 3 2 1 0 2 299 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 10.88 -23.68 -6.35 3.000 0.000 0 1 0 2 2.800 2.800 5.49 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 6 1 2 1 0 2 300 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 9.39 -21.09 -6.09 3.000 0.000 0 1 0 2 2.800 2.800 1.42 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 6 2 2 1 0 2 301 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 9.39 -21.09 -6.09 3.000 0.000 0 1 0 2 2.800 2.800 3.45 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 6 3 2 1 0 2 302 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 9.39 -21.09 -6.09 3.000 0.000 0 1 0 2 2.800 2.800 5.49 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 6 1 2 1 0 2 303 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 9.39 -21.09 -6.09 3.000 0.000 0 1 0 2 2.800 2.800 1.42 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 6 2 2 1 0 2 304 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 9.39 -21.09 -6.09 3.000 0.000 0 1 0 2 2.800 2.800 3.45 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 6 3 2 1 0 2 305 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 9.39 -21.09 -6.09 3.000 0.000 0 1 0 2 2.800 2.800 5.49 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 7 1 2 1 0 2 306 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 7.89 -18.50 -5.83 3.000 0.000 0 1 0 2 2.800 2.800 1.42 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 7 2 2 1 0 2 307 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 7.89 -18.50 -5.83 3.000 0.000 0 1 0 2 2.800 2.800 3.45 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 7 3 2 1 0 2 308 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 7.89 -18.50 -5.83 3.000 0.000 0 1 0 2 2.800 2.800 5.49 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 7 1 2 1 0 2 309 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 7.89 -18.50 -5.83 3.000 0.000 0 1 0 2 2.800 2.800 1.42 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 7 2 2 1 0 2 310 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 7.89 -18.50 -5.83 3.000 0.000 0 1 0 2 2.800 2.800 3.45 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 7 3 2 1 0 2 311 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 7.89 -18.50 -5.83 3.000 0.000 0 1 0 2 2.800 2.800 5.49 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 8 1 2 1 0 2 312 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 6.40 -15.91 -5.57 3.000 0.000 0 1 0 2 2.800 2.800 1.42 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 8 2 2 1 0 2 313 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 6.40 -15.91 -5.57 3.000 0.000 0 1 0 2 2.800 2.800 3.45 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 8 3 2 1 0 2 314 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 6.40 -15.91 -5.57 3.000 0.000 0 1 0 2 2.800 2.800 5.49 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 1 - 8 1 2 1 0 2 315 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 6.40 -15.91 -5.57 3.000 0.000 0 1 0 2 2.800 2.800 1.42 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 8 2 2 1 0 2 316 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 6.40 -15.91 -5.57 3.000 0.000 0 1 0 2 2.800 2.800 3.45 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - 8 3 2 1 0 2 317 16 98 80 80 0.00000 1.50110 8.85639e-004 1070 1860 5.00 15.00 30.00 6.40 -15.91 -5.57 3.000 0.000 0 1 0 2 2.800 2.800 5.49 71.29 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 3 0 0.000 0.000 0.000 2 - -# === END OF DATA DESCRIPTION FILE =============================================== diff --git a/nibabel/tests/data/DTI.PAR b/nibabel/tests/data/DTI.PAR deleted file mode 100644 index 73e78a5072..0000000000 --- a/nibabel/tests/data/DTI.PAR +++ /dev/null @@ -1,182 +0,0 @@ -# === DATA DESCRIPTION FILE ====================================================== -# -# CAUTION - Investigational device. -# Limited by Federal Law to investigational use. -# -# Dataset name: H:\Export\05aug14_test_samples_12_1 -# -# CLINICAL TRYOUT Research image export tool V4.2 -# -# === GENERAL INFORMATION ======================================================== -# -. Patient name : 05aug14test -. Examination name : test -. Protocol name : WIP DTI SENSE -. Examination date/time : 2014.08.05 / 11:27:34 -. Series Type : Image MRSERIES -. Acquisition nr : 12 -. Reconstruction nr : 1 -. Scan Duration [sec] : 10.5 -. Max. number of cardiac phases : 1 -. Max. number of echoes : 1 -. Max. number of slices/locations : 10 -. Max. number of dynamics : 1 -. Max. number of mixes : 1 -. Patient position : Head First Supine -. Preparation direction : Right-Left -. Technique : DwiSE -. Scan resolution (x, y) : 76 62 -. Scan mode : MS -. Repetition time [ms] : 1166.614 -. FOV (ap,fh,rl) [mm] : 130.000 120.970 154.375 -. Water Fat shift [pixels] : 9.087 -. Angulation midslice(ap,fh,rl)[degr]: -1.979 0.546 0.019 -. Off Centre midslice(ap,fh,rl) [mm] : -18.805 22.157 -17.977 -. Flow compensation <0=no 1=yes> ? : 0 -. Presaturation <0=no 1=yes> ? : 0 -. Phase encoding velocity [cm/sec] : 0.000000 0.000000 0.000000 -. MTC <0=no 1=yes> ? : 0 -. SPIR <0=no 1=yes> ? : 1 -. EPI factor <0,1=no EPI> : 27 -. Dynamic scan <0=no 1=yes> ? : 0 -. Diffusion <0=no 1=yes> ? : 1 -. Diffusion echo time [ms] : 0.0000 -. Max. number of diffusion values : 2 -. Max. number of gradient orients : 7 -. Number of label types <0=no ASL> : 0 -# -# === PIXEL VALUES ============================================================= -# PV = pixel value in REC file, FP = floating point value, DV = displayed value on console -# RS = rescale slope, RI = rescale intercept, SS = scale slope -# DV = PV * RS + RI FP = DV / (RS * SS) -# -# === IMAGE INFORMATION DEFINITION ============================================= -# The rest of this file contains ONE line per image, this line contains the following information: -# -# slice number (integer) -# echo number (integer) -# dynamic scan number (integer) -# cardiac phase number (integer) -# image_type_mr (integer) -# scanning sequence (integer) -# index in REC file (in images) (integer) -# image pixel size (in bits) (integer) -# scan percentage (integer) -# recon resolution (x y) (2*integer) -# rescale intercept (float) -# rescale slope (float) -# scale slope (float) -# window center (integer) -# window width (integer) -# image angulation (ap,fh,rl in degrees ) (3*float) -# image offcentre (ap,fh,rl in mm ) (3*float) -# slice thickness (in mm ) (float) -# slice gap (in mm ) (float) -# image_display_orientation (integer) -# slice orientation ( TRA/SAG/COR ) (integer) -# fmri_status_indication (integer) -# image_type_ed_es (end diast/end syst) (integer) -# pixel spacing (x,y) (in mm) (2*float) -# echo_time (float) -# dyn_scan_begin_time (float) -# trigger_time (float) -# diffusion_b_factor (float) -# number of averages (integer) -# image_flip_angle (in degrees) (float) -# cardiac frequency (bpm) (integer) -# minimum RR-interval (in ms) (integer) -# maximum RR-interval (in ms) (integer) -# TURBO factor <0=no turbo> (integer) -# Inversion delay (in ms) (float) -# diffusion b value number (imagekey!) (integer) -# gradient orientation number (imagekey!) (integer) -# contrast type (string) -# diffusion anisotropy type (string) -# diffusion (ap, fh, rl) (3*float) -# label type (ASL) (imagekey!) (integer) -# -# === IMAGE INFORMATION ========================================================== -# sl ec dyn ph ty idx pix scan% rec size (re)scale window angulation offcentre thick gap info spacing echo dtime ttime diff avg flip freq RR-int turbo delay b grad cont anis diffusion L.ty - - 1 1 1 1 0 1 0 16 81 80 80 0.00000 22.15092 1.35565e-003 69 120 -1.98 0.55 0.02 -18.79 -33.29 -16.06 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 1 0 0 -0.667 -0.667 -0.333 1 - 2 1 1 1 0 1 1 16 81 80 80 0.00000 22.15092 1.35565e-003 322 560 -1.98 0.55 0.02 -18.79 -20.97 -16.49 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 1 0 0 -0.667 -0.667 -0.333 1 - 3 1 1 1 0 1 2 16 81 80 80 0.00000 22.15092 1.35565e-003 688 1195 -1.98 0.55 0.02 -18.80 -8.65 -16.91 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 1 0 0 -0.667 -0.667 -0.333 1 - 4 1 1 1 0 1 3 16 81 80 80 0.00000 22.15092 1.35565e-003 1407 2447 -1.98 0.55 0.02 -18.80 3.67 -17.34 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 1 0 0 -0.667 -0.667 -0.333 1 - 5 1 1 1 0 1 4 16 81 80 80 0.00000 22.15092 1.35565e-003 653 1135 -1.98 0.55 0.02 -18.80 16.00 -17.76 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 1 0 0 -0.667 -0.667 -0.333 1 - 6 1 1 1 0 1 5 16 81 80 80 0.00000 22.15092 1.35565e-003 502 873 -1.98 0.55 0.02 -18.81 28.32 -18.19 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 1 0 0 -0.667 -0.667 -0.333 1 - 7 1 1 1 0 1 6 16 81 80 80 0.00000 22.15092 1.35565e-003 365 635 -1.98 0.55 0.02 -18.81 40.64 -18.62 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 1 0 0 -0.667 -0.667 -0.333 1 - 8 1 1 1 0 1 7 16 81 80 80 0.00000 22.15092 1.35565e-003 301 524 -1.98 0.55 0.02 -18.82 52.96 -19.04 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 1 0 0 -0.667 -0.667 -0.333 1 - 9 1 1 1 0 1 8 16 81 80 80 0.00000 22.15092 1.35565e-003 450 783 -1.98 0.55 0.02 -18.82 65.29 -19.47 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 1 0 0 -0.667 -0.667 -0.333 1 - 10 1 1 1 0 1 9 16 81 80 80 0.00000 22.15092 1.35565e-003 38 66 -1.98 0.55 0.02 -18.82 77.61 -19.89 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 1 0 0 -0.667 -0.667 -0.333 1 - 1 1 1 1 0 1 10 16 81 80 80 0.00000 22.15092 1.35565e-003 65 112 -1.98 0.55 0.02 -18.79 -33.29 -16.06 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 2 0 0 -0.333 0.667 -0.667 1 - 2 1 1 1 0 1 11 16 81 80 80 0.00000 22.15092 1.35565e-003 339 589 -1.98 0.55 0.02 -18.79 -20.97 -16.49 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 2 0 0 -0.333 0.667 -0.667 1 - 3 1 1 1 0 1 12 16 81 80 80 0.00000 22.15092 1.35565e-003 721 1253 -1.98 0.55 0.02 -18.80 -8.65 -16.91 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 2 0 0 -0.333 0.667 -0.667 1 - 4 1 1 1 0 1 13 16 81 80 80 0.00000 22.15092 1.35565e-003 1581 2748 -1.98 0.55 0.02 -18.80 3.67 -17.34 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 2 0 0 -0.333 0.667 -0.667 1 - 5 1 1 1 0 1 14 16 81 80 80 0.00000 22.15092 1.35565e-003 640 1112 -1.98 0.55 0.02 -18.80 16.00 -17.76 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 2 0 0 -0.333 0.667 -0.667 1 - 6 1 1 1 0 1 15 16 81 80 80 0.00000 22.15092 1.35565e-003 471 819 -1.98 0.55 0.02 -18.81 28.32 -18.19 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 2 0 0 -0.333 0.667 -0.667 1 - 7 1 1 1 0 1 16 16 81 80 80 0.00000 22.15092 1.35565e-003 367 638 -1.98 0.55 0.02 -18.81 40.64 -18.62 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 2 0 0 -0.333 0.667 -0.667 1 - 8 1 1 1 0 1 17 16 81 80 80 0.00000 22.15092 1.35565e-003 214 373 -1.98 0.55 0.02 -18.82 52.96 -19.04 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 2 0 0 -0.333 0.667 -0.667 1 - 9 1 1 1 0 1 18 16 81 80 80 0.00000 22.15092 1.35565e-003 436 758 -1.98 0.55 0.02 -18.82 65.29 -19.47 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 2 0 0 -0.333 0.667 -0.667 1 - 10 1 1 1 0 1 19 16 81 80 80 0.00000 22.15092 1.35565e-003 47 82 -1.98 0.55 0.02 -18.82 77.61 -19.89 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 2 0 0 -0.333 0.667 -0.667 1 - 1 1 1 1 0 1 20 16 81 80 80 0.00000 22.15092 1.35565e-003 66 115 -1.98 0.55 0.02 -18.79 -33.29 -16.06 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 3 0 0 -0.667 0.333 0.667 1 - 2 1 1 1 0 1 21 16 81 80 80 0.00000 22.15092 1.35565e-003 334 581 -1.98 0.55 0.02 -18.79 -20.97 -16.49 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 3 0 0 -0.667 0.333 0.667 1 - 3 1 1 1 0 1 22 16 81 80 80 0.00000 22.15092 1.35565e-003 746 1297 -1.98 0.55 0.02 -18.80 -8.65 -16.91 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 3 0 0 -0.667 0.333 0.667 1 - 4 1 1 1 0 1 23 16 81 80 80 0.00000 22.15092 1.35565e-003 1400 2433 -1.98 0.55 0.02 -18.80 3.67 -17.34 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 3 0 0 -0.667 0.333 0.667 1 - 5 1 1 1 0 1 24 16 81 80 80 0.00000 22.15092 1.35565e-003 631 1096 -1.98 0.55 0.02 -18.80 16.00 -17.76 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 3 0 0 -0.667 0.333 0.667 1 - 6 1 1 1 0 1 25 16 81 80 80 0.00000 22.15092 1.35565e-003 438 761 -1.98 0.55 0.02 -18.81 28.32 -18.19 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 3 0 0 -0.667 0.333 0.667 1 - 7 1 1 1 0 1 26 16 81 80 80 0.00000 22.15092 1.35565e-003 374 650 -1.98 0.55 0.02 -18.81 40.64 -18.62 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 3 0 0 -0.667 0.333 0.667 1 - 8 1 1 1 0 1 27 16 81 80 80 0.00000 22.15092 1.35565e-003 259 450 -1.98 0.55 0.02 -18.82 52.96 -19.04 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 3 0 0 -0.667 0.333 0.667 1 - 9 1 1 1 0 1 28 16 81 80 80 0.00000 22.15092 1.35565e-003 436 757 -1.98 0.55 0.02 -18.82 65.29 -19.47 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 3 0 0 -0.667 0.333 0.667 1 - 10 1 1 1 0 1 29 16 81 80 80 0.00000 22.15092 1.35565e-003 50 86 -1.98 0.55 0.02 -18.82 77.61 -19.89 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 3 0 0 -0.667 0.333 0.667 1 - 1 1 1 1 0 1 30 16 81 80 80 0.00000 22.15092 1.35565e-003 67 116 -1.98 0.55 0.02 -18.79 -33.29 -16.06 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 4 0 0 -0.707 -0.000 -0.707 1 - 2 1 1 1 0 1 31 16 81 80 80 0.00000 22.15092 1.35565e-003 312 542 -1.98 0.55 0.02 -18.79 -20.97 -16.49 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 4 0 0 -0.707 -0.000 -0.707 1 - 3 1 1 1 0 1 32 16 81 80 80 0.00000 22.15092 1.35565e-003 694 1206 -1.98 0.55 0.02 -18.80 -8.65 -16.91 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 4 0 0 -0.707 -0.000 -0.707 1 - 4 1 1 1 0 1 33 16 81 80 80 0.00000 22.15092 1.35565e-003 1422 2471 -1.98 0.55 0.02 -18.80 3.67 -17.34 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 4 0 0 -0.707 -0.000 -0.707 1 - 5 1 1 1 0 1 34 16 81 80 80 0.00000 22.15092 1.35565e-003 626 1088 -1.98 0.55 0.02 -18.80 16.00 -17.76 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 4 0 0 -0.707 -0.000 -0.707 1 - 6 1 1 1 0 1 35 16 81 80 80 0.00000 22.15092 1.35565e-003 472 820 -1.98 0.55 0.02 -18.81 28.32 -18.19 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 4 0 0 -0.707 -0.000 -0.707 1 - 7 1 1 1 0 1 36 16 81 80 80 0.00000 22.15092 1.35565e-003 345 600 -1.98 0.55 0.02 -18.81 40.64 -18.62 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 4 0 0 -0.707 -0.000 -0.707 1 - 8 1 1 1 0 1 37 16 81 80 80 0.00000 22.15092 1.35565e-003 312 542 -1.98 0.55 0.02 -18.82 52.96 -19.04 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 4 0 0 -0.707 -0.000 -0.707 1 - 9 1 1 1 0 1 38 16 81 80 80 0.00000 22.15092 1.35565e-003 457 794 -1.98 0.55 0.02 -18.82 65.29 -19.47 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 4 0 0 -0.707 -0.000 -0.707 1 - 10 1 1 1 0 1 39 16 81 80 80 0.00000 22.15092 1.35565e-003 48 83 -1.98 0.55 0.02 -18.82 77.61 -19.89 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 4 0 0 -0.707 -0.000 -0.707 1 - 1 1 1 1 0 1 40 16 81 80 80 0.00000 22.15092 1.35565e-003 55 95 -1.98 0.55 0.02 -18.79 -33.29 -16.06 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 5 0 0 -0.707 0.707 0.000 1 - 2 1 1 1 0 1 41 16 81 80 80 0.00000 22.15092 1.35565e-003 355 618 -1.98 0.55 0.02 -18.79 -20.97 -16.49 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 5 0 0 -0.707 0.707 0.000 1 - 3 1 1 1 0 1 42 16 81 80 80 0.00000 22.15092 1.35565e-003 738 1284 -1.98 0.55 0.02 -18.80 -8.65 -16.91 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 5 0 0 -0.707 0.707 0.000 1 - 4 1 1 1 0 1 43 16 81 80 80 0.00000 22.15092 1.35565e-003 1440 2504 -1.98 0.55 0.02 -18.80 3.67 -17.34 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 5 0 0 -0.707 0.707 0.000 1 - 5 1 1 1 0 1 44 16 81 80 80 0.00000 22.15092 1.35565e-003 676 1174 -1.98 0.55 0.02 -18.80 16.00 -17.76 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 5 0 0 -0.707 0.707 0.000 1 - 6 1 1 1 0 1 45 16 81 80 80 0.00000 22.15092 1.35565e-003 502 872 -1.98 0.55 0.02 -18.81 28.32 -18.19 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 5 0 0 -0.707 0.707 0.000 1 - 7 1 1 1 0 1 46 16 81 80 80 0.00000 22.15092 1.35565e-003 368 639 -1.98 0.55 0.02 -18.81 40.64 -18.62 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 5 0 0 -0.707 0.707 0.000 1 - 8 1 1 1 0 1 47 16 81 80 80 0.00000 22.15092 1.35565e-003 330 573 -1.98 0.55 0.02 -18.82 52.96 -19.04 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 5 0 0 -0.707 0.707 0.000 1 - 9 1 1 1 0 1 48 16 81 80 80 0.00000 22.15092 1.35565e-003 483 839 -1.98 0.55 0.02 -18.82 65.29 -19.47 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 5 0 0 -0.707 0.707 0.000 1 - 10 1 1 1 0 1 49 16 81 80 80 0.00000 22.15092 1.35565e-003 55 95 -1.98 0.55 0.02 -18.82 77.61 -19.89 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 5 0 0 -0.707 0.707 0.000 1 - 1 1 1 1 0 1 50 16 81 80 80 0.00000 22.15092 1.35565e-003 56 97 -1.98 0.55 0.02 -18.79 -33.29 -16.06 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 6 0 0 -0.000 0.707 0.707 1 - 2 1 1 1 0 1 51 16 81 80 80 0.00000 22.15092 1.35565e-003 359 624 -1.98 0.55 0.02 -18.79 -20.97 -16.49 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 6 0 0 -0.000 0.707 0.707 1 - 3 1 1 1 0 1 52 16 81 80 80 0.00000 22.15092 1.35565e-003 818 1422 -1.98 0.55 0.02 -18.80 -8.65 -16.91 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 6 0 0 -0.000 0.707 0.707 1 - 4 1 1 1 0 1 53 16 81 80 80 0.00000 22.15092 1.35565e-003 1526 2652 -1.98 0.55 0.02 -18.80 3.67 -17.34 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 6 0 0 -0.000 0.707 0.707 1 - 5 1 1 1 0 1 54 16 81 80 80 0.00000 22.15092 1.35565e-003 645 1121 -1.98 0.55 0.02 -18.80 16.00 -17.76 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 6 0 0 -0.000 0.707 0.707 1 - 6 1 1 1 0 1 55 16 81 80 80 0.00000 22.15092 1.35565e-003 474 824 -1.98 0.55 0.02 -18.81 28.32 -18.19 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 6 0 0 -0.000 0.707 0.707 1 - 7 1 1 1 0 1 56 16 81 80 80 0.00000 22.15092 1.35565e-003 386 671 -1.98 0.55 0.02 -18.81 40.64 -18.62 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 6 0 0 -0.000 0.707 0.707 1 - 8 1 1 1 0 1 57 16 81 80 80 0.00000 22.15092 1.35565e-003 235 409 -1.98 0.55 0.02 -18.82 52.96 -19.04 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 6 0 0 -0.000 0.707 0.707 1 - 9 1 1 1 0 1 58 16 81 80 80 0.00000 22.15092 1.35565e-003 406 705 -1.98 0.55 0.02 -18.82 65.29 -19.47 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 6 0 0 -0.000 0.707 0.707 1 - 10 1 1 1 0 1 59 16 81 80 80 0.00000 22.15092 1.35565e-003 50 87 -1.98 0.55 0.02 -18.82 77.61 -19.89 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 6 0 0 -0.000 0.707 0.707 1 - 1 1 1 1 0 1 60 16 81 80 80 0.00000 22.15092 1.35565e-003 52 90 -1.98 0.55 0.02 -18.79 -33.29 -16.06 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 7 0 0 0.000 0.000 0.000 1 - 2 1 1 1 0 1 61 16 81 80 80 0.00000 22.15092 1.35565e-003 691 1201 -1.98 0.55 0.02 -18.79 -20.97 -16.49 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 7 0 0 0.000 0.000 0.000 1 - 3 1 1 1 0 1 62 16 81 80 80 0.00000 22.15092 1.35565e-003 1598 2777 -1.98 0.55 0.02 -18.80 -8.65 -16.91 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 7 0 0 0.000 0.000 0.000 1 - 4 1 1 1 0 1 63 16 81 80 80 0.00000 22.15092 1.35565e-003 4569 7943 -1.98 0.55 0.02 -18.80 3.67 -17.34 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 7 0 0 0.000 0.000 0.000 1 - 5 1 1 1 0 1 64 16 81 80 80 0.00000 22.15092 1.35565e-003 1526 2653 -1.98 0.55 0.02 -18.80 16.00 -17.76 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 7 0 0 0.000 0.000 0.000 1 - 6 1 1 1 0 1 65 16 81 80 80 0.00000 22.15092 1.35565e-003 1070 1860 -1.98 0.55 0.02 -18.81 28.32 -18.19 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 7 0 0 0.000 0.000 0.000 1 - 7 1 1 1 0 1 66 16 81 80 80 0.00000 22.15092 1.35565e-003 836 1453 -1.98 0.55 0.02 -18.81 40.64 -18.62 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 7 0 0 0.000 0.000 0.000 1 - 8 1 1 1 0 1 67 16 81 80 80 0.00000 22.15092 1.35565e-003 562 978 -1.98 0.55 0.02 -18.82 52.96 -19.04 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 7 0 0 0.000 0.000 0.000 1 - 9 1 1 1 0 1 68 16 81 80 80 0.00000 22.15092 1.35565e-003 1073 1865 -1.98 0.55 0.02 -18.82 65.29 -19.47 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 7 0 0 0.000 0.000 0.000 1 - 10 1 1 1 0 1 69 16 81 80 80 0.00000 22.15092 1.35565e-003 42 72 -1.98 0.55 0.02 -18.82 77.61 -19.89 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 7 0 0 0.000 0.000 0.000 1 - 1 1 1 1 0 1 70 16 81 80 80 0.00000 22.15092 1.35565e-003 53 92 -1.98 0.55 0.02 -18.79 -33.29 -16.06 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 7 0 0 0.000 0.000 0.000 1 - 2 1 1 1 0 1 71 16 81 80 80 0.00000 22.15092 1.35565e-003 322 561 -1.98 0.55 0.02 -18.79 -20.97 -16.49 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 7 0 0 0.000 0.000 0.000 1 - 3 1 1 1 0 1 72 16 81 80 80 0.00000 22.15092 1.35565e-003 718 1248 -1.98 0.55 0.02 -18.80 -8.65 -16.91 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 7 0 0 0.000 0.000 0.000 1 - 4 1 1 1 0 1 73 16 81 80 80 0.00000 22.15092 1.35565e-003 1440 2503 -1.98 0.55 0.02 -18.80 3.67 -17.34 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 7 0 0 0.000 0.000 0.000 1 - 5 1 1 1 0 1 74 16 81 80 80 0.00000 22.15092 1.35565e-003 636 1105 -1.98 0.55 0.02 -18.80 16.00 -17.76 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 7 0 0 0.000 0.000 0.000 1 - 6 1 1 1 0 1 75 16 81 80 80 0.00000 22.15092 1.35565e-003 467 811 -1.98 0.55 0.02 -18.81 28.32 -18.19 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 7 0 0 0.000 0.000 0.000 1 - 7 1 1 1 0 1 76 16 81 80 80 0.00000 22.15092 1.35565e-003 355 616 -1.98 0.55 0.02 -18.81 40.64 -18.62 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 7 0 0 0.000 0.000 0.000 1 - 8 1 1 1 0 1 77 16 81 80 80 0.00000 22.15092 1.35565e-003 254 441 -1.98 0.55 0.02 -18.82 52.96 -19.04 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 7 0 0 0.000 0.000 0.000 1 - 9 1 1 1 0 1 78 16 81 80 80 0.00000 22.15092 1.35565e-003 442 768 -1.98 0.55 0.02 -18.82 65.29 -19.47 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 7 0 0 0.000 0.000 0.000 1 - 10 1 1 1 0 1 79 16 81 80 80 0.00000 22.15092 1.35565e-003 33 57 -1.98 0.55 0.02 -18.82 77.61 -19.89 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 2 7 0 0 0.000 0.000 0.000 1 - -# === END OF DATA DESCRIPTION FILE =============================================== diff --git a/nibabel/tests/data/DTIv40.PAR b/nibabel/tests/data/DTIv40.PAR deleted file mode 100644 index 14238a926d..0000000000 --- a/nibabel/tests/data/DTIv40.PAR +++ /dev/null @@ -1,177 +0,0 @@ -# === DATA DESCRIPTION FILE ====================================================== -# -# CAUTION - Investigational device. -# Limited by Federal Law to investigational use. -# -# Dataset name: H:\Export\05aug14_test_samples_12_1 -# -# CLINICAL TRYOUT Research image export tool V4 -# -# Note: This is a simulated V4 .PAR file created from a V4.2 .PAR file by -# truncating the columns/fields that were not present in V4. -# -# -# === GENERAL INFORMATION ======================================================== -# -. Patient name : 05aug14test -. Examination name : test -. Protocol name : WIP DTI SENSE -. Examination date/time : 2014.08.05 / 11:27:34 -. Series Type : Image MRSERIES -. Acquisition nr : 12 -. Reconstruction nr : 1 -. Scan Duration [sec] : 10.5 -. Max. number of cardiac phases : 1 -. Max. number of echoes : 1 -. Max. number of slices/locations : 10 -. Max. number of dynamics : 1 -. Max. number of mixes : 1 -. Patient position : Head First Supine -. Preparation direction : Right-Left -. Technique : DwiSE -. Scan resolution (x, y) : 76 62 -. Scan mode : MS -. Repetition time [ms] : 1166.614 -. FOV (ap,fh,rl) [mm] : 130.000 120.970 154.375 -. Water Fat shift [pixels] : 9.087 -. Angulation midslice(ap,fh,rl)[degr]: -1.979 0.546 0.019 -. Off Centre midslice(ap,fh,rl) [mm] : -18.805 22.157 -17.977 -. Flow compensation <0=no 1=yes> ? : 0 -. Presaturation <0=no 1=yes> ? : 0 -. Phase encoding velocity [cm/sec] : 0.000000 0.000000 0.000000 -. MTC <0=no 1=yes> ? : 0 -. SPIR <0=no 1=yes> ? : 1 -. EPI factor <0,1=no EPI> : 27 -. Dynamic scan <0=no 1=yes> ? : 0 -. Diffusion <0=no 1=yes> ? : 1 -. Diffusion echo time [ms] : 0.0000 -# -# === PIXEL VALUES ============================================================= -# PV = pixel value in REC file, FP = floating point value, DV = displayed value on console -# RS = rescale slope, RI = rescale intercept, SS = scale slope -# DV = PV * RS + RI FP = DV / (RS * SS) -# -# === IMAGE INFORMATION DEFINITION ============================================= -# The rest of this file contains ONE line per image, this line contains the following information: -# -# slice number (integer) -# echo number (integer) -# dynamic scan number (integer) -# cardiac phase number (integer) -# image_type_mr (integer) -# scanning sequence (integer) -# index in REC file (in images) (integer) -# image pixel size (in bits) (integer) -# scan percentage (integer) -# recon resolution (x y) (2*integer) -# rescale intercept (float) -# rescale slope (float) -# scale slope (float) -# window center (integer) -# window width (integer) -# image angulation (ap,fh,rl in degrees ) (3*float) -# image offcentre (ap,fh,rl in mm ) (3*float) -# slice thickness (in mm ) (float) -# slice gap (in mm ) (float) -# image_display_orientation (integer) -# slice orientation ( TRA/SAG/COR ) (integer) -# fmri_status_indication (integer) -# image_type_ed_es (end diast/end syst) (integer) -# pixel spacing (x,y) (in mm) (2*float) -# echo_time (float) -# dyn_scan_begin_time (float) -# trigger_time (float) -# diffusion_b_factor (float) -# number of averages (integer) -# image_flip_angle (in degrees) (float) -# cardiac frequency (bpm) (integer) -# minimum RR-interval (in ms) (integer) -# maximum RR-interval (in ms) (integer) -# TURBO factor <0=no turbo> (integer) -# Inversion delay (in ms) (float) -# -# === IMAGE INFORMATION ========================================================== -# sl ec dyn ph ty idx pix scan% rec size (re)scale window angulation offcentre thick gap info spacing echo dtime ttime diff avg flip freq RR-int turbo delay - - 1 1 1 1 0 1 0 16 81 80 80 0.00000 22.15092 1.35565e-003 69 120 -1.98 0.55 0.02 -18.79 -33.29 -16.06 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 2 1 1 1 0 1 1 16 81 80 80 0.00000 22.15092 1.35565e-003 322 560 -1.98 0.55 0.02 -18.79 -20.97 -16.49 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 3 1 1 1 0 1 2 16 81 80 80 0.00000 22.15092 1.35565e-003 688 1195 -1.98 0.55 0.02 -18.80 -8.65 -16.91 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 4 1 1 1 0 1 3 16 81 80 80 0.00000 22.15092 1.35565e-003 1407 2447 -1.98 0.55 0.02 -18.80 3.67 -17.34 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 5 1 1 1 0 1 4 16 81 80 80 0.00000 22.15092 1.35565e-003 653 1135 -1.98 0.55 0.02 -18.80 16.00 -17.76 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 6 1 1 1 0 1 5 16 81 80 80 0.00000 22.15092 1.35565e-003 502 873 -1.98 0.55 0.02 -18.81 28.32 -18.19 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 7 1 1 1 0 1 6 16 81 80 80 0.00000 22.15092 1.35565e-003 365 635 -1.98 0.55 0.02 -18.81 40.64 -18.62 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 8 1 1 1 0 1 7 16 81 80 80 0.00000 22.15092 1.35565e-003 301 524 -1.98 0.55 0.02 -18.82 52.96 -19.04 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 9 1 1 1 0 1 8 16 81 80 80 0.00000 22.15092 1.35565e-003 450 783 -1.98 0.55 0.02 -18.82 65.29 -19.47 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 10 1 1 1 0 1 9 16 81 80 80 0.00000 22.15092 1.35565e-003 38 66 -1.98 0.55 0.02 -18.82 77.61 -19.89 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 1 1 1 1 0 1 10 16 81 80 80 0.00000 22.15092 1.35565e-003 65 112 -1.98 0.55 0.02 -18.79 -33.29 -16.06 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 2 1 1 1 0 1 11 16 81 80 80 0.00000 22.15092 1.35565e-003 339 589 -1.98 0.55 0.02 -18.79 -20.97 -16.49 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 3 1 1 1 0 1 12 16 81 80 80 0.00000 22.15092 1.35565e-003 721 1253 -1.98 0.55 0.02 -18.80 -8.65 -16.91 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 4 1 1 1 0 1 13 16 81 80 80 0.00000 22.15092 1.35565e-003 1581 2748 -1.98 0.55 0.02 -18.80 3.67 -17.34 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 5 1 1 1 0 1 14 16 81 80 80 0.00000 22.15092 1.35565e-003 640 1112 -1.98 0.55 0.02 -18.80 16.00 -17.76 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 6 1 1 1 0 1 15 16 81 80 80 0.00000 22.15092 1.35565e-003 471 819 -1.98 0.55 0.02 -18.81 28.32 -18.19 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 7 1 1 1 0 1 16 16 81 80 80 0.00000 22.15092 1.35565e-003 367 638 -1.98 0.55 0.02 -18.81 40.64 -18.62 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 8 1 1 1 0 1 17 16 81 80 80 0.00000 22.15092 1.35565e-003 214 373 -1.98 0.55 0.02 -18.82 52.96 -19.04 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 9 1 1 1 0 1 18 16 81 80 80 0.00000 22.15092 1.35565e-003 436 758 -1.98 0.55 0.02 -18.82 65.29 -19.47 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 10 1 1 1 0 1 19 16 81 80 80 0.00000 22.15092 1.35565e-003 47 82 -1.98 0.55 0.02 -18.82 77.61 -19.89 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 1 1 1 1 0 1 20 16 81 80 80 0.00000 22.15092 1.35565e-003 66 115 -1.98 0.55 0.02 -18.79 -33.29 -16.06 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 2 1 1 1 0 1 21 16 81 80 80 0.00000 22.15092 1.35565e-003 334 581 -1.98 0.55 0.02 -18.79 -20.97 -16.49 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 3 1 1 1 0 1 22 16 81 80 80 0.00000 22.15092 1.35565e-003 746 1297 -1.98 0.55 0.02 -18.80 -8.65 -16.91 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 4 1 1 1 0 1 23 16 81 80 80 0.00000 22.15092 1.35565e-003 1400 2433 -1.98 0.55 0.02 -18.80 3.67 -17.34 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 5 1 1 1 0 1 24 16 81 80 80 0.00000 22.15092 1.35565e-003 631 1096 -1.98 0.55 0.02 -18.80 16.00 -17.76 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 6 1 1 1 0 1 25 16 81 80 80 0.00000 22.15092 1.35565e-003 438 761 -1.98 0.55 0.02 -18.81 28.32 -18.19 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 7 1 1 1 0 1 26 16 81 80 80 0.00000 22.15092 1.35565e-003 374 650 -1.98 0.55 0.02 -18.81 40.64 -18.62 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 8 1 1 1 0 1 27 16 81 80 80 0.00000 22.15092 1.35565e-003 259 450 -1.98 0.55 0.02 -18.82 52.96 -19.04 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 9 1 1 1 0 1 28 16 81 80 80 0.00000 22.15092 1.35565e-003 436 757 -1.98 0.55 0.02 -18.82 65.29 -19.47 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 10 1 1 1 0 1 29 16 81 80 80 0.00000 22.15092 1.35565e-003 50 86 -1.98 0.55 0.02 -18.82 77.61 -19.89 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 1 1 1 1 0 1 30 16 81 80 80 0.00000 22.15092 1.35565e-003 67 116 -1.98 0.55 0.02 -18.79 -33.29 -16.06 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 2 1 1 1 0 1 31 16 81 80 80 0.00000 22.15092 1.35565e-003 312 542 -1.98 0.55 0.02 -18.79 -20.97 -16.49 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 3 1 1 1 0 1 32 16 81 80 80 0.00000 22.15092 1.35565e-003 694 1206 -1.98 0.55 0.02 -18.80 -8.65 -16.91 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 4 1 1 1 0 1 33 16 81 80 80 0.00000 22.15092 1.35565e-003 1422 2471 -1.98 0.55 0.02 -18.80 3.67 -17.34 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 5 1 1 1 0 1 34 16 81 80 80 0.00000 22.15092 1.35565e-003 626 1088 -1.98 0.55 0.02 -18.80 16.00 -17.76 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 6 1 1 1 0 1 35 16 81 80 80 0.00000 22.15092 1.35565e-003 472 820 -1.98 0.55 0.02 -18.81 28.32 -18.19 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 7 1 1 1 0 1 36 16 81 80 80 0.00000 22.15092 1.35565e-003 345 600 -1.98 0.55 0.02 -18.81 40.64 -18.62 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 8 1 1 1 0 1 37 16 81 80 80 0.00000 22.15092 1.35565e-003 312 542 -1.98 0.55 0.02 -18.82 52.96 -19.04 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 9 1 1 1 0 1 38 16 81 80 80 0.00000 22.15092 1.35565e-003 457 794 -1.98 0.55 0.02 -18.82 65.29 -19.47 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 10 1 1 1 0 1 39 16 81 80 80 0.00000 22.15092 1.35565e-003 48 83 -1.98 0.55 0.02 -18.82 77.61 -19.89 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 1 1 1 1 0 1 40 16 81 80 80 0.00000 22.15092 1.35565e-003 55 95 -1.98 0.55 0.02 -18.79 -33.29 -16.06 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 2 1 1 1 0 1 41 16 81 80 80 0.00000 22.15092 1.35565e-003 355 618 -1.98 0.55 0.02 -18.79 -20.97 -16.49 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 3 1 1 1 0 1 42 16 81 80 80 0.00000 22.15092 1.35565e-003 738 1284 -1.98 0.55 0.02 -18.80 -8.65 -16.91 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 4 1 1 1 0 1 43 16 81 80 80 0.00000 22.15092 1.35565e-003 1440 2504 -1.98 0.55 0.02 -18.80 3.67 -17.34 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 5 1 1 1 0 1 44 16 81 80 80 0.00000 22.15092 1.35565e-003 676 1174 -1.98 0.55 0.02 -18.80 16.00 -17.76 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 6 1 1 1 0 1 45 16 81 80 80 0.00000 22.15092 1.35565e-003 502 872 -1.98 0.55 0.02 -18.81 28.32 -18.19 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 7 1 1 1 0 1 46 16 81 80 80 0.00000 22.15092 1.35565e-003 368 639 -1.98 0.55 0.02 -18.81 40.64 -18.62 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 8 1 1 1 0 1 47 16 81 80 80 0.00000 22.15092 1.35565e-003 330 573 -1.98 0.55 0.02 -18.82 52.96 -19.04 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 9 1 1 1 0 1 48 16 81 80 80 0.00000 22.15092 1.35565e-003 483 839 -1.98 0.55 0.02 -18.82 65.29 -19.47 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 10 1 1 1 0 1 49 16 81 80 80 0.00000 22.15092 1.35565e-003 55 95 -1.98 0.55 0.02 -18.82 77.61 -19.89 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 1 1 1 1 0 1 50 16 81 80 80 0.00000 22.15092 1.35565e-003 56 97 -1.98 0.55 0.02 -18.79 -33.29 -16.06 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 2 1 1 1 0 1 51 16 81 80 80 0.00000 22.15092 1.35565e-003 359 624 -1.98 0.55 0.02 -18.79 -20.97 -16.49 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 3 1 1 1 0 1 52 16 81 80 80 0.00000 22.15092 1.35565e-003 818 1422 -1.98 0.55 0.02 -18.80 -8.65 -16.91 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 4 1 1 1 0 1 53 16 81 80 80 0.00000 22.15092 1.35565e-003 1526 2652 -1.98 0.55 0.02 -18.80 3.67 -17.34 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 5 1 1 1 0 1 54 16 81 80 80 0.00000 22.15092 1.35565e-003 645 1121 -1.98 0.55 0.02 -18.80 16.00 -17.76 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 6 1 1 1 0 1 55 16 81 80 80 0.00000 22.15092 1.35565e-003 474 824 -1.98 0.55 0.02 -18.81 28.32 -18.19 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 7 1 1 1 0 1 56 16 81 80 80 0.00000 22.15092 1.35565e-003 386 671 -1.98 0.55 0.02 -18.81 40.64 -18.62 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 8 1 1 1 0 1 57 16 81 80 80 0.00000 22.15092 1.35565e-003 235 409 -1.98 0.55 0.02 -18.82 52.96 -19.04 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 9 1 1 1 0 1 58 16 81 80 80 0.00000 22.15092 1.35565e-003 406 705 -1.98 0.55 0.02 -18.82 65.29 -19.47 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 10 1 1 1 0 1 59 16 81 80 80 0.00000 22.15092 1.35565e-003 50 87 -1.98 0.55 0.02 -18.82 77.61 -19.89 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 1 1 1 1 0 1 60 16 81 80 80 0.00000 22.15092 1.35565e-003 52 90 -1.98 0.55 0.02 -18.79 -33.29 -16.06 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 - 2 1 1 1 0 1 61 16 81 80 80 0.00000 22.15092 1.35565e-003 691 1201 -1.98 0.55 0.02 -18.79 -20.97 -16.49 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 - 3 1 1 1 0 1 62 16 81 80 80 0.00000 22.15092 1.35565e-003 1598 2777 -1.98 0.55 0.02 -18.80 -8.65 -16.91 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 - 4 1 1 1 0 1 63 16 81 80 80 0.00000 22.15092 1.35565e-003 4569 7943 -1.98 0.55 0.02 -18.80 3.67 -17.34 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 - 5 1 1 1 0 1 64 16 81 80 80 0.00000 22.15092 1.35565e-003 1526 2653 -1.98 0.55 0.02 -18.80 16.00 -17.76 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 - 6 1 1 1 0 1 65 16 81 80 80 0.00000 22.15092 1.35565e-003 1070 1860 -1.98 0.55 0.02 -18.81 28.32 -18.19 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 - 7 1 1 1 0 1 66 16 81 80 80 0.00000 22.15092 1.35565e-003 836 1453 -1.98 0.55 0.02 -18.81 40.64 -18.62 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 - 8 1 1 1 0 1 67 16 81 80 80 0.00000 22.15092 1.35565e-003 562 978 -1.98 0.55 0.02 -18.82 52.96 -19.04 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 - 9 1 1 1 0 1 68 16 81 80 80 0.00000 22.15092 1.35565e-003 1073 1865 -1.98 0.55 0.02 -18.82 65.29 -19.47 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 - 10 1 1 1 0 1 69 16 81 80 80 0.00000 22.15092 1.35565e-003 42 72 -1.98 0.55 0.02 -18.82 77.61 -19.89 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 - 1 1 1 1 0 1 70 16 81 80 80 0.00000 22.15092 1.35565e-003 53 92 -1.98 0.55 0.02 -18.79 -33.29 -16.06 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 2 1 1 1 0 1 71 16 81 80 80 0.00000 22.15092 1.35565e-003 322 561 -1.98 0.55 0.02 -18.79 -20.97 -16.49 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 3 1 1 1 0 1 72 16 81 80 80 0.00000 22.15092 1.35565e-003 718 1248 -1.98 0.55 0.02 -18.80 -8.65 -16.91 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 4 1 1 1 0 1 73 16 81 80 80 0.00000 22.15092 1.35565e-003 1440 2503 -1.98 0.55 0.02 -18.80 3.67 -17.34 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 5 1 1 1 0 1 74 16 81 80 80 0.00000 22.15092 1.35565e-003 636 1105 -1.98 0.55 0.02 -18.80 16.00 -17.76 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 6 1 1 1 0 1 75 16 81 80 80 0.00000 22.15092 1.35565e-003 467 811 -1.98 0.55 0.02 -18.81 28.32 -18.19 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 7 1 1 1 0 1 76 16 81 80 80 0.00000 22.15092 1.35565e-003 355 616 -1.98 0.55 0.02 -18.81 40.64 -18.62 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 8 1 1 1 0 1 77 16 81 80 80 0.00000 22.15092 1.35565e-003 254 441 -1.98 0.55 0.02 -18.82 52.96 -19.04 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 9 1 1 1 0 1 78 16 81 80 80 0.00000 22.15092 1.35565e-003 442 768 -1.98 0.55 0.02 -18.82 65.29 -19.47 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - 10 1 1 1 0 1 79 16 81 80 80 0.00000 22.15092 1.35565e-003 33 57 -1.98 0.55 0.02 -18.82 77.61 -19.89 10.000 2.330 0 1 0 2 1.912 1.912 91.00 0.00 0.00 1000.00 1 90.00 0 0 0 27 0.0 - -# === END OF DATA DESCRIPTION FILE =============================================== diff --git a/nibabel/tests/data/NA.PAR b/nibabel/tests/data/NA.PAR deleted file mode 100644 index 77e3b9c218..0000000000 --- a/nibabel/tests/data/NA.PAR +++ /dev/null @@ -1,111 +0,0 @@ -# === DATA DESCRIPTION FILE ====================================================== -# -# CAUTION - Investigational device. -# Limited by Federal Law to investigational use. -# -# Dataset name: H:\Export\05aug14_test_samples_4_1 -# -# CLINICAL TRYOUT Research image export tool V4.2 -# -# === GENERAL INFORMATION ======================================================== -# -. Patient name : 05aug14test -. Examination name : test -. Protocol name : Survey_32ch_HeadCoil -. Examination date/time : 2014.08.05 / 11:27:34 -. Series Type : Image MRSERIES -. Acquisition nr : 4 -. Reconstruction nr : 1 -. Scan Duration [sec] : 30.3 -. Max. number of cardiac phases : 1 -. Max. number of echoes : 1 -. Max. number of slices/locations : 9 -. Max. number of dynamics : 1 -. Max. number of mixes : 1 -. Patient position : Head First Supine -. Preparation direction : Anterior-Posterior -. Technique : T1TFE -. Scan resolution (x, y) : 256 128 -. Scan mode : MS -. Repetition time [ms] : 9.816 -. FOV (ap,fh,rl) [mm] : 250.000 250.000 50.000 -. Water Fat shift [pixels] : 3.497 -. Angulation midslice(ap,fh,rl)[degr]: 0.000 0.000 0.000 -. Off Centre midslice(ap,fh,rl) [mm] : -20.000 20.000 0.000 -. Flow compensation <0=no 1=yes> ? : 0 -. Presaturation <0=no 1=yes> ? : 0 -. Phase encoding velocity [cm/sec] : 0.000000 0.000000 0.000000 -. MTC <0=no 1=yes> ? : 0 -. SPIR <0=no 1=yes> ? : 0 -. EPI factor <0,1=no EPI> : 1 -. Dynamic scan <0=no 1=yes> ? : 0 -. Diffusion <0=no 1=yes> ? : 0 -. Diffusion echo time [ms] : 0.0000 -. Max. number of diffusion values : 1 -. Max. number of gradient orients : 1 -. Number of label types <0=no ASL> : 0 -# -# === PIXEL VALUES ============================================================= -# PV = pixel value in REC file, FP = floating point value, DV = displayed value on console -# RS = rescale slope, RI = rescale intercept, SS = scale slope -# DV = PV * RS + RI FP = DV / (RS * SS) -# -# === IMAGE INFORMATION DEFINITION ============================================= -# The rest of this file contains ONE line per image, this line contains the following information: -# -# slice number (integer) -# echo number (integer) -# dynamic scan number (integer) -# cardiac phase number (integer) -# image_type_mr (integer) -# scanning sequence (integer) -# index in REC file (in images) (integer) -# image pixel size (in bits) (integer) -# scan percentage (integer) -# recon resolution (x y) (2*integer) -# rescale intercept (float) -# rescale slope (float) -# scale slope (float) -# window center (integer) -# window width (integer) -# image angulation (ap,fh,rl in degrees ) (3*float) -# image offcentre (ap,fh,rl in mm ) (3*float) -# slice thickness (in mm ) (float) -# slice gap (in mm ) (float) -# image_display_orientation (integer) -# slice orientation ( TRA/SAG/COR ) (integer) -# fmri_status_indication (integer) -# image_type_ed_es (end diast/end syst) (integer) -# pixel spacing (x,y) (in mm) (2*float) -# echo_time (float) -# dyn_scan_begin_time (float) -# trigger_time (float) -# diffusion_b_factor (float) -# number of averages (integer) -# image_flip_angle (in degrees) (float) -# cardiac frequency (bpm) (integer) -# minimum RR-interval (in ms) (integer) -# maximum RR-interval (in ms) (integer) -# TURBO factor <0=no turbo> (integer) -# Inversion delay (in ms) (float) -# diffusion b value number (imagekey!) (integer) -# gradient orientation number (imagekey!) (integer) -# contrast type (string) -# diffusion anisotropy type (string) -# diffusion (ap, fh, rl) (3*float) -# label type (ASL) (imagekey!) (integer) -# -# === IMAGE INFORMATION ========================================================== -# sl ec dyn ph ty idx pix scan% rec size (re)scale window angulation offcentre thick gap info spacing echo dtime ttime diff avg flip freq RR-int turbo delay b grad cont anis diffusion L.ty - - 1 1 1 1 0 2 0 16 50 256 256 0.00000 4.06325 1.28441e-002 1070 1860 0.00 0.00 0.00 -20.00 20.00 20.00 10.000 10.000 0 2 0 2 0.977 0.977 4.60 0.00 0.00 0.00 1 15.00 0 0 0 64 0.0 1 1 7 0 0.000 0.000 0.000 1 - 2 1 1 1 0 2 1 16 50 256 256 0.00000 4.06325 1.28441e-002 777 1351 0.00 0.00 0.00 -20.00 20.00 0.00 10.000 10.000 0 2 0 2 0.977 0.977 4.60 0.00 0.00 0.00 1 15.00 0 0 0 64 0.0 1 1 7 0 0.000 0.000 0.000 1 - 3 1 1 1 0 2 2 16 50 256 256 0.00000 4.06325 1.28441e-002 480 835 0.00 0.00 0.00 -20.00 20.00 -20.00 10.000 10.000 0 2 0 2 0.977 0.977 4.60 0.00 0.00 0.00 1 15.00 0 0 0 64 0.0 1 1 7 0 0.000 0.000 0.000 1 - 4 1 1 1 0 2 3 16 50 256 256 0.00000 4.06325 1.28441e-002 1645 2859 0.00 0.00 0.00 -20.00 20.00 0.00 10.000 10.000 0 3 0 2 0.977 0.977 4.60 0.00 0.00 0.00 1 15.00 0 0 0 64 0.0 1 1 7 0 0.000 0.000 0.000 1 - 5 1 1 1 0 2 4 16 50 256 256 0.00000 4.06325 1.28441e-002 902 1567 0.00 0.00 0.00 0.00 20.00 0.00 10.000 10.000 0 3 0 2 0.977 0.977 4.60 0.00 0.00 0.00 1 15.00 0 0 0 64 0.0 1 1 7 0 0.000 0.000 0.000 1 - 6 1 1 1 0 2 5 16 50 256 256 0.00000 4.06325 1.28441e-002 109 190 0.00 0.00 0.00 20.00 20.00 0.00 10.000 10.000 0 3 0 2 0.977 0.977 4.60 0.00 0.00 0.00 1 15.00 0 0 0 64 0.0 1 1 7 0 0.000 0.000 0.000 1 - 7 1 1 1 0 2 6 16 50 256 256 0.00000 4.06325 1.28441e-002 1241 2156 0.00 0.00 0.00 0.00 20.00 0.00 10.000 10.000 0 1 0 2 0.977 0.977 4.60 0.00 0.00 0.00 1 15.00 0 0 0 64 0.0 1 1 7 0 0.000 0.000 0.000 1 - 8 1 1 1 0 2 7 16 50 256 256 0.00000 4.06325 1.28441e-002 941 1636 0.00 0.00 0.00 0.00 40.00 0.00 10.000 10.000 0 1 0 2 0.977 0.977 4.60 0.00 0.00 0.00 1 15.00 0 0 0 64 0.0 1 1 7 0 0.000 0.000 0.000 1 - 9 1 1 1 0 2 8 16 50 256 256 0.00000 4.06325 1.28441e-002 150 260 0.00 0.00 0.00 0.00 60.00 0.00 10.000 10.000 0 1 0 2 0.977 0.977 4.60 0.00 0.00 0.00 1 15.00 0 0 0 64 0.0 1 1 7 0 0.000 0.000 0.000 1 - -# === END OF DATA DESCRIPTION FILE =============================================== diff --git a/nibabel/tests/data/Phantom_EPI_3mm_cor_20APtrans_15RLrot_SENSE_15_1.PAR b/nibabel/tests/data/Phantom_EPI_3mm_cor_20APtrans_15RLrot_SENSE_15_1.PAR deleted file mode 100644 index 4b6072e3cf..0000000000 --- a/nibabel/tests/data/Phantom_EPI_3mm_cor_20APtrans_15RLrot_SENSE_15_1.PAR +++ /dev/null @@ -1,142 +0,0 @@ -# === DATA DESCRIPTION FILE ====================================================== -# -# CAUTION - Investigational device. -# Limited by Federal Law to investigational use. -# -# Dataset name: E:\\Export\\Phantom_EPI_3mm_cor_20APtrans_15RLrot_SENSE_15_1 -# -# CLINICAL TRYOUT Research image export tool V4.2 -# -# === GENERAL INFORMATION ======================================================== -# -. Patient name : Phantom -. Examination name : Orientation Par Rec -. Protocol name : EPI_3mm_cor_20APtrans_15RLrot SENSE -. Examination date/time : 2014.08.05 / 11:08:56 -. Series Type : Image MRSERIES -. Acquisition nr : 15 -. Reconstruction nr : 1 -. Scan Duration [sec] : 7.97 -. Max. number of cardiac phases : 1 -. Max. number of echoes : 1 -. Max. number of slices/locations : 40 -. Max. number of dynamics : 1 -. Max. number of mixes : 1 -. Patient position : Head First Supine -. Preparation direction : Right-Left -. Technique : FEEPI -. Scan resolution (x, y) : 80 78 -. Scan mode : MS -. Repetition time [ms] : 2000.000 -. FOV (ap,fh,rl) [mm] : 131.700 240.000 240.000 -. Water Fat shift [pixels] : 7.231 -. Angulation midslice(ap,fh,rl)[degr]: 0.000 0.000 15.000 -. Off Centre midslice(ap,fh,rl) [mm] : 20.000 0.000 0.000 -. Flow compensation <0=no 1=yes> ? : 0 -. Presaturation <0=no 1=yes> ? : 0 -. Phase encoding velocity [cm/sec] : 0.000000 0.000000 0.000000 -. MTC <0=no 1=yes> ? : 0 -. SPIR <0=no 1=yes> ? : 0 -. EPI factor <0,1=no EPI> : 39 -. Dynamic scan <0=no 1=yes> ? : 1 -. Diffusion <0=no 1=yes> ? : 0 -. Diffusion echo time [ms] : 0.0000 -. Max. number of diffusion values : 1 -. Max. number of gradient orients : 1 -. Number of label types <0=no ASL> : 0 -# -# === PIXEL VALUES ============================================================= -# PV = pixel value in REC file, FP = floating point value, DV = displayed value on console -# RS = rescale slope, RI = rescale intercept, SS = scale slope -# DV = PV * RS + RI FP = DV / (RS * SS) -# -# === IMAGE INFORMATION DEFINITION ============================================= -# The rest of this file contains ONE line per image, this line contains the following information: -# -# slice number (integer) -# echo number (integer) -# dynamic scan number (integer) -# cardiac phase number (integer) -# image_type_mr (integer) -# scanning sequence (integer) -# index in REC file (in images) (integer) -# image pixel size (in bits) (integer) -# scan percentage (integer) -# recon resolution (x y) (2*integer) -# rescale intercept (float) -# rescale slope (float) -# scale slope (float) -# window center (integer) -# window width (integer) -# image angulation (ap,fh,rl in degrees ) (3*float) -# image offcentre (ap,fh,rl in mm ) (3*float) -# slice thickness (in mm ) (float) -# slice gap (in mm ) (float) -# image_display_orientation (integer) -# slice orientation ( TRA/SAG/COR ) (integer) -# fmri_status_indication (integer) -# image_type_ed_es (end diast/end syst) (integer) -# pixel spacing (x,y) (in mm) (2*float) -# echo_time (float) -# dyn_scan_begin_time (float) -# trigger_time (float) -# diffusion_b_factor (float) -# number of averages (integer) -# image_flip_angle (in degrees) (float) -# cardiac frequency (bpm) (integer) -# minimum RR-interval (in ms) (integer) -# maximum RR-interval (in ms) (integer) -# TURBO factor <0=no turbo> (integer) -# Inversion delay (in ms) (float) -# diffusion b value number (imagekey!) (integer) -# gradient orientation number (imagekey!) (integer) -# contrast type (string) -# diffusion anisotropy type (string) -# diffusion (ap, fh, rl) (3*float) -# label type (ASL) (imagekey!) (integer) -# -# === IMAGE INFORMATION ========================================================== -# sl ec dyn ph ty idx pix scan% rec size (re)scale window angulation offcentre thick gap info spacing echo dtime ttime diff avg flip freq RR-int turbo delay b grad cont anis diffusion L.ty - - 1 1 1 1 0 2 0 16 100 80 80 0.00000 229.40904 1.57233e-002 1070 1860 0.00 -0.00 15.00 -42.16 -16.66 0.00 3.000 0.300 0 3 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 2 1 1 1 0 2 1 16 100 80 80 0.00000 229.40904 1.57233e-002 4110 7144 0.00 -0.00 15.00 -38.97 -15.80 0.00 3.000 0.300 0 3 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 3 1 1 1 0 2 2 16 100 80 80 0.00000 229.40904 1.57233e-002 13410 23311 0.00 -0.00 15.00 -35.78 -14.95 0.00 3.000 0.300 0 3 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 4 1 1 1 0 2 3 16 100 80 80 0.00000 229.40904 1.57233e-002 8655 15045 0.00 -0.00 15.00 -32.59 -14.09 0.00 3.000 0.300 0 3 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 5 1 1 1 0 2 4 16 100 80 80 0.00000 229.40904 1.57233e-002 2055 3573 0.00 -0.00 15.00 -29.41 -13.24 0.00 3.000 0.300 0 3 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 6 1 1 1 0 2 5 16 100 80 80 0.00000 229.40904 1.57233e-002 1026 1783 0.00 -0.00 15.00 -26.22 -12.38 0.00 3.000 0.300 0 3 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 7 1 1 1 0 2 6 16 100 80 80 0.00000 229.40904 1.57233e-002 1261 2193 0.00 -0.00 15.00 -23.03 -11.53 0.00 3.000 0.300 0 3 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 8 1 1 1 0 2 7 16 100 80 80 0.00000 229.40904 1.57233e-002 1307 2272 0.00 -0.00 15.00 -19.84 -10.68 0.00 3.000 0.300 0 3 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 9 1 1 1 0 2 8 16 100 80 80 0.00000 229.40904 1.57233e-002 1247 2168 0.00 -0.00 15.00 -16.66 -9.82 0.00 3.000 0.300 0 3 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 10 1 1 1 0 2 9 16 100 80 80 0.00000 229.40904 1.57233e-002 37299 64838 0.00 -0.00 15.00 -13.47 -8.97 0.00 3.000 0.300 0 3 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 11 1 1 1 0 2 10 16 100 80 80 0.00000 229.40904 1.57233e-002 126595 220062 0.00 -0.00 15.00 -10.28 -8.11 0.00 3.000 0.300 0 3 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 12 1 1 1 0 2 11 16 100 80 80 0.00000 229.40904 1.57233e-002 145851 253536 0.00 -0.00 15.00 -7.09 -7.26 0.00 3.000 0.300 0 3 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 13 1 1 1 0 2 12 16 100 80 80 0.00000 229.40904 1.57233e-002 165572 287818 0.00 -0.00 15.00 -3.91 -6.41 0.00 3.000 0.300 0 3 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 14 1 1 1 0 2 13 16 100 80 80 0.00000 229.40904 1.57233e-002 165758 288141 0.00 -0.00 15.00 -0.72 -5.55 0.00 3.000 0.300 0 3 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 15 1 1 1 0 2 14 16 100 80 80 0.00000 229.40904 1.57233e-002 164924 286690 0.00 -0.00 15.00 2.47 -4.70 0.00 3.000 0.300 0 3 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 16 1 1 1 0 2 15 16 100 80 80 0.00000 229.40904 1.57233e-002 163353 283960 0.00 -0.00 15.00 5.66 -3.84 0.00 3.000 0.300 0 3 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 17 1 1 1 0 2 16 16 100 80 80 0.00000 229.40904 1.57233e-002 168392 292718 0.00 -0.00 15.00 8.84 -2.99 0.00 3.000 0.300 0 3 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 18 1 1 1 0 2 17 16 100 80 80 0.00000 229.40904 1.57233e-002 167115 290498 0.00 -0.00 15.00 12.03 -2.14 0.00 3.000 0.300 0 3 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 19 1 1 1 0 2 18 16 100 80 80 0.00000 229.40904 1.57233e-002 175638 305314 0.00 -0.00 15.00 15.22 -1.28 0.00 3.000 0.300 0 3 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 20 1 1 1 0 2 19 16 100 80 80 0.00000 229.40904 1.57233e-002 180623 313981 0.00 -0.00 15.00 18.41 -0.43 0.00 3.000 0.300 0 3 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 21 1 1 1 0 2 20 16 100 80 80 0.00000 229.40904 1.57233e-002 177518 308583 0.00 -0.00 15.00 21.59 0.43 0.00 3.000 0.300 0 3 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 22 1 1 1 0 2 21 16 100 80 80 0.00000 229.40904 1.57233e-002 176098 306114 0.00 -0.00 15.00 24.78 1.28 0.00 3.000 0.300 0 3 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 23 1 1 1 0 2 22 16 100 80 80 0.00000 229.40904 1.57233e-002 177713 308922 0.00 -0.00 15.00 27.97 2.14 0.00 3.000 0.300 0 3 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 24 1 1 1 0 2 23 16 100 80 80 0.00000 229.40904 1.57233e-002 176764 307271 0.00 -0.00 15.00 31.16 2.99 0.00 3.000 0.300 0 3 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 25 1 1 1 0 2 24 16 100 80 80 0.00000 229.40904 1.57233e-002 176761 307266 0.00 -0.00 15.00 34.34 3.84 0.00 3.000 0.300 0 3 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 26 1 1 1 0 2 25 16 100 80 80 0.00000 229.40904 1.57233e-002 175999 305942 0.00 -0.00 15.00 37.53 4.70 0.00 3.000 0.300 0 3 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 27 1 1 1 0 2 26 16 100 80 80 0.00000 229.40904 1.57233e-002 174917 304061 0.00 -0.00 15.00 40.72 5.55 0.00 3.000 0.300 0 3 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 28 1 1 1 0 2 27 16 100 80 80 0.00000 229.40904 1.57233e-002 176697 307156 0.00 -0.00 15.00 43.91 6.41 0.00 3.000 0.300 0 3 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 29 1 1 1 0 2 28 16 100 80 80 0.00000 229.40904 1.57233e-002 176173 306245 0.00 -0.00 15.00 47.09 7.26 0.00 3.000 0.300 0 3 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 30 1 1 1 0 2 29 16 100 80 80 0.00000 229.40904 1.57233e-002 176924 307550 0.00 -0.00 15.00 50.28 8.11 0.00 3.000 0.300 0 3 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 31 1 1 1 0 2 30 16 100 80 80 0.00000 229.40904 1.57233e-002 178604 310471 0.00 -0.00 15.00 53.47 8.97 0.00 3.000 0.300 0 3 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 32 1 1 1 0 2 31 16 100 80 80 0.00000 229.40904 1.57233e-002 180280 313384 0.00 -0.00 15.00 56.66 9.82 0.00 3.000 0.300 0 3 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 33 1 1 1 0 2 32 16 100 80 80 0.00000 229.40904 1.57233e-002 182643 317492 0.00 -0.00 15.00 59.84 10.68 0.00 3.000 0.300 0 3 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 34 1 1 1 0 2 33 16 100 80 80 0.00000 229.40904 1.57233e-002 186877 324851 0.00 -0.00 15.00 63.03 11.53 0.00 3.000 0.300 0 3 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 35 1 1 1 0 2 34 16 100 80 80 0.00000 229.40904 1.57233e-002 187381 325728 0.00 -0.00 15.00 66.22 12.38 0.00 3.000 0.300 0 3 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 36 1 1 1 0 2 35 16 100 80 80 0.00000 229.40904 1.57233e-002 190117 330484 0.00 -0.00 15.00 69.41 13.24 0.00 3.000 0.300 0 3 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 37 1 1 1 0 2 36 16 100 80 80 0.00000 229.40904 1.57233e-002 193754 336806 0.00 -0.00 15.00 72.59 14.09 0.00 3.000 0.300 0 3 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 38 1 1 1 0 2 37 16 100 80 80 0.00000 229.40904 1.57233e-002 198681 345371 0.00 -0.00 15.00 75.78 14.95 0.00 3.000 0.300 0 3 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 39 1 1 1 0 2 38 16 100 80 80 0.00000 229.40904 1.57233e-002 106532 185187 0.00 -0.00 15.00 78.97 15.80 0.00 3.000 0.300 0 3 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 40 1 1 1 0 2 39 16 100 80 80 0.00000 229.40904 1.57233e-002 1545 2686 0.00 -0.00 15.00 82.16 16.66 0.00 3.000 0.300 0 3 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - -# === END OF DATA DESCRIPTION FILE =============================================== diff --git a/nibabel/tests/data/Phantom_EPI_3mm_cor_SENSE_8_1.PAR b/nibabel/tests/data/Phantom_EPI_3mm_cor_SENSE_8_1.PAR deleted file mode 100644 index 6d56bd9db4..0000000000 --- a/nibabel/tests/data/Phantom_EPI_3mm_cor_SENSE_8_1.PAR +++ /dev/null @@ -1,142 +0,0 @@ -# === DATA DESCRIPTION FILE ====================================================== -# -# CAUTION - Investigational device. -# Limited by Federal Law to investigational use. -# -# Dataset name: E:\\Export\\Phantom_EPI_3mm_cor_SENSE_8_1 -# -# CLINICAL TRYOUT Research image export tool V4.2 -# -# === GENERAL INFORMATION ======================================================== -# -. Patient name : Phantom -. Examination name : Orientation Par Rec -. Protocol name : EPI_3mm_cor SENSE -. Examination date/time : 2014.08.05 / 11:08:56 -. Series Type : Image MRSERIES -. Acquisition nr : 8 -. Reconstruction nr : 1 -. Scan Duration [sec] : 7.97 -. Max. number of cardiac phases : 1 -. Max. number of echoes : 1 -. Max. number of slices/locations : 40 -. Max. number of dynamics : 1 -. Max. number of mixes : 1 -. Patient position : Head First Supine -. Preparation direction : Right-Left -. Technique : FEEPI -. Scan resolution (x, y) : 80 78 -. Scan mode : MS -. Repetition time [ms] : 2000.000 -. FOV (ap,fh,rl) [mm] : 131.700 240.000 240.000 -. Water Fat shift [pixels] : 8.381 -. Angulation midslice(ap,fh,rl)[degr]: 0.000 0.000 0.000 -. Off Centre midslice(ap,fh,rl) [mm] : 0.000 0.000 0.000 -. Flow compensation <0=no 1=yes> ? : 0 -. Presaturation <0=no 1=yes> ? : 0 -. Phase encoding velocity [cm/sec] : 0.000000 0.000000 0.000000 -. MTC <0=no 1=yes> ? : 0 -. SPIR <0=no 1=yes> ? : 0 -. EPI factor <0,1=no EPI> : 39 -. Dynamic scan <0=no 1=yes> ? : 1 -. Diffusion <0=no 1=yes> ? : 0 -. Diffusion echo time [ms] : 0.0000 -. Max. number of diffusion values : 1 -. Max. number of gradient orients : 1 -. Number of label types <0=no ASL> : 0 -# -# === PIXEL VALUES ============================================================= -# PV = pixel value in REC file, FP = floating point value, DV = displayed value on console -# RS = rescale slope, RI = rescale intercept, SS = scale slope -# DV = PV * RS + RI FP = DV / (RS * SS) -# -# === IMAGE INFORMATION DEFINITION ============================================= -# The rest of this file contains ONE line per image, this line contains the following information: -# -# slice number (integer) -# echo number (integer) -# dynamic scan number (integer) -# cardiac phase number (integer) -# image_type_mr (integer) -# scanning sequence (integer) -# index in REC file (in images) (integer) -# image pixel size (in bits) (integer) -# scan percentage (integer) -# recon resolution (x y) (2*integer) -# rescale intercept (float) -# rescale slope (float) -# scale slope (float) -# window center (integer) -# window width (integer) -# image angulation (ap,fh,rl in degrees ) (3*float) -# image offcentre (ap,fh,rl in mm ) (3*float) -# slice thickness (in mm ) (float) -# slice gap (in mm ) (float) -# image_display_orientation (integer) -# slice orientation ( TRA/SAG/COR ) (integer) -# fmri_status_indication (integer) -# image_type_ed_es (end diast/end syst) (integer) -# pixel spacing (x,y) (in mm) (2*float) -# echo_time (float) -# dyn_scan_begin_time (float) -# trigger_time (float) -# diffusion_b_factor (float) -# number of averages (integer) -# image_flip_angle (in degrees) (float) -# cardiac frequency (bpm) (integer) -# minimum RR-interval (in ms) (integer) -# maximum RR-interval (in ms) (integer) -# TURBO factor <0=no turbo> (integer) -# Inversion delay (in ms) (float) -# diffusion b value number (imagekey!) (integer) -# gradient orientation number (imagekey!) (integer) -# contrast type (string) -# diffusion anisotropy type (string) -# diffusion (ap, fh, rl) (3*float) -# label type (ASL) (imagekey!) (integer) -# -# === IMAGE INFORMATION ========================================================== -# sl ec dyn ph ty idx pix scan% rec size (re)scale window angulation offcentre thick gap info spacing echo dtime ttime diff avg flip freq RR-int turbo delay b grad cont anis diffusion L.ty - - 1 1 1 1 0 2 0 16 100 80 80 0.00000 383.84616 1.85950e-002 1070 1860 0.00 -0.00 -0.00 -64.35 0.00 0.00 3.000 0.300 0 3 0 2 3.000 3.000 10.98 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 2 1 1 1 0 2 1 16 100 80 80 0.00000 383.84616 1.85950e-002 978 1701 0.00 -0.00 -0.00 -61.05 0.00 0.00 3.000 0.300 0 3 0 2 3.000 3.000 10.98 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 3 1 1 1 0 2 2 16 100 80 80 0.00000 383.84616 1.85950e-002 854 1485 0.00 -0.00 -0.00 -57.75 0.00 0.00 3.000 0.300 0 3 0 2 3.000 3.000 10.98 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 4 1 1 1 0 2 3 16 100 80 80 0.00000 383.84616 1.85950e-002 1079 1876 0.00 -0.00 -0.00 -54.45 0.00 0.00 3.000 0.300 0 3 0 2 3.000 3.000 10.98 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 5 1 1 1 0 2 4 16 100 80 80 0.00000 383.84616 1.85950e-002 1606 2792 0.00 -0.00 -0.00 -51.15 0.00 0.00 3.000 0.300 0 3 0 2 3.000 3.000 10.98 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 6 1 1 1 0 2 5 16 100 80 80 0.00000 383.84616 1.85950e-002 1191 2070 0.00 -0.00 -0.00 -47.85 0.00 0.00 3.000 0.300 0 3 0 2 3.000 3.000 10.98 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 7 1 1 1 0 2 6 16 100 80 80 0.00000 383.84616 1.85950e-002 2928 5090 0.00 -0.00 -0.00 -44.55 0.00 0.00 3.000 0.300 0 3 0 2 3.000 3.000 10.98 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 8 1 1 1 0 2 7 16 100 80 80 0.00000 383.84616 1.85950e-002 19125 33244 0.00 -0.00 -0.00 -41.25 0.00 0.00 3.000 0.300 0 3 0 2 3.000 3.000 10.98 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 9 1 1 1 0 2 8 16 100 80 80 0.00000 383.84616 1.85950e-002 14988 26055 0.00 -0.00 -0.00 -37.95 0.00 0.00 3.000 0.300 0 3 0 2 3.000 3.000 10.98 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 10 1 1 1 0 2 9 16 100 80 80 0.00000 383.84616 1.85950e-002 1530 2659 0.00 -0.00 -0.00 -34.65 0.00 0.00 3.000 0.300 0 3 0 2 3.000 3.000 10.98 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 11 1 1 1 0 2 10 16 100 80 80 0.00000 383.84616 1.85950e-002 1147 1994 0.00 -0.00 -0.00 -31.35 0.00 0.00 3.000 0.300 0 3 0 2 3.000 3.000 10.98 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 12 1 1 1 0 2 11 16 100 80 80 0.00000 383.84616 1.85950e-002 1874 3257 0.00 -0.00 -0.00 -28.05 0.00 0.00 3.000 0.300 0 3 0 2 3.000 3.000 10.98 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 13 1 1 1 0 2 12 16 100 80 80 0.00000 383.84616 1.85950e-002 2386 4147 0.00 -0.00 -0.00 -24.75 0.00 0.00 3.000 0.300 0 3 0 2 3.000 3.000 10.98 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 14 1 1 1 0 2 13 16 100 80 80 0.00000 383.84616 1.85950e-002 1822 3167 0.00 -0.00 -0.00 -21.45 0.00 0.00 3.000 0.300 0 3 0 2 3.000 3.000 10.98 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 15 1 1 1 0 2 14 16 100 80 80 0.00000 383.84616 1.85950e-002 2660 4625 0.00 -0.00 -0.00 -18.15 0.00 0.00 3.000 0.300 0 3 0 2 3.000 3.000 10.98 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 16 1 1 1 0 2 15 16 100 80 80 0.00000 383.84616 1.85950e-002 50239 87332 0.00 -0.00 -0.00 -14.85 0.00 0.00 3.000 0.300 0 3 0 2 3.000 3.000 10.98 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 17 1 1 1 0 2 16 16 100 80 80 0.00000 383.84616 1.85950e-002 222975 387602 0.00 -0.00 -0.00 -11.55 0.00 0.00 3.000 0.300 0 3 0 2 3.000 3.000 10.98 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 18 1 1 1 0 2 17 16 100 80 80 0.00000 383.84616 1.85950e-002 330374 574296 0.00 -0.00 -0.00 -8.25 0.00 0.00 3.000 0.300 0 3 0 2 3.000 3.000 10.98 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 19 1 1 1 0 2 18 16 100 80 80 0.00000 383.84616 1.85950e-002 324395 563901 0.00 -0.00 -0.00 -4.95 0.00 0.00 3.000 0.300 0 3 0 2 3.000 3.000 10.98 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 20 1 1 1 0 2 19 16 100 80 80 0.00000 383.84616 1.85950e-002 317227 551441 0.00 -0.00 -0.00 -1.65 0.00 0.00 3.000 0.300 0 3 0 2 3.000 3.000 10.98 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 21 1 1 1 0 2 20 16 100 80 80 0.00000 383.84616 1.85950e-002 321375 558651 0.00 -0.00 -0.00 1.65 0.00 0.00 3.000 0.300 0 3 0 2 3.000 3.000 10.98 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 22 1 1 1 0 2 21 16 100 80 80 0.00000 383.84616 1.85950e-002 329987 573621 0.00 -0.00 -0.00 4.95 0.00 0.00 3.000 0.300 0 3 0 2 3.000 3.000 10.98 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 23 1 1 1 0 2 22 16 100 80 80 0.00000 383.84616 1.85950e-002 331076 575515 0.00 -0.00 -0.00 8.25 0.00 0.00 3.000 0.300 0 3 0 2 3.000 3.000 10.98 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 24 1 1 1 0 2 23 16 100 80 80 0.00000 383.84616 1.85950e-002 341217 593144 0.00 -0.00 -0.00 11.55 0.00 0.00 3.000 0.300 0 3 0 2 3.000 3.000 10.98 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 25 1 1 1 0 2 24 16 100 80 80 0.00000 383.84616 1.85950e-002 346832 602903 0.00 -0.00 -0.00 14.85 0.00 0.00 3.000 0.300 0 3 0 2 3.000 3.000 10.98 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 26 1 1 1 0 2 25 16 100 80 80 0.00000 383.84616 1.85950e-002 348800 606325 0.00 -0.00 -0.00 18.15 0.00 0.00 3.000 0.300 0 3 0 2 3.000 3.000 10.98 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 27 1 1 1 0 2 26 16 100 80 80 0.00000 383.84616 1.85950e-002 354770 616703 0.00 -0.00 -0.00 21.45 0.00 0.00 3.000 0.300 0 3 0 2 3.000 3.000 10.98 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 28 1 1 1 0 2 27 16 100 80 80 0.00000 383.84616 1.85950e-002 350225 608803 0.00 -0.00 -0.00 24.75 0.00 0.00 3.000 0.300 0 3 0 2 3.000 3.000 10.98 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 29 1 1 1 0 2 28 16 100 80 80 0.00000 383.84616 1.85950e-002 343897 597803 0.00 -0.00 -0.00 28.05 0.00 0.00 3.000 0.300 0 3 0 2 3.000 3.000 10.98 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 30 1 1 1 0 2 29 16 100 80 80 0.00000 383.84616 1.85950e-002 345817 601139 0.00 -0.00 -0.00 31.35 0.00 0.00 3.000 0.300 0 3 0 2 3.000 3.000 10.98 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 31 1 1 1 0 2 30 16 100 80 80 0.00000 383.84616 1.85950e-002 341615 593835 0.00 -0.00 -0.00 34.65 0.00 0.00 3.000 0.300 0 3 0 2 3.000 3.000 10.98 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 32 1 1 1 0 2 31 16 100 80 80 0.00000 383.84616 1.85950e-002 347921 604798 0.00 -0.00 -0.00 37.95 0.00 0.00 3.000 0.300 0 3 0 2 3.000 3.000 10.98 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 33 1 1 1 0 2 32 16 100 80 80 0.00000 383.84616 1.85950e-002 352208 612249 0.00 -0.00 -0.00 41.25 0.00 0.00 3.000 0.300 0 3 0 2 3.000 3.000 10.98 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 34 1 1 1 0 2 33 16 100 80 80 0.00000 383.84616 1.85950e-002 356572 619836 0.00 -0.00 -0.00 44.55 0.00 0.00 3.000 0.300 0 3 0 2 3.000 3.000 10.98 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 35 1 1 1 0 2 34 16 100 80 80 0.00000 383.84616 1.85950e-002 348625 606021 0.00 -0.00 -0.00 47.85 0.00 0.00 3.000 0.300 0 3 0 2 3.000 3.000 10.98 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 36 1 1 1 0 2 35 16 100 80 80 0.00000 383.84616 1.85950e-002 353473 614449 0.00 -0.00 -0.00 51.15 0.00 0.00 3.000 0.300 0 3 0 2 3.000 3.000 10.98 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 37 1 1 1 0 2 36 16 100 80 80 0.00000 383.84616 1.85950e-002 362771 630611 0.00 -0.00 -0.00 54.45 0.00 0.00 3.000 0.300 0 3 0 2 3.000 3.000 10.98 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 38 1 1 1 0 2 37 16 100 80 80 0.00000 383.84616 1.85950e-002 362080 629410 0.00 -0.00 -0.00 57.75 0.00 0.00 3.000 0.300 0 3 0 2 3.000 3.000 10.98 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 39 1 1 1 0 2 38 16 100 80 80 0.00000 383.84616 1.85950e-002 361879 629060 0.00 -0.00 -0.00 61.05 0.00 0.00 3.000 0.300 0 3 0 2 3.000 3.000 10.98 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 40 1 1 1 0 2 39 16 100 80 80 0.00000 383.84616 1.85950e-002 369991 643162 0.00 -0.00 -0.00 64.35 0.00 0.00 3.000 0.300 0 3 0 2 3.000 3.000 10.98 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - -# === END OF DATA DESCRIPTION FILE =============================================== diff --git a/nibabel/tests/data/Phantom_EPI_3mm_sag_15AP_SENSE_13_1.PAR b/nibabel/tests/data/Phantom_EPI_3mm_sag_15AP_SENSE_13_1.PAR deleted file mode 100644 index d0f3ac5930..0000000000 --- a/nibabel/tests/data/Phantom_EPI_3mm_sag_15AP_SENSE_13_1.PAR +++ /dev/null @@ -1,142 +0,0 @@ -# === DATA DESCRIPTION FILE ====================================================== -# -# CAUTION - Investigational device. -# Limited by Federal Law to investigational use. -# -# Dataset name: E:\\Export\\Phantom_EPI_3mm_sag_15AP_SENSE_13_1 -# -# CLINICAL TRYOUT Research image export tool V4.2 -# -# === GENERAL INFORMATION ======================================================== -# -. Patient name : Phantom -. Examination name : Orientation Par Rec -. Protocol name : EPI_3mm_sag_15AP SENSE -. Examination date/time : 2014.08.05 / 11:08:56 -. Series Type : Image MRSERIES -. Acquisition nr : 13 -. Reconstruction nr : 1 -. Scan Duration [sec] : 7.97 -. Max. number of cardiac phases : 1 -. Max. number of echoes : 1 -. Max. number of slices/locations : 40 -. Max. number of dynamics : 1 -. Max. number of mixes : 1 -. Patient position : Head First Supine -. Preparation direction : Anterior-Posterior -. Technique : FEEPI -. Scan resolution (x, y) : 80 78 -. Scan mode : MS -. Repetition time [ms] : 2000.000 -. FOV (ap,fh,rl) [mm] : 240.000 240.000 131.700 -. Water Fat shift [pixels] : 7.231 -. Angulation midslice(ap,fh,rl)[degr]: 15.000 0.000 0.000 -. Off Centre midslice(ap,fh,rl) [mm] : 0.000 0.000 0.000 -. Flow compensation <0=no 1=yes> ? : 0 -. Presaturation <0=no 1=yes> ? : 0 -. Phase encoding velocity [cm/sec] : 0.000000 0.000000 0.000000 -. MTC <0=no 1=yes> ? : 0 -. SPIR <0=no 1=yes> ? : 0 -. EPI factor <0,1=no EPI> : 39 -. Dynamic scan <0=no 1=yes> ? : 1 -. Diffusion <0=no 1=yes> ? : 0 -. Diffusion echo time [ms] : 0.0000 -. Max. number of diffusion values : 1 -. Max. number of gradient orients : 1 -. Number of label types <0=no ASL> : 0 -# -# === PIXEL VALUES ============================================================= -# PV = pixel value in REC file, FP = floating point value, DV = displayed value on console -# RS = rescale slope, RI = rescale intercept, SS = scale slope -# DV = PV * RS + RI FP = DV / (RS * SS) -# -# === IMAGE INFORMATION DEFINITION ============================================= -# The rest of this file contains ONE line per image, this line contains the following information: -# -# slice number (integer) -# echo number (integer) -# dynamic scan number (integer) -# cardiac phase number (integer) -# image_type_mr (integer) -# scanning sequence (integer) -# index in REC file (in images) (integer) -# image pixel size (in bits) (integer) -# scan percentage (integer) -# recon resolution (x y) (2*integer) -# rescale intercept (float) -# rescale slope (float) -# scale slope (float) -# window center (integer) -# window width (integer) -# image angulation (ap,fh,rl in degrees ) (3*float) -# image offcentre (ap,fh,rl in mm ) (3*float) -# slice thickness (in mm ) (float) -# slice gap (in mm ) (float) -# image_display_orientation (integer) -# slice orientation ( TRA/SAG/COR ) (integer) -# fmri_status_indication (integer) -# image_type_ed_es (end diast/end syst) (integer) -# pixel spacing (x,y) (in mm) (2*float) -# echo_time (float) -# dyn_scan_begin_time (float) -# trigger_time (float) -# diffusion_b_factor (float) -# number of averages (integer) -# image_flip_angle (in degrees) (float) -# cardiac frequency (bpm) (integer) -# minimum RR-interval (in ms) (integer) -# maximum RR-interval (in ms) (integer) -# TURBO factor <0=no turbo> (integer) -# Inversion delay (in ms) (float) -# diffusion b value number (imagekey!) (integer) -# gradient orientation number (imagekey!) (integer) -# contrast type (string) -# diffusion anisotropy type (string) -# diffusion (ap, fh, rl) (3*float) -# label type (ASL) (imagekey!) (integer) -# -# === IMAGE INFORMATION ========================================================== -# sl ec dyn ph ty idx pix scan% rec size (re)scale window angulation offcentre thick gap info spacing echo dtime ttime diff avg flip freq RR-int turbo delay b grad cont anis diffusion L.ty - - 1 1 1 1 0 2 0 16 100 80 80 0.00000 379.58487 1.53218e-002 1070 1860 15.00 -0.00 -0.00 0.00 -16.66 62.16 3.000 0.300 0 2 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 2 1 1 1 0 2 1 16 100 80 80 0.00000 379.58487 1.53218e-002 1576 2740 15.00 -0.00 -0.00 0.00 -15.80 58.97 3.000 0.300 0 2 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 3 1 1 1 0 2 2 16 100 80 80 0.00000 379.58487 1.53218e-002 32199 55971 15.00 -0.00 -0.00 0.00 -14.95 55.78 3.000 0.300 0 2 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 4 1 1 1 0 2 3 16 100 80 80 0.00000 379.58487 1.53218e-002 49226 85570 15.00 -0.00 -0.00 0.00 -14.09 52.59 3.000 0.300 0 2 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 5 1 1 1 0 2 4 16 100 80 80 0.00000 379.58487 1.53218e-002 31645 55008 15.00 -0.00 -0.00 0.00 -13.24 49.41 3.000 0.300 0 2 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 6 1 1 1 0 2 5 16 100 80 80 0.00000 379.58487 1.53218e-002 36888 64124 15.00 -0.00 -0.00 0.00 -12.38 46.22 3.000 0.300 0 2 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 7 1 1 1 0 2 6 16 100 80 80 0.00000 379.58487 1.53218e-002 12781 22217 15.00 -0.00 -0.00 0.00 -11.53 43.03 3.000 0.300 0 2 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 8 1 1 1 0 2 7 16 100 80 80 0.00000 379.58487 1.53218e-002 233716 406273 15.00 -0.00 -0.00 0.00 -10.68 39.84 3.000 0.300 0 2 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 9 1 1 1 0 2 8 16 100 80 80 0.00000 379.58487 1.53218e-002 256318 445562 15.00 -0.00 -0.00 0.00 -9.82 36.66 3.000 0.300 0 2 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 10 1 1 1 0 2 9 16 100 80 80 0.00000 379.58487 1.53218e-002 262949 457089 15.00 -0.00 -0.00 0.00 -8.97 33.47 3.000 0.300 0 2 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 11 1 1 1 0 2 10 16 100 80 80 0.00000 379.58487 1.53218e-002 274600 477342 15.00 -0.00 -0.00 0.00 -8.11 30.28 3.000 0.300 0 2 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 12 1 1 1 0 2 11 16 100 80 80 0.00000 379.58487 1.53218e-002 279784 486353 15.00 -0.00 -0.00 0.00 -7.26 27.09 3.000 0.300 0 2 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 13 1 1 1 0 2 12 16 100 80 80 0.00000 379.58487 1.53218e-002 285642 496536 15.00 -0.00 -0.00 0.00 -6.41 23.91 3.000 0.300 0 2 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 14 1 1 1 0 2 13 16 100 80 80 0.00000 379.58487 1.53218e-002 289907 503951 15.00 -0.00 -0.00 0.00 -5.55 20.72 3.000 0.300 0 2 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 15 1 1 1 0 2 14 16 100 80 80 0.00000 379.58487 1.53218e-002 300159 521772 15.00 -0.00 -0.00 0.00 -4.70 17.53 3.000 0.300 0 2 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 16 1 1 1 0 2 15 16 100 80 80 0.00000 379.58487 1.53218e-002 302576 525973 15.00 -0.00 -0.00 0.00 -3.84 14.34 3.000 0.300 0 2 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 17 1 1 1 0 2 16 16 100 80 80 0.00000 379.58487 1.53218e-002 312170 542651 15.00 -0.00 -0.00 0.00 -2.99 11.16 3.000 0.300 0 2 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 18 1 1 1 0 2 17 16 100 80 80 0.00000 379.58487 1.53218e-002 316827 550746 15.00 -0.00 -0.00 0.00 -2.14 7.97 3.000 0.300 0 2 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 19 1 1 1 0 2 18 16 100 80 80 0.00000 379.58487 1.53218e-002 318740 554072 15.00 -0.00 -0.00 0.00 -1.28 4.78 3.000 0.300 0 2 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 20 1 1 1 0 2 19 16 100 80 80 0.00000 379.58487 1.53218e-002 313900 545657 15.00 -0.00 -0.00 0.00 -0.43 1.59 3.000 0.300 0 2 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 21 1 1 1 0 2 20 16 100 80 80 0.00000 379.58487 1.53218e-002 317106 551230 15.00 -0.00 -0.00 0.00 0.43 -1.59 3.000 0.300 0 2 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 22 1 1 1 0 2 21 16 100 80 80 0.00000 379.58487 1.53218e-002 322465 560547 15.00 -0.00 -0.00 0.00 1.28 -4.78 3.000 0.300 0 2 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 23 1 1 1 0 2 22 16 100 80 80 0.00000 379.58487 1.53218e-002 323730 562745 15.00 -0.00 -0.00 0.00 2.14 -7.97 3.000 0.300 0 2 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 24 1 1 1 0 2 23 16 100 80 80 0.00000 379.58487 1.53218e-002 319532 555448 15.00 -0.00 -0.00 0.00 2.99 -11.16 3.000 0.300 0 2 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 25 1 1 1 0 2 24 16 100 80 80 0.00000 379.58487 1.53218e-002 325121 565164 15.00 -0.00 -0.00 0.00 3.84 -14.34 3.000 0.300 0 2 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 26 1 1 1 0 2 25 16 100 80 80 0.00000 379.58487 1.53218e-002 323653 562611 15.00 -0.00 -0.00 0.00 4.70 -17.53 3.000 0.300 0 2 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 27 1 1 1 0 2 26 16 100 80 80 0.00000 379.58487 1.53218e-002 326501 567562 15.00 -0.00 -0.00 0.00 5.55 -20.72 3.000 0.300 0 2 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 28 1 1 1 0 2 27 16 100 80 80 0.00000 379.58487 1.53218e-002 325199 565300 15.00 -0.00 -0.00 0.00 6.41 -23.91 3.000 0.300 0 2 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 29 1 1 1 0 2 28 16 100 80 80 0.00000 379.58487 1.53218e-002 320664 557416 15.00 -0.00 -0.00 0.00 7.26 -27.09 3.000 0.300 0 2 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 30 1 1 1 0 2 29 16 100 80 80 0.00000 379.58487 1.53218e-002 317113 551243 15.00 -0.00 -0.00 0.00 8.11 -30.28 3.000 0.300 0 2 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 31 1 1 1 0 2 30 16 100 80 80 0.00000 379.58487 1.53218e-002 305591 531214 15.00 -0.00 -0.00 0.00 8.97 -33.47 3.000 0.300 0 2 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 32 1 1 1 0 2 31 16 100 80 80 0.00000 379.58487 1.53218e-002 303485 527553 15.00 -0.00 -0.00 0.00 9.82 -36.66 3.000 0.300 0 2 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 33 1 1 1 0 2 32 16 100 80 80 0.00000 379.58487 1.53218e-002 300166 521784 15.00 -0.00 -0.00 0.00 10.68 -39.84 3.000 0.300 0 2 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 34 1 1 1 0 2 33 16 100 80 80 0.00000 379.58487 1.53218e-002 294512 511956 15.00 -0.00 -0.00 0.00 11.53 -43.03 3.000 0.300 0 2 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 35 1 1 1 0 2 34 16 100 80 80 0.00000 379.58487 1.53218e-002 283583 492958 15.00 -0.00 -0.00 0.00 12.38 -46.22 3.000 0.300 0 2 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 36 1 1 1 0 2 35 16 100 80 80 0.00000 379.58487 1.53218e-002 279239 485407 15.00 -0.00 -0.00 0.00 13.24 -49.41 3.000 0.300 0 2 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 37 1 1 1 0 2 36 16 100 80 80 0.00000 379.58487 1.53218e-002 276832 481222 15.00 -0.00 -0.00 0.00 14.09 -52.59 3.000 0.300 0 2 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 38 1 1 1 0 2 37 16 100 80 80 0.00000 379.58487 1.53218e-002 66087 114880 15.00 -0.00 -0.00 0.00 14.95 -55.78 3.000 0.300 0 2 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 39 1 1 1 0 2 38 16 100 80 80 0.00000 379.58487 1.53218e-002 1938 3369 15.00 -0.00 -0.00 0.00 15.80 -58.97 3.000 0.300 0 2 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 40 1 1 1 0 2 39 16 100 80 80 0.00000 379.58487 1.53218e-002 1055 1834 15.00 -0.00 -0.00 0.00 16.66 -62.16 3.000 0.300 0 2 0 2 3.000 3.000 9.66 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - -# === END OF DATA DESCRIPTION FILE =============================================== diff --git a/nibabel/tests/data/Phantom_EPI_3mm_sag_15FH_SENSE_12_1.PAR b/nibabel/tests/data/Phantom_EPI_3mm_sag_15FH_SENSE_12_1.PAR deleted file mode 100644 index c4bd9db947..0000000000 --- a/nibabel/tests/data/Phantom_EPI_3mm_sag_15FH_SENSE_12_1.PAR +++ /dev/null @@ -1,142 +0,0 @@ -# === DATA DESCRIPTION FILE ====================================================== -# -# CAUTION - Investigational device. -# Limited by Federal Law to investigational use. -# -# Dataset name: E:\\Export\\Phantom_EPI_3mm_sag_15FH_SENSE_12_1 -# -# CLINICAL TRYOUT Research image export tool V4.2 -# -# === GENERAL INFORMATION ======================================================== -# -. Patient name : Phantom -. Examination name : Orientation Par Rec -. Protocol name : EPI_3mm_sag_15FH SENSE -. Examination date/time : 2014.08.05 / 11:08:56 -. Series Type : Image MRSERIES -. Acquisition nr : 12 -. Reconstruction nr : 1 -. Scan Duration [sec] : 7.97 -. Max. number of cardiac phases : 1 -. Max. number of echoes : 1 -. Max. number of slices/locations : 40 -. Max. number of dynamics : 1 -. Max. number of mixes : 1 -. Patient position : Head First Supine -. Preparation direction : Anterior-Posterior -. Technique : FEEPI -. Scan resolution (x, y) : 80 78 -. Scan mode : MS -. Repetition time [ms] : 2000.000 -. FOV (ap,fh,rl) [mm] : 240.000 240.000 131.700 -. Water Fat shift [pixels] : 8.121 -. Angulation midslice(ap,fh,rl)[degr]: 0.000 15.000 0.000 -. Off Centre midslice(ap,fh,rl) [mm] : 0.000 0.000 0.000 -. Flow compensation <0=no 1=yes> ? : 0 -. Presaturation <0=no 1=yes> ? : 0 -. Phase encoding velocity [cm/sec] : 0.000000 0.000000 0.000000 -. MTC <0=no 1=yes> ? : 0 -. SPIR <0=no 1=yes> ? : 0 -. EPI factor <0,1=no EPI> : 39 -. Dynamic scan <0=no 1=yes> ? : 1 -. Diffusion <0=no 1=yes> ? : 0 -. Diffusion echo time [ms] : 0.0000 -. Max. number of diffusion values : 1 -. Max. number of gradient orients : 1 -. Number of label types <0=no ASL> : 0 -# -# === PIXEL VALUES ============================================================= -# PV = pixel value in REC file, FP = floating point value, DV = displayed value on console -# RS = rescale slope, RI = rescale intercept, SS = scale slope -# DV = PV * RS + RI FP = DV / (RS * SS) -# -# === IMAGE INFORMATION DEFINITION ============================================= -# The rest of this file contains ONE line per image, this line contains the following information: -# -# slice number (integer) -# echo number (integer) -# dynamic scan number (integer) -# cardiac phase number (integer) -# image_type_mr (integer) -# scanning sequence (integer) -# index in REC file (in images) (integer) -# image pixel size (in bits) (integer) -# scan percentage (integer) -# recon resolution (x y) (2*integer) -# rescale intercept (float) -# rescale slope (float) -# scale slope (float) -# window center (integer) -# window width (integer) -# image angulation (ap,fh,rl in degrees ) (3*float) -# image offcentre (ap,fh,rl in mm ) (3*float) -# slice thickness (in mm ) (float) -# slice gap (in mm ) (float) -# image_display_orientation (integer) -# slice orientation ( TRA/SAG/COR ) (integer) -# fmri_status_indication (integer) -# image_type_ed_es (end diast/end syst) (integer) -# pixel spacing (x,y) (in mm) (2*float) -# echo_time (float) -# dyn_scan_begin_time (float) -# trigger_time (float) -# diffusion_b_factor (float) -# number of averages (integer) -# image_flip_angle (in degrees) (float) -# cardiac frequency (bpm) (integer) -# minimum RR-interval (in ms) (integer) -# maximum RR-interval (in ms) (integer) -# TURBO factor <0=no turbo> (integer) -# Inversion delay (in ms) (float) -# diffusion b value number (imagekey!) (integer) -# gradient orientation number (imagekey!) (integer) -# contrast type (string) -# diffusion anisotropy type (string) -# diffusion (ap, fh, rl) (3*float) -# label type (ASL) (imagekey!) (integer) -# -# === IMAGE INFORMATION ========================================================== -# sl ec dyn ph ty idx pix scan% rec size (re)scale window angulation offcentre thick gap info spacing echo dtime ttime diff avg flip freq RR-int turbo delay b grad cont anis diffusion L.ty - - 1 1 1 1 0 2 0 16 100 80 80 0.00000 28.40195 1.68760e-002 1070 1860 -0.00 15.00 -0.00 16.66 0.00 62.16 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 2 1 1 1 0 2 1 16 100 80 80 0.00000 28.40195 1.68760e-002 1721 2992 -0.00 15.00 -0.00 15.80 0.00 58.97 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 3 1 1 1 0 2 2 16 100 80 80 0.00000 28.40195 1.68760e-002 1646 2861 -0.00 15.00 -0.00 14.95 0.00 55.78 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 4 1 1 1 0 2 3 16 100 80 80 0.00000 28.40195 1.68760e-002 3328 5785 -0.00 15.00 -0.00 14.09 0.00 52.59 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 5 1 1 1 0 2 4 16 100 80 80 0.00000 28.40195 1.68760e-002 20485 35609 -0.00 15.00 -0.00 13.24 0.00 49.41 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 6 1 1 1 0 2 5 16 100 80 80 0.00000 28.40195 1.68760e-002 22401 38940 -0.00 15.00 -0.00 12.38 0.00 46.22 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 7 1 1 1 0 2 6 16 100 80 80 0.00000 28.40195 1.68760e-002 23426 40721 -0.00 15.00 -0.00 11.53 0.00 43.03 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 8 1 1 1 0 2 7 16 100 80 80 0.00000 28.40195 1.68760e-002 23915 41572 -0.00 15.00 -0.00 10.68 0.00 39.84 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 9 1 1 1 0 2 8 16 100 80 80 0.00000 28.40195 1.68760e-002 24509 42604 -0.00 15.00 -0.00 9.82 0.00 36.66 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 10 1 1 1 0 2 9 16 100 80 80 0.00000 28.40195 1.68760e-002 25379 44117 -0.00 15.00 -0.00 8.97 0.00 33.47 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 11 1 1 1 0 2 10 16 100 80 80 0.00000 28.40195 1.68760e-002 25423 44193 -0.00 15.00 -0.00 8.11 0.00 30.28 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 12 1 1 1 0 2 11 16 100 80 80 0.00000 28.40195 1.68760e-002 25437 44218 -0.00 15.00 -0.00 7.26 0.00 27.09 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 13 1 1 1 0 2 12 16 100 80 80 0.00000 28.40195 1.68760e-002 25657 44600 -0.00 15.00 -0.00 6.41 0.00 23.91 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 14 1 1 1 0 2 13 16 100 80 80 0.00000 28.40195 1.68760e-002 25584 44473 -0.00 15.00 -0.00 5.55 0.00 20.72 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 15 1 1 1 0 2 14 16 100 80 80 0.00000 28.40195 1.68760e-002 25960 45127 -0.00 15.00 -0.00 4.70 0.00 17.53 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 16 1 1 1 0 2 15 16 100 80 80 0.00000 28.40195 1.68760e-002 26082 45340 -0.00 15.00 -0.00 3.84 0.00 14.34 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 17 1 1 1 0 2 16 16 100 80 80 0.00000 28.40195 1.68760e-002 25961 45128 -0.00 15.00 -0.00 2.99 0.00 11.16 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 18 1 1 1 0 2 17 16 100 80 80 0.00000 28.40195 1.68760e-002 26290 45701 -0.00 15.00 -0.00 2.14 0.00 7.97 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 19 1 1 1 0 2 18 16 100 80 80 0.00000 28.40195 1.68760e-002 26300 45718 -0.00 15.00 -0.00 1.28 0.00 4.78 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 20 1 1 1 0 2 19 16 100 80 80 0.00000 28.40195 1.68760e-002 26601 46241 -0.00 15.00 -0.00 0.43 0.00 1.59 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 21 1 1 1 0 2 20 16 100 80 80 0.00000 28.40195 1.68760e-002 26447 45973 -0.00 15.00 -0.00 -0.43 0.00 -1.59 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 22 1 1 1 0 2 21 16 100 80 80 0.00000 28.40195 1.68760e-002 26810 46605 -0.00 15.00 -0.00 -1.28 0.00 -4.78 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 23 1 1 1 0 2 22 16 100 80 80 0.00000 28.40195 1.68760e-002 26046 45276 -0.00 15.00 -0.00 -2.14 0.00 -7.97 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 24 1 1 1 0 2 23 16 100 80 80 0.00000 28.40195 1.68760e-002 26687 46390 -0.00 15.00 -0.00 -2.99 0.00 -11.16 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 25 1 1 1 0 2 24 16 100 80 80 0.00000 28.40195 1.68760e-002 26879 46724 -0.00 15.00 -0.00 -3.84 0.00 -14.34 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 26 1 1 1 0 2 25 16 100 80 80 0.00000 28.40195 1.68760e-002 26437 45955 -0.00 15.00 -0.00 -4.70 0.00 -17.53 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 27 1 1 1 0 2 26 16 100 80 80 0.00000 28.40195 1.68760e-002 26711 46433 -0.00 15.00 -0.00 -5.55 0.00 -20.72 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 28 1 1 1 0 2 27 16 100 80 80 0.00000 28.40195 1.68760e-002 26787 46565 -0.00 15.00 -0.00 -6.41 0.00 -23.91 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 29 1 1 1 0 2 28 16 100 80 80 0.00000 28.40195 1.68760e-002 25029 43508 -0.00 15.00 -0.00 -7.26 0.00 -27.09 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 30 1 1 1 0 2 29 16 100 80 80 0.00000 28.40195 1.68760e-002 24056 41816 -0.00 15.00 -0.00 -8.11 0.00 -30.28 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 31 1 1 1 0 2 30 16 100 80 80 0.00000 28.40195 1.68760e-002 23063 40090 -0.00 15.00 -0.00 -8.97 0.00 -33.47 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 32 1 1 1 0 2 31 16 100 80 80 0.00000 28.40195 1.68760e-002 22503 39118 -0.00 15.00 -0.00 -9.82 0.00 -36.66 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 33 1 1 1 0 2 32 16 100 80 80 0.00000 28.40195 1.68760e-002 22341 38836 -0.00 15.00 -0.00 -10.68 0.00 -39.84 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 34 1 1 1 0 2 33 16 100 80 80 0.00000 28.40195 1.68760e-002 22076 38375 -0.00 15.00 -0.00 -11.53 0.00 -43.03 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 35 1 1 1 0 2 34 16 100 80 80 0.00000 28.40195 1.68760e-002 2188 3803 -0.00 15.00 -0.00 -12.38 0.00 -46.22 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 36 1 1 1 0 2 35 16 100 80 80 0.00000 28.40195 1.68760e-002 108 188 -0.00 15.00 -0.00 -13.24 0.00 -49.41 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 37 1 1 1 0 2 36 16 100 80 80 0.00000 28.40195 1.68760e-002 80 140 -0.00 15.00 -0.00 -14.09 0.00 -52.59 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 38 1 1 1 0 2 37 16 100 80 80 0.00000 28.40195 1.68760e-002 59 103 -0.00 15.00 -0.00 -14.95 0.00 -55.78 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 39 1 1 1 0 2 38 16 100 80 80 0.00000 28.40195 1.68760e-002 60 105 -0.00 15.00 -0.00 -15.80 0.00 -58.97 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 40 1 1 1 0 2 39 16 100 80 80 0.00000 28.40195 1.68760e-002 60 103 -0.00 15.00 -0.00 -16.66 0.00 -62.16 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - -# === END OF DATA DESCRIPTION FILE =============================================== diff --git a/nibabel/tests/data/Phantom_EPI_3mm_sag_15RL_SENSE_11_1.PAR b/nibabel/tests/data/Phantom_EPI_3mm_sag_15RL_SENSE_11_1.PAR deleted file mode 100644 index ddb2e3c810..0000000000 --- a/nibabel/tests/data/Phantom_EPI_3mm_sag_15RL_SENSE_11_1.PAR +++ /dev/null @@ -1,142 +0,0 @@ -# === DATA DESCRIPTION FILE ====================================================== -# -# CAUTION - Investigational device. -# Limited by Federal Law to investigational use. -# -# Dataset name: E:\\Export\\Phantom_EPI_3mm_sag_15RL_SENSE_11_1 -# -# CLINICAL TRYOUT Research image export tool V4.2 -# -# === GENERAL INFORMATION ======================================================== -# -. Patient name : Phantom -. Examination name : Orientation Par Rec -. Protocol name : EPI_3mm_sag_15RL SENSE -. Examination date/time : 2014.08.05 / 11:08:56 -. Series Type : Image MRSERIES -. Acquisition nr : 11 -. Reconstruction nr : 1 -. Scan Duration [sec] : 7.97 -. Max. number of cardiac phases : 1 -. Max. number of echoes : 1 -. Max. number of slices/locations : 40 -. Max. number of dynamics : 1 -. Max. number of mixes : 1 -. Patient position : Head First Supine -. Preparation direction : Anterior-Posterior -. Technique : FEEPI -. Scan resolution (x, y) : 80 78 -. Scan mode : MS -. Repetition time [ms] : 2000.000 -. FOV (ap,fh,rl) [mm] : 240.000 240.000 131.700 -. Water Fat shift [pixels] : 7.231 -. Angulation midslice(ap,fh,rl)[degr]: 0.000 0.000 15.000 -. Off Centre midslice(ap,fh,rl) [mm] : 0.000 0.000 0.000 -. Flow compensation <0=no 1=yes> ? : 0 -. Presaturation <0=no 1=yes> ? : 0 -. Phase encoding velocity [cm/sec] : 0.000000 0.000000 0.000000 -. MTC <0=no 1=yes> ? : 0 -. SPIR <0=no 1=yes> ? : 0 -. EPI factor <0,1=no EPI> : 39 -. Dynamic scan <0=no 1=yes> ? : 1 -. Diffusion <0=no 1=yes> ? : 0 -. Diffusion echo time [ms] : 0.0000 -. Max. number of diffusion values : 1 -. Max. number of gradient orients : 1 -. Number of label types <0=no ASL> : 0 -# -# === PIXEL VALUES ============================================================= -# PV = pixel value in REC file, FP = floating point value, DV = displayed value on console -# RS = rescale slope, RI = rescale intercept, SS = scale slope -# DV = PV * RS + RI FP = DV / (RS * SS) -# -# === IMAGE INFORMATION DEFINITION ============================================= -# The rest of this file contains ONE line per image, this line contains the following information: -# -# slice number (integer) -# echo number (integer) -# dynamic scan number (integer) -# cardiac phase number (integer) -# image_type_mr (integer) -# scanning sequence (integer) -# index in REC file (in images) (integer) -# image pixel size (in bits) (integer) -# scan percentage (integer) -# recon resolution (x y) (2*integer) -# rescale intercept (float) -# rescale slope (float) -# scale slope (float) -# window center (integer) -# window width (integer) -# image angulation (ap,fh,rl in degrees ) (3*float) -# image offcentre (ap,fh,rl in mm ) (3*float) -# slice thickness (in mm ) (float) -# slice gap (in mm ) (float) -# image_display_orientation (integer) -# slice orientation ( TRA/SAG/COR ) (integer) -# fmri_status_indication (integer) -# image_type_ed_es (end diast/end syst) (integer) -# pixel spacing (x,y) (in mm) (2*float) -# echo_time (float) -# dyn_scan_begin_time (float) -# trigger_time (float) -# diffusion_b_factor (float) -# number of averages (integer) -# image_flip_angle (in degrees) (float) -# cardiac frequency (bpm) (integer) -# minimum RR-interval (in ms) (integer) -# maximum RR-interval (in ms) (integer) -# TURBO factor <0=no turbo> (integer) -# Inversion delay (in ms) (float) -# diffusion b value number (imagekey!) (integer) -# gradient orientation number (imagekey!) (integer) -# contrast type (string) -# diffusion anisotropy type (string) -# diffusion (ap, fh, rl) (3*float) -# label type (ASL) (imagekey!) (integer) -# -# === IMAGE INFORMATION ========================================================== -# sl ec dyn ph ty idx pix scan% rec size (re)scale window angulation offcentre thick gap info spacing echo dtime ttime diff avg flip freq RR-int turbo delay b grad cont anis diffusion L.ty - - 1 1 1 1 0 2 0 16 100 80 80 0.00000 190.08913 1.53218e-002 1070 1860 0.00 -0.00 15.00 0.00 0.00 64.35 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 2 1 1 1 0 2 1 16 100 80 80 0.00000 190.08913 1.53218e-002 11254 19563 0.00 -0.00 15.00 0.00 0.00 61.05 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 3 1 1 1 0 2 2 16 100 80 80 0.00000 190.08913 1.53218e-002 21166 36794 0.00 -0.00 15.00 0.00 0.00 57.75 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 4 1 1 1 0 2 3 16 100 80 80 0.00000 190.08913 1.53218e-002 14559 25308 0.00 -0.00 15.00 0.00 0.00 54.45 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 5 1 1 1 0 2 4 16 100 80 80 0.00000 190.08913 1.53218e-002 1416 2462 0.00 -0.00 15.00 0.00 0.00 51.15 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 6 1 1 1 0 2 5 16 100 80 80 0.00000 190.08913 1.53218e-002 825 1434 0.00 -0.00 15.00 0.00 0.00 47.85 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 7 1 1 1 0 2 6 16 100 80 80 0.00000 190.08913 1.53218e-002 106199 184608 0.00 -0.00 15.00 0.00 0.00 44.55 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 8 1 1 1 0 2 7 16 100 80 80 0.00000 190.08913 1.53218e-002 129086 224393 0.00 -0.00 15.00 0.00 0.00 41.25 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 9 1 1 1 0 2 8 16 100 80 80 0.00000 190.08913 1.53218e-002 137100 238323 0.00 -0.00 15.00 0.00 0.00 37.95 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 10 1 1 1 0 2 9 16 100 80 80 0.00000 190.08913 1.53218e-002 142328 247411 0.00 -0.00 15.00 0.00 0.00 34.65 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 11 1 1 1 0 2 10 16 100 80 80 0.00000 190.08913 1.53218e-002 147227 255928 0.00 -0.00 15.00 0.00 0.00 31.35 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 12 1 1 1 0 2 11 16 100 80 80 0.00000 190.08913 1.53218e-002 146808 255200 0.00 -0.00 15.00 0.00 0.00 28.05 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 13 1 1 1 0 2 12 16 100 80 80 0.00000 190.08913 1.53218e-002 150628 261839 0.00 -0.00 15.00 0.00 0.00 24.75 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 14 1 1 1 0 2 13 16 100 80 80 0.00000 190.08913 1.53218e-002 150985 262460 0.00 -0.00 15.00 0.00 0.00 21.45 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 15 1 1 1 0 2 14 16 100 80 80 0.00000 190.08913 1.53218e-002 156068 271295 0.00 -0.00 15.00 0.00 0.00 18.15 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 16 1 1 1 0 2 15 16 100 80 80 0.00000 190.08913 1.53218e-002 156834 272627 0.00 -0.00 15.00 0.00 0.00 14.85 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 17 1 1 1 0 2 16 16 100 80 80 0.00000 190.08913 1.53218e-002 159234 276799 0.00 -0.00 15.00 0.00 0.00 11.55 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 18 1 1 1 0 2 17 16 100 80 80 0.00000 190.08913 1.53218e-002 159678 277571 0.00 -0.00 15.00 0.00 0.00 8.25 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 19 1 1 1 0 2 18 16 100 80 80 0.00000 190.08913 1.53218e-002 159347 276995 0.00 -0.00 15.00 0.00 0.00 4.95 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 20 1 1 1 0 2 19 16 100 80 80 0.00000 190.08913 1.53218e-002 162381 282270 0.00 -0.00 15.00 0.00 0.00 1.65 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 21 1 1 1 0 2 20 16 100 80 80 0.00000 190.08913 1.53218e-002 162772 282949 0.00 -0.00 15.00 0.00 0.00 -1.65 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 22 1 1 1 0 2 21 16 100 80 80 0.00000 190.08913 1.53218e-002 163668 284506 0.00 -0.00 15.00 0.00 0.00 -4.95 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 23 1 1 1 0 2 22 16 100 80 80 0.00000 190.08913 1.53218e-002 164379 285743 0.00 -0.00 15.00 0.00 0.00 -8.25 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 24 1 1 1 0 2 23 16 100 80 80 0.00000 190.08913 1.53218e-002 162898 283169 0.00 -0.00 15.00 0.00 0.00 -11.55 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 25 1 1 1 0 2 24 16 100 80 80 0.00000 190.08913 1.53218e-002 161117 280073 0.00 -0.00 15.00 0.00 0.00 -14.85 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 26 1 1 1 0 2 25 16 100 80 80 0.00000 190.08913 1.53218e-002 162716 282851 0.00 -0.00 15.00 0.00 0.00 -18.15 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 27 1 1 1 0 2 26 16 100 80 80 0.00000 190.08913 1.53218e-002 162306 282140 0.00 -0.00 15.00 0.00 0.00 -21.45 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 28 1 1 1 0 2 27 16 100 80 80 0.00000 190.08913 1.53218e-002 158850 276131 0.00 -0.00 15.00 0.00 0.00 -24.75 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 29 1 1 1 0 2 28 16 100 80 80 0.00000 190.08913 1.53218e-002 156531 272101 0.00 -0.00 15.00 0.00 0.00 -28.05 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 30 1 1 1 0 2 29 16 100 80 80 0.00000 190.08913 1.53218e-002 155780 270795 0.00 -0.00 15.00 0.00 0.00 -31.35 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 31 1 1 1 0 2 30 16 100 80 80 0.00000 190.08913 1.53218e-002 151891 264035 0.00 -0.00 15.00 0.00 0.00 -34.65 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 32 1 1 1 0 2 31 16 100 80 80 0.00000 190.08913 1.53218e-002 150097 260916 0.00 -0.00 15.00 0.00 0.00 -37.95 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 33 1 1 1 0 2 32 16 100 80 80 0.00000 190.08913 1.53218e-002 145959 253724 0.00 -0.00 15.00 0.00 0.00 -41.25 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 34 1 1 1 0 2 33 16 100 80 80 0.00000 190.08913 1.53218e-002 141854 246588 0.00 -0.00 15.00 0.00 0.00 -44.55 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 35 1 1 1 0 2 34 16 100 80 80 0.00000 190.08913 1.53218e-002 139246 242053 0.00 -0.00 15.00 0.00 0.00 -47.85 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 36 1 1 1 0 2 35 16 100 80 80 0.00000 190.08913 1.53218e-002 139005 241636 0.00 -0.00 15.00 0.00 0.00 -51.15 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 37 1 1 1 0 2 36 16 100 80 80 0.00000 190.08913 1.53218e-002 55362 96236 0.00 -0.00 15.00 0.00 0.00 -54.45 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 38 1 1 1 0 2 37 16 100 80 80 0.00000 190.08913 1.53218e-002 764 1327 0.00 -0.00 15.00 0.00 0.00 -57.75 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 39 1 1 1 0 2 38 16 100 80 80 0.00000 190.08913 1.53218e-002 565 982 0.00 -0.00 15.00 0.00 0.00 -61.05 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 40 1 1 1 0 2 39 16 100 80 80 0.00000 190.08913 1.53218e-002 543 944 0.00 -0.00 15.00 0.00 0.00 -64.35 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - -# === END OF DATA DESCRIPTION FILE =============================================== diff --git a/nibabel/tests/data/Phantom_EPI_3mm_sag_SENSE_7_1.PAR b/nibabel/tests/data/Phantom_EPI_3mm_sag_SENSE_7_1.PAR deleted file mode 100644 index 56036b6db7..0000000000 --- a/nibabel/tests/data/Phantom_EPI_3mm_sag_SENSE_7_1.PAR +++ /dev/null @@ -1,142 +0,0 @@ -# === DATA DESCRIPTION FILE ====================================================== -# -# CAUTION - Investigational device. -# Limited by Federal Law to investigational use. -# -# Dataset name: E:\\Export\\Phantom_EPI_3mm_sag_SENSE_7_1 -# -# CLINICAL TRYOUT Research image export tool V4.2 -# -# === GENERAL INFORMATION ======================================================== -# -. Patient name : Phantom -. Examination name : Orientation Par Rec -. Protocol name : EPI_3mm_sag SENSE -. Examination date/time : 2014.08.05 / 11:08:56 -. Series Type : Image MRSERIES -. Acquisition nr : 7 -. Reconstruction nr : 1 -. Scan Duration [sec] : 7.97 -. Max. number of cardiac phases : 1 -. Max. number of echoes : 1 -. Max. number of slices/locations : 40 -. Max. number of dynamics : 1 -. Max. number of mixes : 1 -. Patient position : Head First Supine -. Preparation direction : Anterior-Posterior -. Technique : FEEPI -. Scan resolution (x, y) : 80 78 -. Scan mode : MS -. Repetition time [ms] : 2000.000 -. FOV (ap,fh,rl) [mm] : 240.000 240.000 131.700 -. Water Fat shift [pixels] : 8.263 -. Angulation midslice(ap,fh,rl)[degr]: 0.000 0.000 0.000 -. Off Centre midslice(ap,fh,rl) [mm] : 0.000 0.000 0.000 -. Flow compensation <0=no 1=yes> ? : 0 -. Presaturation <0=no 1=yes> ? : 0 -. Phase encoding velocity [cm/sec] : 0.000000 0.000000 0.000000 -. MTC <0=no 1=yes> ? : 0 -. SPIR <0=no 1=yes> ? : 0 -. EPI factor <0,1=no EPI> : 39 -. Dynamic scan <0=no 1=yes> ? : 1 -. Diffusion <0=no 1=yes> ? : 0 -. Diffusion echo time [ms] : 0.0000 -. Max. number of diffusion values : 1 -. Max. number of gradient orients : 1 -. Number of label types <0=no ASL> : 0 -# -# === PIXEL VALUES ============================================================= -# PV = pixel value in REC file, FP = floating point value, DV = displayed value on console -# RS = rescale slope, RI = rescale intercept, SS = scale slope -# DV = PV * RS + RI FP = DV / (RS * SS) -# -# === IMAGE INFORMATION DEFINITION ============================================= -# The rest of this file contains ONE line per image, this line contains the following information: -# -# slice number (integer) -# echo number (integer) -# dynamic scan number (integer) -# cardiac phase number (integer) -# image_type_mr (integer) -# scanning sequence (integer) -# index in REC file (in images) (integer) -# image pixel size (in bits) (integer) -# scan percentage (integer) -# recon resolution (x y) (2*integer) -# rescale intercept (float) -# rescale slope (float) -# scale slope (float) -# window center (integer) -# window width (integer) -# image angulation (ap,fh,rl in degrees ) (3*float) -# image offcentre (ap,fh,rl in mm ) (3*float) -# slice thickness (in mm ) (float) -# slice gap (in mm ) (float) -# image_display_orientation (integer) -# slice orientation ( TRA/SAG/COR ) (integer) -# fmri_status_indication (integer) -# image_type_ed_es (end diast/end syst) (integer) -# pixel spacing (x,y) (in mm) (2*float) -# echo_time (float) -# dyn_scan_begin_time (float) -# trigger_time (float) -# diffusion_b_factor (float) -# number of averages (integer) -# image_flip_angle (in degrees) (float) -# cardiac frequency (bpm) (integer) -# minimum RR-interval (in ms) (integer) -# maximum RR-interval (in ms) (integer) -# TURBO factor <0=no turbo> (integer) -# Inversion delay (in ms) (float) -# diffusion b value number (imagekey!) (integer) -# gradient orientation number (imagekey!) (integer) -# contrast type (string) -# diffusion anisotropy type (string) -# diffusion (ap, fh, rl) (3*float) -# label type (ASL) (imagekey!) (integer) -# -# === IMAGE INFORMATION ========================================================== -# sl ec dyn ph ty idx pix scan% rec size (re)scale window angulation offcentre thick gap info spacing echo dtime ttime diff avg flip freq RR-int turbo delay b grad cont anis diffusion L.ty - - 1 1 1 1 0 2 0 16 100 80 80 0.00000 389.46399 1.72142e-002 1070 1860 0.00 -0.00 -0.00 0.00 0.00 64.35 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 2 1 1 1 0 2 1 16 100 80 80 0.00000 389.46399 1.72142e-002 15486 26920 0.00 -0.00 -0.00 0.00 0.00 61.05 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 3 1 1 1 0 2 2 16 100 80 80 0.00000 389.46399 1.72142e-002 23986 41695 0.00 -0.00 -0.00 0.00 0.00 57.75 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 4 1 1 1 0 2 3 16 100 80 80 0.00000 389.46399 1.72142e-002 24360 42345 0.00 -0.00 -0.00 0.00 0.00 54.45 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 5 1 1 1 0 2 4 16 100 80 80 0.00000 389.46399 1.72142e-002 2852 4957 0.00 -0.00 -0.00 0.00 0.00 51.15 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 6 1 1 1 0 2 5 16 100 80 80 0.00000 389.46399 1.72142e-002 2060 3582 0.00 -0.00 -0.00 0.00 0.00 47.85 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 7 1 1 1 0 2 6 16 100 80 80 0.00000 389.46399 1.72142e-002 246519 428528 0.00 -0.00 -0.00 0.00 0.00 44.55 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 8 1 1 1 0 2 7 16 100 80 80 0.00000 389.46399 1.72142e-002 296644 515662 0.00 -0.00 -0.00 0.00 0.00 41.25 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 9 1 1 1 0 2 8 16 100 80 80 0.00000 389.46399 1.72142e-002 307875 535185 0.00 -0.00 -0.00 0.00 0.00 37.95 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 10 1 1 1 0 2 9 16 100 80 80 0.00000 389.46399 1.72142e-002 319215 554897 0.00 -0.00 -0.00 0.00 0.00 34.65 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 11 1 1 1 0 2 10 16 100 80 80 0.00000 389.46399 1.72142e-002 330350 574254 0.00 -0.00 -0.00 0.00 0.00 31.35 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 12 1 1 1 0 2 11 16 100 80 80 0.00000 389.46399 1.72142e-002 332836 578575 0.00 -0.00 -0.00 0.00 0.00 28.05 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 13 1 1 1 0 2 12 16 100 80 80 0.00000 389.46399 1.72142e-002 339357 589911 0.00 -0.00 -0.00 0.00 0.00 24.75 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 14 1 1 1 0 2 13 16 100 80 80 0.00000 389.46399 1.72142e-002 345875 601241 0.00 -0.00 -0.00 0.00 0.00 21.45 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 15 1 1 1 0 2 14 16 100 80 80 0.00000 389.46399 1.72142e-002 353058 613726 0.00 -0.00 -0.00 0.00 0.00 18.15 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 16 1 1 1 0 2 15 16 100 80 80 0.00000 389.46399 1.72142e-002 353894 615180 0.00 -0.00 -0.00 0.00 0.00 14.85 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 17 1 1 1 0 2 16 16 100 80 80 0.00000 389.46399 1.72142e-002 369615 642508 0.00 -0.00 -0.00 0.00 0.00 11.55 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 18 1 1 1 0 2 17 16 100 80 80 0.00000 389.46399 1.72142e-002 370847 644649 0.00 -0.00 -0.00 0.00 0.00 8.25 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 19 1 1 1 0 2 18 16 100 80 80 0.00000 389.46399 1.72142e-002 376545 654554 0.00 -0.00 -0.00 0.00 0.00 4.95 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 20 1 1 1 0 2 19 16 100 80 80 0.00000 389.46399 1.72142e-002 373086 648543 0.00 -0.00 -0.00 0.00 0.00 1.65 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 21 1 1 1 0 2 20 16 100 80 80 0.00000 389.46399 1.72142e-002 374986 651845 0.00 -0.00 -0.00 0.00 0.00 -1.65 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 22 1 1 1 0 2 21 16 100 80 80 0.00000 389.46399 1.72142e-002 379234 659230 0.00 -0.00 -0.00 0.00 0.00 -4.95 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 23 1 1 1 0 2 22 16 100 80 80 0.00000 389.46399 1.72142e-002 376914 655197 0.00 -0.00 -0.00 0.00 0.00 -8.25 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 24 1 1 1 0 2 23 16 100 80 80 0.00000 389.46399 1.72142e-002 381735 663577 0.00 -0.00 -0.00 0.00 0.00 -11.55 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 25 1 1 1 0 2 24 16 100 80 80 0.00000 389.46399 1.72142e-002 377424 656083 0.00 -0.00 -0.00 0.00 0.00 -14.85 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 26 1 1 1 0 2 25 16 100 80 80 0.00000 389.46399 1.72142e-002 379368 659463 0.00 -0.00 -0.00 0.00 0.00 -18.15 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 27 1 1 1 0 2 26 16 100 80 80 0.00000 389.46399 1.72142e-002 376246 654035 0.00 -0.00 -0.00 0.00 0.00 -21.45 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 28 1 1 1 0 2 27 16 100 80 80 0.00000 389.46399 1.72142e-002 358461 623119 0.00 -0.00 -0.00 0.00 0.00 -24.75 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 29 1 1 1 0 2 28 16 100 80 80 0.00000 389.46399 1.72142e-002 351059 610253 0.00 -0.00 -0.00 0.00 0.00 -28.05 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 30 1 1 1 0 2 29 16 100 80 80 0.00000 389.46399 1.72142e-002 346599 602500 0.00 -0.00 -0.00 0.00 0.00 -31.35 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 31 1 1 1 0 2 30 16 100 80 80 0.00000 389.46399 1.72142e-002 340911 592611 0.00 -0.00 -0.00 0.00 0.00 -34.65 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 32 1 1 1 0 2 31 16 100 80 80 0.00000 389.46399 1.72142e-002 331507 576265 0.00 -0.00 -0.00 0.00 0.00 -37.95 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 33 1 1 1 0 2 32 16 100 80 80 0.00000 389.46399 1.72142e-002 321620 559078 0.00 -0.00 -0.00 0.00 0.00 -41.25 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 34 1 1 1 0 2 33 16 100 80 80 0.00000 389.46399 1.72142e-002 326147 566946 0.00 -0.00 -0.00 0.00 0.00 -44.55 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 35 1 1 1 0 2 34 16 100 80 80 0.00000 389.46399 1.72142e-002 320928 557874 0.00 -0.00 -0.00 0.00 0.00 -47.85 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 36 1 1 1 0 2 35 16 100 80 80 0.00000 389.46399 1.72142e-002 312475 543180 0.00 -0.00 -0.00 0.00 0.00 -51.15 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 37 1 1 1 0 2 36 16 100 80 80 0.00000 389.46399 1.72142e-002 122043 212150 0.00 -0.00 -0.00 0.00 0.00 -54.45 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 38 1 1 1 0 2 37 16 100 80 80 0.00000 389.46399 1.72142e-002 2087 3629 0.00 -0.00 -0.00 0.00 0.00 -57.75 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 39 1 1 1 0 2 38 16 100 80 80 0.00000 389.46399 1.72142e-002 1288 2239 0.00 -0.00 -0.00 0.00 0.00 -61.05 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 40 1 1 1 0 2 39 16 100 80 80 0.00000 389.46399 1.72142e-002 851 1479 0.00 -0.00 -0.00 0.00 0.00 -64.35 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - -# === END OF DATA DESCRIPTION FILE =============================================== diff --git a/nibabel/tests/data/Phantom_EPI_3mm_tra_-30AP_10RL_20FH_SENSE_14_1.PAR b/nibabel/tests/data/Phantom_EPI_3mm_tra_-30AP_10RL_20FH_SENSE_14_1.PAR deleted file mode 100644 index ef5e019994..0000000000 --- a/nibabel/tests/data/Phantom_EPI_3mm_tra_-30AP_10RL_20FH_SENSE_14_1.PAR +++ /dev/null @@ -1,142 +0,0 @@ -# === DATA DESCRIPTION FILE ====================================================== -# -# CAUTION - Investigational device. -# Limited by Federal Law to investigational use. -# -# Dataset name: E:\\Export\\Phantom_EPI_3mm_tra_-30AP_10RL_20FH_SENSE_14_1 -# -# CLINICAL TRYOUT Research image export tool V4.2 -# -# === GENERAL INFORMATION ======================================================== -# -. Patient name : Phantom -. Examination name : Orientation Par Rec -. Protocol name : EPI_3mm_tra_-30AP_10RL_20FH SENSE -. Examination date/time : 2014.08.05 / 11:08:56 -. Series Type : Image MRSERIES -. Acquisition nr : 14 -. Reconstruction nr : 1 -. Scan Duration [sec] : 7.97 -. Max. number of cardiac phases : 1 -. Max. number of echoes : 1 -. Max. number of slices/locations : 40 -. Max. number of dynamics : 1 -. Max. number of mixes : 1 -. Patient position : Head First Supine -. Preparation direction : Anterior-Posterior -. Technique : FEEPI -. Scan resolution (x, y) : 80 78 -. Scan mode : MS -. Repetition time [ms] : 2000.000 -. FOV (ap,fh,rl) [mm] : 240.000 240.000 131.700 -. Water Fat shift [pixels] : 8.263 -. Angulation midslice(ap,fh,rl)[degr]: 0.000 0.000 0.000 -. Off Centre midslice(ap,fh,rl) [mm] : -30.000 20.000 10.000 -. Flow compensation <0=no 1=yes> ? : 0 -. Presaturation <0=no 1=yes> ? : 0 -. Phase encoding velocity [cm/sec] : 0.000000 0.000000 0.000000 -. MTC <0=no 1=yes> ? : 0 -. SPIR <0=no 1=yes> ? : 0 -. EPI factor <0,1=no EPI> : 39 -. Dynamic scan <0=no 1=yes> ? : 1 -. Diffusion <0=no 1=yes> ? : 0 -. Diffusion echo time [ms] : 0.0000 -. Max. number of diffusion values : 1 -. Max. number of gradient orients : 1 -. Number of label types <0=no ASL> : 0 -# -# === PIXEL VALUES ============================================================= -# PV = pixel value in REC file, FP = floating point value, DV = displayed value on console -# RS = rescale slope, RI = rescale intercept, SS = scale slope -# DV = PV * RS + RI FP = DV / (RS * SS) -# -# === IMAGE INFORMATION DEFINITION ============================================= -# The rest of this file contains ONE line per image, this line contains the following information: -# -# slice number (integer) -# echo number (integer) -# dynamic scan number (integer) -# cardiac phase number (integer) -# image_type_mr (integer) -# scanning sequence (integer) -# index in REC file (in images) (integer) -# image pixel size (in bits) (integer) -# scan percentage (integer) -# recon resolution (x y) (2*integer) -# rescale intercept (float) -# rescale slope (float) -# scale slope (float) -# window center (integer) -# window width (integer) -# image angulation (ap,fh,rl in degrees ) (3*float) -# image offcentre (ap,fh,rl in mm ) (3*float) -# slice thickness (in mm ) (float) -# slice gap (in mm ) (float) -# image_display_orientation (integer) -# slice orientation ( TRA/SAG/COR ) (integer) -# fmri_status_indication (integer) -# image_type_ed_es (end diast/end syst) (integer) -# pixel spacing (x,y) (in mm) (2*float) -# echo_time (float) -# dyn_scan_begin_time (float) -# trigger_time (float) -# diffusion_b_factor (float) -# number of averages (integer) -# image_flip_angle (in degrees) (float) -# cardiac frequency (bpm) (integer) -# minimum RR-interval (in ms) (integer) -# maximum RR-interval (in ms) (integer) -# TURBO factor <0=no turbo> (integer) -# Inversion delay (in ms) (float) -# diffusion b value number (imagekey!) (integer) -# gradient orientation number (imagekey!) (integer) -# contrast type (string) -# diffusion anisotropy type (string) -# diffusion (ap, fh, rl) (3*float) -# label type (ASL) (imagekey!) (integer) -# -# === IMAGE INFORMATION ========================================================== -# sl ec dyn ph ty idx pix scan% rec size (re)scale window angulation offcentre thick gap info spacing echo dtime ttime diff avg flip freq RR-int turbo delay b grad cont anis diffusion L.ty - - 1 1 1 1 0 2 0 16 100 80 80 0.00000 356.82980 1.72142e-002 1070 1860 0.00 -0.00 -0.00 -30.00 20.00 74.35 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 2 1 1 1 0 2 1 16 100 80 80 0.00000 356.82980 1.72142e-002 983 1709 0.00 -0.00 -0.00 -30.00 20.00 71.05 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 3 1 1 1 0 2 2 16 100 80 80 0.00000 356.82980 1.72142e-002 1013 1761 0.00 -0.00 -0.00 -30.00 20.00 67.75 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 4 1 1 1 0 2 3 16 100 80 80 0.00000 356.82980 1.72142e-002 2178 3786 0.00 -0.00 -0.00 -30.00 20.00 64.45 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 5 1 1 1 0 2 4 16 100 80 80 0.00000 356.82980 1.72142e-002 27930 48551 0.00 -0.00 -0.00 -30.00 20.00 61.15 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 6 1 1 1 0 2 5 16 100 80 80 0.00000 356.82980 1.72142e-002 37558 65287 0.00 -0.00 -0.00 -30.00 20.00 57.85 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 7 1 1 1 0 2 6 16 100 80 80 0.00000 356.82980 1.72142e-002 29943 52051 0.00 -0.00 -0.00 -30.00 20.00 54.55 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 8 1 1 1 0 2 7 16 100 80 80 0.00000 356.82980 1.72142e-002 2898 5038 0.00 -0.00 -0.00 -30.00 20.00 51.25 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 9 1 1 1 0 2 8 16 100 80 80 0.00000 356.82980 1.72142e-002 1614 2806 0.00 -0.00 -0.00 -30.00 20.00 47.95 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 10 1 1 1 0 2 9 16 100 80 80 0.00000 356.82980 1.72142e-002 218425 379693 0.00 -0.00 -0.00 -30.00 20.00 44.65 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 11 1 1 1 0 2 10 16 100 80 80 0.00000 356.82980 1.72142e-002 276174 480078 0.00 -0.00 -0.00 -30.00 20.00 41.35 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 12 1 1 1 0 2 11 16 100 80 80 0.00000 356.82980 1.72142e-002 289982 504081 0.00 -0.00 -0.00 -30.00 20.00 38.05 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 13 1 1 1 0 2 12 16 100 80 80 0.00000 356.82980 1.72142e-002 293458 510123 0.00 -0.00 -0.00 -30.00 20.00 34.75 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 14 1 1 1 0 2 13 16 100 80 80 0.00000 356.82980 1.72142e-002 302048 525055 0.00 -0.00 -0.00 -30.00 20.00 31.45 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 15 1 1 1 0 2 14 16 100 80 80 0.00000 356.82980 1.72142e-002 308831 536846 0.00 -0.00 -0.00 -30.00 20.00 28.15 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 16 1 1 1 0 2 15 16 100 80 80 0.00000 356.82980 1.72142e-002 315409 548281 0.00 -0.00 -0.00 -30.00 20.00 24.85 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 17 1 1 1 0 2 16 16 100 80 80 0.00000 356.82980 1.72142e-002 321972 559690 0.00 -0.00 -0.00 -30.00 20.00 21.55 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 18 1 1 1 0 2 17 16 100 80 80 0.00000 356.82980 1.72142e-002 321161 558279 0.00 -0.00 -0.00 -30.00 20.00 18.25 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 19 1 1 1 0 2 18 16 100 80 80 0.00000 356.82980 1.72142e-002 325846 566424 0.00 -0.00 -0.00 -30.00 20.00 14.95 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 20 1 1 1 0 2 19 16 100 80 80 0.00000 356.82980 1.72142e-002 341428 593510 0.00 -0.00 -0.00 -30.00 20.00 11.65 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 21 1 1 1 0 2 20 16 100 80 80 0.00000 356.82980 1.72142e-002 345281 600208 0.00 -0.00 -0.00 -30.00 20.00 8.35 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 22 1 1 1 0 2 21 16 100 80 80 0.00000 356.82980 1.72142e-002 343689 597440 0.00 -0.00 -0.00 -30.00 20.00 5.05 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 23 1 1 1 0 2 22 16 100 80 80 0.00000 356.82980 1.72142e-002 339976 590986 0.00 -0.00 -0.00 -30.00 20.00 1.75 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 24 1 1 1 0 2 23 16 100 80 80 0.00000 356.82980 1.72142e-002 340348 591633 0.00 -0.00 -0.00 -30.00 20.00 -1.55 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 25 1 1 1 0 2 24 16 100 80 80 0.00000 356.82980 1.72142e-002 341236 593177 0.00 -0.00 -0.00 -30.00 20.00 -4.85 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 26 1 1 1 0 2 25 16 100 80 80 0.00000 356.82980 1.72142e-002 343686 597435 0.00 -0.00 -0.00 -30.00 20.00 -8.15 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 27 1 1 1 0 2 26 16 100 80 80 0.00000 356.82980 1.72142e-002 348404 605637 0.00 -0.00 -0.00 -30.00 20.00 -11.45 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 28 1 1 1 0 2 27 16 100 80 80 0.00000 356.82980 1.72142e-002 348723 606191 0.00 -0.00 -0.00 -30.00 20.00 -14.75 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 29 1 1 1 0 2 28 16 100 80 80 0.00000 356.82980 1.72142e-002 352612 612951 0.00 -0.00 -0.00 -30.00 20.00 -18.05 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 30 1 1 1 0 2 29 16 100 80 80 0.00000 356.82980 1.72142e-002 345832 601166 0.00 -0.00 -0.00 -30.00 20.00 -21.35 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 31 1 1 1 0 2 30 16 100 80 80 0.00000 356.82980 1.72142e-002 332450 577903 0.00 -0.00 -0.00 -30.00 20.00 -24.65 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 32 1 1 1 0 2 31 16 100 80 80 0.00000 356.82980 1.72142e-002 325654 566090 0.00 -0.00 -0.00 -30.00 20.00 -27.95 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 33 1 1 1 0 2 32 16 100 80 80 0.00000 356.82980 1.72142e-002 312036 542418 0.00 -0.00 -0.00 -30.00 20.00 -31.25 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 34 1 1 1 0 2 33 16 100 80 80 0.00000 356.82980 1.72142e-002 312355 542973 0.00 -0.00 -0.00 -30.00 20.00 -34.55 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 35 1 1 1 0 2 34 16 100 80 80 0.00000 356.82980 1.72142e-002 308664 536556 0.00 -0.00 -0.00 -30.00 20.00 -37.85 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 36 1 1 1 0 2 35 16 100 80 80 0.00000 356.82980 1.72142e-002 301103 523412 0.00 -0.00 -0.00 -30.00 20.00 -41.15 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 37 1 1 1 0 2 36 16 100 80 80 0.00000 356.82980 1.72142e-002 287897 500457 0.00 -0.00 -0.00 -30.00 20.00 -44.45 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 38 1 1 1 0 2 37 16 100 80 80 0.00000 356.82980 1.72142e-002 287281 499386 0.00 -0.00 -0.00 -30.00 20.00 -47.75 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 39 1 1 1 0 2 38 16 100 80 80 0.00000 356.82980 1.72142e-002 290866 505618 0.00 -0.00 -0.00 -30.00 20.00 -51.05 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 40 1 1 1 0 2 39 16 100 80 80 0.00000 356.82980 1.72142e-002 127876 222289 0.00 -0.00 -0.00 -30.00 20.00 -54.35 3.000 0.300 0 2 0 2 3.000 3.000 10.82 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - -# === END OF DATA DESCRIPTION FILE =============================================== diff --git a/nibabel/tests/data/Phantom_EPI_3mm_tra_15FH_SENSE_9_1.PAR b/nibabel/tests/data/Phantom_EPI_3mm_tra_15FH_SENSE_9_1.PAR deleted file mode 100644 index 645ef87992..0000000000 --- a/nibabel/tests/data/Phantom_EPI_3mm_tra_15FH_SENSE_9_1.PAR +++ /dev/null @@ -1,142 +0,0 @@ -# === DATA DESCRIPTION FILE ====================================================== -# -# CAUTION - Investigational device. -# Limited by Federal Law to investigational use. -# -# Dataset name: E:\\Export\\Phantom_EPI_3mm_tra_15FH_SENSE_9_1 -# -# CLINICAL TRYOUT Research image export tool V4.2 -# -# === GENERAL INFORMATION ======================================================== -# -. Patient name : Phantom -. Examination name : Orientation Par Rec -. Protocol name : EPI_3mm_tra_15FH SENSE -. Examination date/time : 2014.08.05 / 11:08:56 -. Series Type : Image MRSERIES -. Acquisition nr : 9 -. Reconstruction nr : 1 -. Scan Duration [sec] : 7.97 -. Max. number of cardiac phases : 1 -. Max. number of echoes : 1 -. Max. number of slices/locations : 40 -. Max. number of dynamics : 1 -. Max. number of mixes : 1 -. Patient position : Head First Supine -. Preparation direction : Anterior-Posterior -. Technique : FEEPI -. Scan resolution (x, y) : 80 78 -. Scan mode : MS -. Repetition time [ms] : 2000.000 -. FOV (ap,fh,rl) [mm] : 240.000 240.000 131.700 -. Water Fat shift [pixels] : 8.121 -. Angulation midslice(ap,fh,rl)[degr]: 0.000 15.000 0.000 -. Off Centre midslice(ap,fh,rl) [mm] : 0.000 0.000 0.000 -. Flow compensation <0=no 1=yes> ? : 0 -. Presaturation <0=no 1=yes> ? : 0 -. Phase encoding velocity [cm/sec] : 0.000000 0.000000 0.000000 -. MTC <0=no 1=yes> ? : 0 -. SPIR <0=no 1=yes> ? : 0 -. EPI factor <0,1=no EPI> : 39 -. Dynamic scan <0=no 1=yes> ? : 1 -. Diffusion <0=no 1=yes> ? : 0 -. Diffusion echo time [ms] : 0.0000 -. Max. number of diffusion values : 1 -. Max. number of gradient orients : 1 -. Number of label types <0=no ASL> : 0 -# -# === PIXEL VALUES ============================================================= -# PV = pixel value in REC file, FP = floating point value, DV = displayed value on console -# RS = rescale slope, RI = rescale intercept, SS = scale slope -# DV = PV * RS + RI FP = DV / (RS * SS) -# -# === IMAGE INFORMATION DEFINITION ============================================= -# The rest of this file contains ONE line per image, this line contains the following information: -# -# slice number (integer) -# echo number (integer) -# dynamic scan number (integer) -# cardiac phase number (integer) -# image_type_mr (integer) -# scanning sequence (integer) -# index in REC file (in images) (integer) -# image pixel size (in bits) (integer) -# scan percentage (integer) -# recon resolution (x y) (2*integer) -# rescale intercept (float) -# rescale slope (float) -# scale slope (float) -# window center (integer) -# window width (integer) -# image angulation (ap,fh,rl in degrees ) (3*float) -# image offcentre (ap,fh,rl in mm ) (3*float) -# slice thickness (in mm ) (float) -# slice gap (in mm ) (float) -# image_display_orientation (integer) -# slice orientation ( TRA/SAG/COR ) (integer) -# fmri_status_indication (integer) -# image_type_ed_es (end diast/end syst) (integer) -# pixel spacing (x,y) (in mm) (2*float) -# echo_time (float) -# dyn_scan_begin_time (float) -# trigger_time (float) -# diffusion_b_factor (float) -# number of averages (integer) -# image_flip_angle (in degrees) (float) -# cardiac frequency (bpm) (integer) -# minimum RR-interval (in ms) (integer) -# maximum RR-interval (in ms) (integer) -# TURBO factor <0=no turbo> (integer) -# Inversion delay (in ms) (float) -# diffusion b value number (imagekey!) (integer) -# gradient orientation number (imagekey!) (integer) -# contrast type (string) -# diffusion anisotropy type (string) -# diffusion (ap, fh, rl) (3*float) -# label type (ASL) (imagekey!) (integer) -# -# === IMAGE INFORMATION ========================================================== -# sl ec dyn ph ty idx pix scan% rec size (re)scale window angulation offcentre thick gap info spacing echo dtime ttime diff avg flip freq RR-int turbo delay b grad cont anis diffusion L.ty - - 1 1 1 1 0 2 0 16 100 80 80 0.00000 29.22393 1.68760e-002 1070 1860 -0.00 15.00 -0.00 16.66 0.00 62.16 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 2 1 1 1 0 2 1 16 100 80 80 0.00000 29.22393 1.68760e-002 1869 3249 -0.00 15.00 -0.00 15.80 0.00 58.97 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 3 1 1 1 0 2 2 16 100 80 80 0.00000 29.22393 1.68760e-002 1631 2835 -0.00 15.00 -0.00 14.95 0.00 55.78 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 4 1 1 1 0 2 3 16 100 80 80 0.00000 29.22393 1.68760e-002 3391 5894 -0.00 15.00 -0.00 14.09 0.00 52.59 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 5 1 1 1 0 2 4 16 100 80 80 0.00000 29.22393 1.68760e-002 21060 36608 -0.00 15.00 -0.00 13.24 0.00 49.41 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 6 1 1 1 0 2 5 16 100 80 80 0.00000 29.22393 1.68760e-002 23131 40208 -0.00 15.00 -0.00 12.38 0.00 46.22 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 7 1 1 1 0 2 6 16 100 80 80 0.00000 29.22393 1.68760e-002 24022 41758 -0.00 15.00 -0.00 11.53 0.00 43.03 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 8 1 1 1 0 2 7 16 100 80 80 0.00000 29.22393 1.68760e-002 24552 42679 -0.00 15.00 -0.00 10.68 0.00 39.84 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 9 1 1 1 0 2 8 16 100 80 80 0.00000 29.22393 1.68760e-002 25124 43674 -0.00 15.00 -0.00 9.82 0.00 36.66 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 10 1 1 1 0 2 9 16 100 80 80 0.00000 29.22393 1.68760e-002 26014 45221 -0.00 15.00 -0.00 8.97 0.00 33.47 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 11 1 1 1 0 2 10 16 100 80 80 0.00000 29.22393 1.68760e-002 25987 45173 -0.00 15.00 -0.00 8.11 0.00 30.28 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 12 1 1 1 0 2 11 16 100 80 80 0.00000 29.22393 1.68760e-002 26187 45522 -0.00 15.00 -0.00 7.26 0.00 27.09 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 13 1 1 1 0 2 12 16 100 80 80 0.00000 29.22393 1.68760e-002 26282 45686 -0.00 15.00 -0.00 6.41 0.00 23.91 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 14 1 1 1 0 2 13 16 100 80 80 0.00000 29.22393 1.68760e-002 26297 45713 -0.00 15.00 -0.00 5.55 0.00 20.72 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 15 1 1 1 0 2 14 16 100 80 80 0.00000 29.22393 1.68760e-002 26612 46260 -0.00 15.00 -0.00 4.70 0.00 17.53 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 16 1 1 1 0 2 15 16 100 80 80 0.00000 29.22393 1.68760e-002 26706 46423 -0.00 15.00 -0.00 3.84 0.00 14.34 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 17 1 1 1 0 2 16 16 100 80 80 0.00000 29.22393 1.68760e-002 26655 46335 -0.00 15.00 -0.00 2.99 0.00 11.16 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 18 1 1 1 0 2 17 16 100 80 80 0.00000 29.22393 1.68760e-002 27142 47182 -0.00 15.00 -0.00 2.14 0.00 7.97 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 19 1 1 1 0 2 18 16 100 80 80 0.00000 29.22393 1.68760e-002 26984 46906 -0.00 15.00 -0.00 1.28 0.00 4.78 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 20 1 1 1 0 2 19 16 100 80 80 0.00000 29.22393 1.68760e-002 27278 47417 -0.00 15.00 -0.00 0.43 0.00 1.59 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 21 1 1 1 0 2 20 16 100 80 80 0.00000 29.22393 1.68760e-002 27215 47308 -0.00 15.00 -0.00 -0.43 0.00 -1.59 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 22 1 1 1 0 2 21 16 100 80 80 0.00000 29.22393 1.68760e-002 27429 47680 -0.00 15.00 -0.00 -1.28 0.00 -4.78 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 23 1 1 1 0 2 22 16 100 80 80 0.00000 29.22393 1.68760e-002 26783 46557 -0.00 15.00 -0.00 -2.14 0.00 -7.97 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 24 1 1 1 0 2 23 16 100 80 80 0.00000 29.22393 1.68760e-002 27440 47699 -0.00 15.00 -0.00 -2.99 0.00 -11.16 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 25 1 1 1 0 2 24 16 100 80 80 0.00000 29.22393 1.68760e-002 27745 48230 -0.00 15.00 -0.00 -3.84 0.00 -14.34 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 26 1 1 1 0 2 25 16 100 80 80 0.00000 29.22393 1.68760e-002 27124 47150 -0.00 15.00 -0.00 -4.70 0.00 -17.53 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 27 1 1 1 0 2 26 16 100 80 80 0.00000 29.22393 1.68760e-002 27470 47751 -0.00 15.00 -0.00 -5.55 0.00 -20.72 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 28 1 1 1 0 2 27 16 100 80 80 0.00000 29.22393 1.68760e-002 27779 48289 -0.00 15.00 -0.00 -6.41 0.00 -23.91 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 29 1 1 1 0 2 28 16 100 80 80 0.00000 29.22393 1.68760e-002 25858 44949 -0.00 15.00 -0.00 -7.26 0.00 -27.09 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 30 1 1 1 0 2 29 16 100 80 80 0.00000 29.22393 1.68760e-002 24754 43031 -0.00 15.00 -0.00 -8.11 0.00 -30.28 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 31 1 1 1 0 2 30 16 100 80 80 0.00000 29.22393 1.68760e-002 23793 41361 -0.00 15.00 -0.00 -8.97 0.00 -33.47 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 32 1 1 1 0 2 31 16 100 80 80 0.00000 29.22393 1.68760e-002 23169 40275 -0.00 15.00 -0.00 -9.82 0.00 -36.66 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 33 1 1 1 0 2 32 16 100 80 80 0.00000 29.22393 1.68760e-002 23015 40008 -0.00 15.00 -0.00 -10.68 0.00 -39.84 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 34 1 1 1 0 2 33 16 100 80 80 0.00000 29.22393 1.68760e-002 22656 39383 -0.00 15.00 -0.00 -11.53 0.00 -43.03 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 35 1 1 1 0 2 34 16 100 80 80 0.00000 29.22393 1.68760e-002 2086 3627 -0.00 15.00 -0.00 -12.38 0.00 -46.22 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 36 1 1 1 0 2 35 16 100 80 80 0.00000 29.22393 1.68760e-002 156 271 -0.00 15.00 -0.00 -13.24 0.00 -49.41 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 37 1 1 1 0 2 36 16 100 80 80 0.00000 29.22393 1.68760e-002 69 120 -0.00 15.00 -0.00 -14.09 0.00 -52.59 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 38 1 1 1 0 2 37 16 100 80 80 0.00000 29.22393 1.68760e-002 62 108 -0.00 15.00 -0.00 -14.95 0.00 -55.78 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 39 1 1 1 0 2 38 16 100 80 80 0.00000 29.22393 1.68760e-002 63 110 -0.00 15.00 -0.00 -15.80 0.00 -58.97 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 40 1 1 1 0 2 39 16 100 80 80 0.00000 29.22393 1.68760e-002 66 114 -0.00 15.00 -0.00 -16.66 0.00 -62.16 3.000 0.300 0 2 0 2 3.000 3.000 10.68 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - -# === END OF DATA DESCRIPTION FILE =============================================== diff --git a/nibabel/tests/data/Phantom_EPI_3mm_tra_15RL_SENSE_10_1.PAR b/nibabel/tests/data/Phantom_EPI_3mm_tra_15RL_SENSE_10_1.PAR deleted file mode 100644 index b31c133a04..0000000000 --- a/nibabel/tests/data/Phantom_EPI_3mm_tra_15RL_SENSE_10_1.PAR +++ /dev/null @@ -1,142 +0,0 @@ -# === DATA DESCRIPTION FILE ====================================================== -# -# CAUTION - Investigational device. -# Limited by Federal Law to investigational use. -# -# Dataset name: E:\\Export\\Phantom_EPI_3mm_tra_15RL_SENSE_10_1 -# -# CLINICAL TRYOUT Research image export tool V4.2 -# -# === GENERAL INFORMATION ======================================================== -# -. Patient name : Phantom -. Examination name : Orientation Par Rec -. Protocol name : EPI_3mm_tra_15RL SENSE -. Examination date/time : 2014.08.05 / 11:08:56 -. Series Type : Image MRSERIES -. Acquisition nr : 10 -. Reconstruction nr : 1 -. Scan Duration [sec] : 7.97 -. Max. number of cardiac phases : 1 -. Max. number of echoes : 1 -. Max. number of slices/locations : 40 -. Max. number of dynamics : 1 -. Max. number of mixes : 1 -. Patient position : Head First Supine -. Preparation direction : Anterior-Posterior -. Technique : FEEPI -. Scan resolution (x, y) : 80 78 -. Scan mode : MS -. Repetition time [ms] : 2000.000 -. FOV (ap,fh,rl) [mm] : 240.000 240.000 131.700 -. Water Fat shift [pixels] : 7.231 -. Angulation midslice(ap,fh,rl)[degr]: 0.000 0.000 15.000 -. Off Centre midslice(ap,fh,rl) [mm] : 0.000 0.000 0.000 -. Flow compensation <0=no 1=yes> ? : 0 -. Presaturation <0=no 1=yes> ? : 0 -. Phase encoding velocity [cm/sec] : 0.000000 0.000000 0.000000 -. MTC <0=no 1=yes> ? : 0 -. SPIR <0=no 1=yes> ? : 0 -. EPI factor <0,1=no EPI> : 39 -. Dynamic scan <0=no 1=yes> ? : 1 -. Diffusion <0=no 1=yes> ? : 0 -. Diffusion echo time [ms] : 0.0000 -. Max. number of diffusion values : 1 -. Max. number of gradient orients : 1 -. Number of label types <0=no ASL> : 0 -# -# === PIXEL VALUES ============================================================= -# PV = pixel value in REC file, FP = floating point value, DV = displayed value on console -# RS = rescale slope, RI = rescale intercept, SS = scale slope -# DV = PV * RS + RI FP = DV / (RS * SS) -# -# === IMAGE INFORMATION DEFINITION ============================================= -# The rest of this file contains ONE line per image, this line contains the following information: -# -# slice number (integer) -# echo number (integer) -# dynamic scan number (integer) -# cardiac phase number (integer) -# image_type_mr (integer) -# scanning sequence (integer) -# index in REC file (in images) (integer) -# image pixel size (in bits) (integer) -# scan percentage (integer) -# recon resolution (x y) (2*integer) -# rescale intercept (float) -# rescale slope (float) -# scale slope (float) -# window center (integer) -# window width (integer) -# image angulation (ap,fh,rl in degrees ) (3*float) -# image offcentre (ap,fh,rl in mm ) (3*float) -# slice thickness (in mm ) (float) -# slice gap (in mm ) (float) -# image_display_orientation (integer) -# slice orientation ( TRA/SAG/COR ) (integer) -# fmri_status_indication (integer) -# image_type_ed_es (end diast/end syst) (integer) -# pixel spacing (x,y) (in mm) (2*float) -# echo_time (float) -# dyn_scan_begin_time (float) -# trigger_time (float) -# diffusion_b_factor (float) -# number of averages (integer) -# image_flip_angle (in degrees) (float) -# cardiac frequency (bpm) (integer) -# minimum RR-interval (in ms) (integer) -# maximum RR-interval (in ms) (integer) -# TURBO factor <0=no turbo> (integer) -# Inversion delay (in ms) (float) -# diffusion b value number (imagekey!) (integer) -# gradient orientation number (imagekey!) (integer) -# contrast type (string) -# diffusion anisotropy type (string) -# diffusion (ap, fh, rl) (3*float) -# label type (ASL) (imagekey!) (integer) -# -# === IMAGE INFORMATION ========================================================== -# sl ec dyn ph ty idx pix scan% rec size (re)scale window angulation offcentre thick gap info spacing echo dtime ttime diff avg flip freq RR-int turbo delay b grad cont anis diffusion L.ty - - 1 1 1 1 0 2 0 16 100 80 80 0.00000 259.72406 1.53218e-002 1070 1860 0.00 -0.00 15.00 0.00 0.00 64.35 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 2 1 1 1 0 2 1 16 100 80 80 0.00000 259.72406 1.53218e-002 15508 26958 0.00 -0.00 15.00 0.00 0.00 61.05 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 3 1 1 1 0 2 2 16 100 80 80 0.00000 259.72406 1.53218e-002 27745 48229 0.00 -0.00 15.00 0.00 0.00 57.75 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 4 1 1 1 0 2 3 16 100 80 80 0.00000 259.72406 1.53218e-002 19842 34491 0.00 -0.00 15.00 0.00 0.00 54.45 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 5 1 1 1 0 2 4 16 100 80 80 0.00000 259.72406 1.53218e-002 2434 4232 0.00 -0.00 15.00 0.00 0.00 51.15 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 6 1 1 1 0 2 5 16 100 80 80 0.00000 259.72406 1.53218e-002 1456 2530 0.00 -0.00 15.00 0.00 0.00 47.85 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 7 1 1 1 0 2 6 16 100 80 80 0.00000 259.72406 1.53218e-002 145411 252771 0.00 -0.00 15.00 0.00 0.00 44.55 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 8 1 1 1 0 2 7 16 100 80 80 0.00000 259.72406 1.53218e-002 177050 307769 0.00 -0.00 15.00 0.00 0.00 41.25 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 9 1 1 1 0 2 8 16 100 80 80 0.00000 259.72406 1.53218e-002 188750 328108 0.00 -0.00 15.00 0.00 0.00 37.95 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 10 1 1 1 0 2 9 16 100 80 80 0.00000 259.72406 1.53218e-002 193758 336812 0.00 -0.00 15.00 0.00 0.00 34.65 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 11 1 1 1 0 2 10 16 100 80 80 0.00000 259.72406 1.53218e-002 199698 347139 0.00 -0.00 15.00 0.00 0.00 31.35 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 12 1 1 1 0 2 11 16 100 80 80 0.00000 259.72406 1.53218e-002 200294 348175 0.00 -0.00 15.00 0.00 0.00 28.05 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 13 1 1 1 0 2 12 16 100 80 80 0.00000 259.72406 1.53218e-002 205793 357733 0.00 -0.00 15.00 0.00 0.00 24.75 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 14 1 1 1 0 2 13 16 100 80 80 0.00000 259.72406 1.53218e-002 206112 358288 0.00 -0.00 15.00 0.00 0.00 21.45 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 15 1 1 1 0 2 14 16 100 80 80 0.00000 259.72406 1.53218e-002 213705 371487 0.00 -0.00 15.00 0.00 0.00 18.15 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 16 1 1 1 0 2 15 16 100 80 80 0.00000 259.72406 1.53218e-002 214078 372136 0.00 -0.00 15.00 0.00 0.00 14.85 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 17 1 1 1 0 2 16 16 100 80 80 0.00000 259.72406 1.53218e-002 218418 379680 0.00 -0.00 15.00 0.00 0.00 11.55 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 18 1 1 1 0 2 17 16 100 80 80 0.00000 259.72406 1.53218e-002 217818 378638 0.00 -0.00 15.00 0.00 0.00 8.25 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 19 1 1 1 0 2 18 16 100 80 80 0.00000 259.72406 1.53218e-002 217442 377984 0.00 -0.00 15.00 0.00 0.00 4.95 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 20 1 1 1 0 2 19 16 100 80 80 0.00000 259.72406 1.53218e-002 221488 385017 0.00 -0.00 15.00 0.00 0.00 1.65 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 21 1 1 1 0 2 20 16 100 80 80 0.00000 259.72406 1.53218e-002 221780 385524 0.00 -0.00 15.00 0.00 0.00 -1.65 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 22 1 1 1 0 2 21 16 100 80 80 0.00000 259.72406 1.53218e-002 223435 388401 0.00 -0.00 15.00 0.00 0.00 -4.95 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 23 1 1 1 0 2 22 16 100 80 80 0.00000 259.72406 1.53218e-002 223302 388170 0.00 -0.00 15.00 0.00 0.00 -8.25 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 24 1 1 1 0 2 23 16 100 80 80 0.00000 259.72406 1.53218e-002 222120 386116 0.00 -0.00 15.00 0.00 0.00 -11.55 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 25 1 1 1 0 2 24 16 100 80 80 0.00000 259.72406 1.53218e-002 220403 383130 0.00 -0.00 15.00 0.00 0.00 -14.85 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 26 1 1 1 0 2 25 16 100 80 80 0.00000 259.72406 1.53218e-002 222475 386732 0.00 -0.00 15.00 0.00 0.00 -18.15 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 27 1 1 1 0 2 26 16 100 80 80 0.00000 259.72406 1.53218e-002 221493 385026 0.00 -0.00 15.00 0.00 0.00 -21.45 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 28 1 1 1 0 2 27 16 100 80 80 0.00000 259.72406 1.53218e-002 217191 377546 0.00 -0.00 15.00 0.00 0.00 -24.75 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 29 1 1 1 0 2 28 16 100 80 80 0.00000 259.72406 1.53218e-002 214388 372674 0.00 -0.00 15.00 0.00 0.00 -28.05 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 30 1 1 1 0 2 29 16 100 80 80 0.00000 259.72406 1.53218e-002 212685 369715 0.00 -0.00 15.00 0.00 0.00 -31.35 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 31 1 1 1 0 2 30 16 100 80 80 0.00000 259.72406 1.53218e-002 207643 360949 0.00 -0.00 15.00 0.00 0.00 -34.65 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 32 1 1 1 0 2 31 16 100 80 80 0.00000 259.72406 1.53218e-002 204580 355625 0.00 -0.00 15.00 0.00 0.00 -37.95 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 33 1 1 1 0 2 32 16 100 80 80 0.00000 259.72406 1.53218e-002 198733 345461 0.00 -0.00 15.00 0.00 0.00 -41.25 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 34 1 1 1 0 2 33 16 100 80 80 0.00000 259.72406 1.53218e-002 194698 338446 0.00 -0.00 15.00 0.00 0.00 -44.55 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 35 1 1 1 0 2 34 16 100 80 80 0.00000 259.72406 1.53218e-002 190002 330284 0.00 -0.00 15.00 0.00 0.00 -47.85 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 36 1 1 1 0 2 35 16 100 80 80 0.00000 259.72406 1.53218e-002 190822 331709 0.00 -0.00 15.00 0.00 0.00 -51.15 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 37 1 1 1 0 2 36 16 100 80 80 0.00000 259.72406 1.53218e-002 75888 131918 0.00 -0.00 15.00 0.00 0.00 -54.45 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 38 1 1 1 0 2 37 16 100 80 80 0.00000 259.72406 1.53218e-002 1478 2569 0.00 -0.00 15.00 0.00 0.00 -57.75 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 39 1 1 1 0 2 38 16 100 80 80 0.00000 259.72406 1.53218e-002 971 1689 0.00 -0.00 15.00 0.00 0.00 -61.05 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 40 1 1 1 0 2 39 16 100 80 80 0.00000 259.72406 1.53218e-002 634 1102 0.00 -0.00 15.00 0.00 0.00 -64.35 3.000 0.300 0 2 0 2 3.000 3.000 9.63 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - -# === END OF DATA DESCRIPTION FILE =============================================== diff --git a/nibabel/tests/data/Phantom_EPI_3mm_tra_SENSE_6_1.PAR b/nibabel/tests/data/Phantom_EPI_3mm_tra_SENSE_6_1.PAR deleted file mode 100644 index bd493a72a2..0000000000 --- a/nibabel/tests/data/Phantom_EPI_3mm_tra_SENSE_6_1.PAR +++ /dev/null @@ -1,142 +0,0 @@ -# === DATA DESCRIPTION FILE ====================================================== -# -# CAUTION - Investigational device. -# Limited by Federal Law to investigational use. -# -# Dataset name: E:\\Export\\Phantom_EPI_3mm_tra_SENSE_6_1 -# -# CLINICAL TRYOUT Research image export tool V4.2 -# -# === GENERAL INFORMATION ======================================================== -# -. Patient name : Phantom -. Examination name : Orientation Par Rec -. Protocol name : EPI_3mm_tra SENSE -. Examination date/time : 2014.08.05 / 11:08:56 -. Series Type : Image MRSERIES -. Acquisition nr : 6 -. Reconstruction nr : 1 -. Scan Duration [sec] : 7.97 -. Max. number of cardiac phases : 1 -. Max. number of echoes : 1 -. Max. number of slices/locations : 40 -. Max. number of dynamics : 1 -. Max. number of mixes : 1 -. Patient position : Head First Supine -. Preparation direction : Anterior-Posterior -. Technique : FEEPI -. Scan resolution (x, y) : 80 78 -. Scan mode : MS -. Repetition time [ms] : 2000.000 -. FOV (ap,fh,rl) [mm] : 240.000 131.700 240.000 -. Water Fat shift [pixels] : 7.686 -. Angulation midslice(ap,fh,rl)[degr]: 0.000 0.000 0.000 -. Off Centre midslice(ap,fh,rl) [mm] : 0.000 0.000 0.000 -. Flow compensation <0=no 1=yes> ? : 0 -. Presaturation <0=no 1=yes> ? : 0 -. Phase encoding velocity [cm/sec] : 0.000000 0.000000 0.000000 -. MTC <0=no 1=yes> ? : 0 -. SPIR <0=no 1=yes> ? : 0 -. EPI factor <0,1=no EPI> : 39 -. Dynamic scan <0=no 1=yes> ? : 1 -. Diffusion <0=no 1=yes> ? : 0 -. Diffusion echo time [ms] : 0.0000 -. Max. number of diffusion values : 1 -. Max. number of gradient orients : 1 -. Number of label types <0=no ASL> : 0 -# -# === PIXEL VALUES ============================================================= -# PV = pixel value in REC file, FP = floating point value, DV = displayed value on console -# RS = rescale slope, RI = rescale intercept, SS = scale slope -# DV = PV * RS + RI FP = DV / (RS * SS) -# -# === IMAGE INFORMATION DEFINITION ============================================= -# The rest of this file contains ONE line per image, this line contains the following information: -# -# slice number (integer) -# echo number (integer) -# dynamic scan number (integer) -# cardiac phase number (integer) -# image_type_mr (integer) -# scanning sequence (integer) -# index in REC file (in images) (integer) -# image pixel size (in bits) (integer) -# scan percentage (integer) -# recon resolution (x y) (2*integer) -# rescale intercept (float) -# rescale slope (float) -# scale slope (float) -# window center (integer) -# window width (integer) -# image angulation (ap,fh,rl in degrees ) (3*float) -# image offcentre (ap,fh,rl in mm ) (3*float) -# slice thickness (in mm ) (float) -# slice gap (in mm ) (float) -# image_display_orientation (integer) -# slice orientation ( TRA/SAG/COR ) (integer) -# fmri_status_indication (integer) -# image_type_ed_es (end diast/end syst) (integer) -# pixel spacing (x,y) (in mm) (2*float) -# echo_time (float) -# dyn_scan_begin_time (float) -# trigger_time (float) -# diffusion_b_factor (float) -# number of averages (integer) -# image_flip_angle (in degrees) (float) -# cardiac frequency (bpm) (integer) -# minimum RR-interval (in ms) (integer) -# maximum RR-interval (in ms) (integer) -# TURBO factor <0=no turbo> (integer) -# Inversion delay (in ms) (float) -# diffusion b value number (imagekey!) (integer) -# gradient orientation number (imagekey!) (integer) -# contrast type (string) -# diffusion anisotropy type (string) -# diffusion (ap, fh, rl) (3*float) -# label type (ASL) (imagekey!) (integer) -# -# === IMAGE INFORMATION ========================================================== -# sl ec dyn ph ty idx pix scan% rec size (re)scale window angulation offcentre thick gap info spacing echo dtime ttime diff avg flip freq RR-int turbo delay b grad cont anis diffusion L.ty - - 1 1 1 1 0 2 0 16 100 80 80 0.00000 414.19659 1.64074e-002 1070 1860 0.00 -0.00 -0.00 0.00 -64.35 0.00 3.000 0.300 0 1 0 2 3.000 3.000 10.17 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 2 1 1 1 0 2 1 16 100 80 80 0.00000 414.19659 1.64074e-002 1164 2023 0.00 -0.00 -0.00 0.00 -61.05 0.00 3.000 0.300 0 1 0 2 3.000 3.000 10.17 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 3 1 1 1 0 2 2 16 100 80 80 0.00000 414.19659 1.64074e-002 1506 2619 0.00 -0.00 -0.00 0.00 -57.75 0.00 3.000 0.300 0 1 0 2 3.000 3.000 10.17 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 4 1 1 1 0 2 3 16 100 80 80 0.00000 414.19659 1.64074e-002 13012 22618 0.00 -0.00 -0.00 0.00 -54.45 0.00 3.000 0.300 0 1 0 2 3.000 3.000 10.17 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 5 1 1 1 0 2 4 16 100 80 80 0.00000 414.19659 1.64074e-002 8549 14861 0.00 -0.00 -0.00 0.00 -51.15 0.00 3.000 0.300 0 1 0 2 3.000 3.000 10.17 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 6 1 1 1 0 2 5 16 100 80 80 0.00000 414.19659 1.64074e-002 4059 7056 0.00 -0.00 -0.00 0.00 -47.85 0.00 3.000 0.300 0 1 0 2 3.000 3.000 10.17 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 7 1 1 1 0 2 6 16 100 80 80 0.00000 414.19659 1.64074e-002 1543 2682 0.00 -0.00 -0.00 0.00 -44.55 0.00 3.000 0.300 0 1 0 2 3.000 3.000 10.17 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 8 1 1 1 0 2 7 16 100 80 80 0.00000 414.19659 1.64074e-002 1839 3197 0.00 -0.00 -0.00 0.00 -41.25 0.00 3.000 0.300 0 1 0 2 3.000 3.000 10.17 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 9 1 1 1 0 2 8 16 100 80 80 0.00000 414.19659 1.64074e-002 190371 330925 0.00 -0.00 -0.00 0.00 -37.95 0.00 3.000 0.300 0 1 0 2 3.000 3.000 10.17 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 10 1 1 1 0 2 9 16 100 80 80 0.00000 414.19659 1.64074e-002 318821 554212 0.00 -0.00 -0.00 0.00 -34.65 0.00 3.000 0.300 0 1 0 2 3.000 3.000 10.17 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 11 1 1 1 0 2 10 16 100 80 80 0.00000 414.19659 1.64074e-002 303205 527066 0.00 -0.00 -0.00 0.00 -31.35 0.00 3.000 0.300 0 1 0 2 3.000 3.000 10.17 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 12 1 1 1 0 2 11 16 100 80 80 0.00000 414.19659 1.64074e-002 300314 522040 0.00 -0.00 -0.00 0.00 -28.05 0.00 3.000 0.300 0 1 0 2 3.000 3.000 10.17 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 13 1 1 1 0 2 12 16 100 80 80 0.00000 414.19659 1.64074e-002 311429 541363 0.00 -0.00 -0.00 0.00 -24.75 0.00 3.000 0.300 0 1 0 2 3.000 3.000 10.17 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 14 1 1 1 0 2 13 16 100 80 80 0.00000 414.19659 1.64074e-002 320594 557294 0.00 -0.00 -0.00 0.00 -21.45 0.00 3.000 0.300 0 1 0 2 3.000 3.000 10.17 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 15 1 1 1 0 2 14 16 100 80 80 0.00000 414.19659 1.64074e-002 329299 572427 0.00 -0.00 -0.00 0.00 -18.15 0.00 3.000 0.300 0 1 0 2 3.000 3.000 10.17 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 16 1 1 1 0 2 15 16 100 80 80 0.00000 414.19659 1.64074e-002 336674 585246 0.00 -0.00 -0.00 0.00 -14.85 0.00 3.000 0.300 0 1 0 2 3.000 3.000 10.17 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 17 1 1 1 0 2 16 16 100 80 80 0.00000 414.19659 1.64074e-002 338184 587871 0.00 -0.00 -0.00 0.00 -11.55 0.00 3.000 0.300 0 1 0 2 3.000 3.000 10.17 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 18 1 1 1 0 2 17 16 100 80 80 0.00000 414.19659 1.64074e-002 343986 597957 0.00 -0.00 -0.00 0.00 -8.25 0.00 3.000 0.300 0 1 0 2 3.000 3.000 10.17 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 19 1 1 1 0 2 18 16 100 80 80 0.00000 414.19659 1.64074e-002 343444 597015 0.00 -0.00 -0.00 0.00 -4.95 0.00 3.000 0.300 0 1 0 2 3.000 3.000 10.17 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 20 1 1 1 0 2 19 16 100 80 80 0.00000 414.19659 1.64074e-002 359925 625665 0.00 -0.00 -0.00 0.00 -1.65 0.00 3.000 0.300 0 1 0 2 3.000 3.000 10.17 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 21 1 1 1 0 2 20 16 100 80 80 0.00000 414.19659 1.64074e-002 373149 648652 0.00 -0.00 -0.00 0.00 1.65 0.00 3.000 0.300 0 1 0 2 3.000 3.000 10.17 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 22 1 1 1 0 2 21 16 100 80 80 0.00000 414.19659 1.64074e-002 377171 655644 0.00 -0.00 -0.00 0.00 4.95 0.00 3.000 0.300 0 1 0 2 3.000 3.000 10.17 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 23 1 1 1 0 2 22 16 100 80 80 0.00000 414.19659 1.64074e-002 378778 658436 0.00 -0.00 -0.00 0.00 8.25 0.00 3.000 0.300 0 1 0 2 3.000 3.000 10.17 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 24 1 1 1 0 2 23 16 100 80 80 0.00000 414.19659 1.64074e-002 388963 676141 0.00 -0.00 -0.00 0.00 11.55 0.00 3.000 0.300 0 1 0 2 3.000 3.000 10.17 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 25 1 1 1 0 2 24 16 100 80 80 0.00000 414.19659 1.64074e-002 391028 679731 0.00 -0.00 -0.00 0.00 14.85 0.00 3.000 0.300 0 1 0 2 3.000 3.000 10.17 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 26 1 1 1 0 2 25 16 100 80 80 0.00000 414.19659 1.64074e-002 398781 693208 0.00 -0.00 -0.00 0.00 18.15 0.00 3.000 0.300 0 1 0 2 3.000 3.000 10.17 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 27 1 1 1 0 2 26 16 100 80 80 0.00000 414.19659 1.64074e-002 403891 702092 0.00 -0.00 -0.00 0.00 21.45 0.00 3.000 0.300 0 1 0 2 3.000 3.000 10.17 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 28 1 1 1 0 2 27 16 100 80 80 0.00000 414.19659 1.64074e-002 392711 682657 0.00 -0.00 -0.00 0.00 24.75 0.00 3.000 0.300 0 1 0 2 3.000 3.000 10.17 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 29 1 1 1 0 2 28 16 100 80 80 0.00000 414.19659 1.64074e-002 368011 639720 0.00 -0.00 -0.00 0.00 28.05 0.00 3.000 0.300 0 1 0 2 3.000 3.000 10.17 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 30 1 1 1 0 2 29 16 100 80 80 0.00000 414.19659 1.64074e-002 371379 645575 0.00 -0.00 -0.00 0.00 31.35 0.00 3.000 0.300 0 1 0 2 3.000 3.000 10.17 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 31 1 1 1 0 2 30 16 100 80 80 0.00000 414.19659 1.64074e-002 360746 627092 0.00 -0.00 -0.00 0.00 34.65 0.00 3.000 0.300 0 1 0 2 3.000 3.000 10.17 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 32 1 1 1 0 2 31 16 100 80 80 0.00000 414.19659 1.64074e-002 363409 631720 0.00 -0.00 -0.00 0.00 37.95 0.00 3.000 0.300 0 1 0 2 3.000 3.000 10.17 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 33 1 1 1 0 2 32 16 100 80 80 0.00000 414.19659 1.64074e-002 363776 632359 0.00 -0.00 -0.00 0.00 41.25 0.00 3.000 0.300 0 1 0 2 3.000 3.000 10.17 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 34 1 1 1 0 2 33 16 100 80 80 0.00000 414.19659 1.64074e-002 360339 626384 0.00 -0.00 -0.00 0.00 44.55 0.00 3.000 0.300 0 1 0 2 3.000 3.000 10.17 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 35 1 1 1 0 2 34 16 100 80 80 0.00000 414.19659 1.64074e-002 357627 621670 0.00 -0.00 -0.00 0.00 47.85 0.00 3.000 0.300 0 1 0 2 3.000 3.000 10.17 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 36 1 1 1 0 2 35 16 100 80 80 0.00000 414.19659 1.64074e-002 354632 616463 0.00 -0.00 -0.00 0.00 51.15 0.00 3.000 0.300 0 1 0 2 3.000 3.000 10.17 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 37 1 1 1 0 2 36 16 100 80 80 0.00000 414.19659 1.64074e-002 348500 605803 0.00 -0.00 -0.00 0.00 54.45 0.00 3.000 0.300 0 1 0 2 3.000 3.000 10.17 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 38 1 1 1 0 2 37 16 100 80 80 0.00000 414.19659 1.64074e-002 340439 591791 0.00 -0.00 -0.00 0.00 57.75 0.00 3.000 0.300 0 1 0 2 3.000 3.000 10.17 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 39 1 1 1 0 2 38 16 100 80 80 0.00000 414.19659 1.64074e-002 216356 376095 0.00 -0.00 -0.00 0.00 61.05 0.00 3.000 0.300 0 1 0 2 3.000 3.000 10.17 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - 40 1 1 1 0 2 39 16 100 80 80 0.00000 414.19659 1.64074e-002 1973 3429 0.00 -0.00 -0.00 0.00 64.35 0.00 3.000 0.300 0 1 0 2 3.000 3.000 10.17 0.00 0.00 0.00 1 90.00 0 0 0 39 0.0 1 1 4 0 0.000 0.000 0.000 1 - -# === END OF DATA DESCRIPTION FILE =============================================== diff --git a/nibabel/tests/data/README.rst b/nibabel/tests/data/README.rst deleted file mode 100644 index 35240d06b4..0000000000 --- a/nibabel/tests/data/README.rst +++ /dev/null @@ -1,11 +0,0 @@ -################## -Nibabel data files -################## - -``phantom_truncated.REC`` is a copy of ``phantom_EPI_asc_CLEAR_2_1.REC``. - -``phantom_truncated.PAR`` is a slightly edited copy of -``phantom_EPI_asc_CLEAR_2_1.PAR``. - -``umass_anonymized.PAR`` courtesy of Github user ``cccbauer``, data from the -University of Massachusetts medical school. diff --git a/nibabel/tests/data/T1.PAR b/nibabel/tests/data/T1.PAR deleted file mode 100644 index 4abc1987e5..0000000000 --- a/nibabel/tests/data/T1.PAR +++ /dev/null @@ -1,112 +0,0 @@ -# === DATA DESCRIPTION FILE ====================================================== -# -# CAUTION - Investigational device. -# Limited by Federal Law to investigational use. -# -# Dataset name: H:\Export\05aug14_test_samples_6_1 -# -# CLINICAL TRYOUT Research image export tool V4.2 -# -# === GENERAL INFORMATION ======================================================== -# -. Patient name : 05aug14test -. Examination name : test -. Protocol name : T1 SENSE -. Examination date/time : 2014.08.05 / 11:27:34 -. Series Type : Image MRSERIES -. Acquisition nr : 6 -. Reconstruction nr : 1 -. Scan Duration [sec] : 65 -. Max. number of cardiac phases : 1 -. Max. number of echoes : 1 -. Max. number of slices/locations : 10 -. Max. number of dynamics : 1 -. Max. number of mixes : 1 -. Patient position : Head First Supine -. Preparation direction : Right-Left -. Technique : T1TFE -. Scan resolution (x, y) : 76 62 -. Scan mode : 3D -. Repetition time [ms] : 4.364 -. FOV (ap,fh,rl) [mm] : 130.000 100.000 154.375 -. Water Fat shift [pixels] : 1.117 -. Angulation midslice(ap,fh,rl)[degr]: -1.979 0.546 0.019 -. Off Centre midslice(ap,fh,rl) [mm] : -18.805 22.157 -17.977 -. Flow compensation <0=no 1=yes> ? : 0 -. Presaturation <0=no 1=yes> ? : 0 -. Phase encoding velocity [cm/sec] : 0.000000 0.000000 0.000000 -. MTC <0=no 1=yes> ? : 0 -. SPIR <0=no 1=yes> ? : 0 -. EPI factor <0,1=no EPI> : 1 -. Dynamic scan <0=no 1=yes> ? : 0 -. Diffusion <0=no 1=yes> ? : 0 -. Diffusion echo time [ms] : 0.0000 -. Max. number of diffusion values : 1 -. Max. number of gradient orients : 1 -. Number of label types <0=no ASL> : 0 -# -# === PIXEL VALUES ============================================================= -# PV = pixel value in REC file, FP = floating point value, DV = displayed value on console -# RS = rescale slope, RI = rescale intercept, SS = scale slope -# DV = PV * RS + RI FP = DV / (RS * SS) -# -# === IMAGE INFORMATION DEFINITION ============================================= -# The rest of this file contains ONE line per image, this line contains the following information: -# -# slice number (integer) -# echo number (integer) -# dynamic scan number (integer) -# cardiac phase number (integer) -# image_type_mr (integer) -# scanning sequence (integer) -# index in REC file (in images) (integer) -# image pixel size (in bits) (integer) -# scan percentage (integer) -# recon resolution (x y) (2*integer) -# rescale intercept (float) -# rescale slope (float) -# scale slope (float) -# window center (integer) -# window width (integer) -# image angulation (ap,fh,rl in degrees ) (3*float) -# image offcentre (ap,fh,rl in mm ) (3*float) -# slice thickness (in mm ) (float) -# slice gap (in mm ) (float) -# image_display_orientation (integer) -# slice orientation ( TRA/SAG/COR ) (integer) -# fmri_status_indication (integer) -# image_type_ed_es (end diast/end syst) (integer) -# pixel spacing (x,y) (in mm) (2*float) -# echo_time (float) -# dyn_scan_begin_time (float) -# trigger_time (float) -# diffusion_b_factor (float) -# number of averages (integer) -# image_flip_angle (in degrees) (float) -# cardiac frequency (bpm) (integer) -# minimum RR-interval (in ms) (integer) -# maximum RR-interval (in ms) (integer) -# TURBO factor <0=no turbo> (integer) -# Inversion delay (in ms) (float) -# diffusion b value number (imagekey!) (integer) -# gradient orientation number (imagekey!) (integer) -# contrast type (string) -# diffusion anisotropy type (string) -# diffusion (ap, fh, rl) (3*float) -# label type (ASL) (imagekey!) (integer) -# -# === IMAGE INFORMATION ========================================================== -# sl ec dyn ph ty idx pix scan% rec size (re)scale window angulation offcentre thick gap info spacing echo dtime ttime diff avg flip freq RR-int turbo delay b grad cont anis diffusion L.ty - - 1 1 1 1 0 2 0 16 81 80 80 0.00000 1.26032 2.84925e-005 133 231 -1.98 0.55 0.02 -18.79 -22.82 -16.42 10.000 0.000 0 1 0 2 1.912 1.912 2.08 0.00 0.00 0.00 1 8.00 0 0 0 7 0.0 1 1 7 0 0.000 0.000 0.000 1 - 2 1 1 1 0 2 1 16 81 80 80 0.00000 1.26032 2.84925e-005 294 512 -1.98 0.55 0.02 -18.79 -12.82 -16.77 10.000 0.000 0 1 0 2 1.912 1.912 2.08 0.00 0.00 0.00 1 8.00 0 0 0 7 0.0 1 1 7 0 0.000 0.000 0.000 1 - 3 1 1 1 0 2 2 16 81 80 80 0.00000 1.26032 2.84925e-005 427 742 -1.98 0.55 0.02 -18.80 -2.83 -17.11 10.000 0.000 0 1 0 2 1.912 1.912 2.08 0.00 0.00 0.00 1 8.00 0 0 0 7 0.0 1 1 7 0 0.000 0.000 0.000 1 - 4 1 1 1 0 2 3 16 81 80 80 0.00000 1.26032 2.84925e-005 565 982 -1.98 0.55 0.02 -18.80 7.17 -17.46 10.000 0.000 0 1 0 2 1.912 1.912 2.08 0.00 0.00 0.00 1 8.00 0 0 0 7 0.0 1 1 7 0 0.000 0.000 0.000 1 - 5 1 1 1 0 2 4 16 81 80 80 0.00000 1.26032 2.84925e-005 474 825 -1.98 0.55 0.02 -18.80 17.16 -17.80 10.000 0.000 0 1 0 2 1.912 1.912 2.08 0.00 0.00 0.00 1 8.00 0 0 0 7 0.0 1 1 7 0 0.000 0.000 0.000 1 - 6 1 1 1 0 2 5 16 81 80 80 0.00000 1.26032 2.84925e-005 1070 1860 -1.98 0.55 0.02 -18.81 27.15 -18.15 10.000 0.000 0 1 0 2 1.912 1.912 2.08 0.00 0.00 0.00 1 8.00 0 0 0 7 0.0 1 1 7 0 0.000 0.000 0.000 1 - 7 1 1 1 0 2 6 16 81 80 80 0.00000 1.26032 2.84925e-005 1179 2049 -1.98 0.55 0.02 -18.81 37.15 -18.49 10.000 0.000 0 1 0 2 1.912 1.912 2.08 0.00 0.00 0.00 1 8.00 0 0 0 7 0.0 1 1 7 0 0.000 0.000 0.000 1 - 8 1 1 1 0 2 7 16 81 80 80 0.00000 1.26032 2.84925e-005 427 742 -1.98 0.55 0.02 -18.81 47.14 -18.84 10.000 0.000 0 1 0 2 1.912 1.912 2.08 0.00 0.00 0.00 1 8.00 0 0 0 7 0.0 1 1 7 0 0.000 0.000 0.000 1 - 9 1 1 1 0 2 8 16 81 80 80 0.00000 1.26032 2.84925e-005 175 304 -1.98 0.55 0.02 -18.82 57.14 -19.19 10.000 0.000 0 1 0 2 1.912 1.912 2.08 0.00 0.00 0.00 1 8.00 0 0 0 7 0.0 1 1 7 0 0.000 0.000 0.000 1 - 10 1 1 1 0 2 9 16 81 80 80 0.00000 1.26032 2.84925e-005 114 199 -1.98 0.55 0.02 -18.82 67.13 -19.53 10.000 0.000 0 1 0 2 1.912 1.912 2.08 0.00 0.00 0.00 1 8.00 0 0 0 7 0.0 1 1 7 0 0.000 0.000 0.000 1 - -# === END OF DATA DESCRIPTION FILE =============================================== diff --git a/nibabel/tests/data/T1_3echo_mag_real_imag_phase.PAR b/nibabel/tests/data/T1_3echo_mag_real_imag_phase.PAR deleted file mode 100644 index e918475f2c..0000000000 --- a/nibabel/tests/data/T1_3echo_mag_real_imag_phase.PAR +++ /dev/null @@ -1,462 +0,0 @@ -# === DATA DESCRIPTION FILE ====================================================== -# -# CAUTION - Investigational device. -# Limited by Federal Law to investigational use. -# -# Dataset name: E:\\Export\\T1_3echo_mag_real_imag_phase -# -# CLINICAL TRYOUT Research image export tool V4.2 -# -# === GENERAL INFORMATION ======================================================== -# -. Patient name : anon -. Examination name : anon -. Protocol name : anon -. Examination date/time : anon -. Series Type : Image MRSERIES -. Acquisition nr : 15 -. Reconstruction nr : 1 -. Scan Duration [sec] : 11.5 -. Max. number of cardiac phases : 1 -. Max. number of echoes : 3 -. Max. number of slices/locations : 30 -. Max. number of dynamics : 1 -. Max. number of mixes : 1 -. Patient position : Head First Supine -. Preparation direction : Anterior-Posterior -. Technique : T1TFE -. Scan resolution (x, y) : 76 75 -. Scan mode : 3D -. Repetition time [ms] : 6.746 -. FOV (ap,fh,rl) [mm] : 224.000 224.000 90.000 -. Water Fat shift [pixels] : 0.701 -. Angulation midslice(ap,fh,rl)[degr]: 0.000 0.000 0.000 -. Off Centre midslice(ap,fh,rl) [mm] : 5.090 8.176 -4.208 -. Flow compensation <0=no 1=yes> ? : 0 -. Presaturation <0=no 1=yes> ? : 0 -. Phase encoding velocity [cm/sec] : 0.000000 0.000000 0.000000 -. MTC <0=no 1=yes> ? : 0 -. SPIR <0=no 1=yes> ? : 0 -. EPI factor <0,1=no EPI> : 1 -. Dynamic scan <0=no 1=yes> ? : 0 -. Diffusion <0=no 1=yes> ? : 0 -. Diffusion echo time [ms] : 0.0000 -. Max. number of diffusion values : 1 -. Max. number of gradient orients : 1 -. Number of label types <0=no ASL> : 0 -# -# === PIXEL VALUES ============================================================= -# PV = pixel value in REC file, FP = floating point value, DV = displayed value on console -# RS = rescale slope, RI = rescale intercept, SS = scale slope -# DV = PV * RS + RI FP = DV / (RS * SS) -# -# === IMAGE INFORMATION DEFINITION ============================================= -# The rest of this file contains ONE line per image, this line contains the following information: -# -# slice number (integer) -# echo number (integer) -# dynamic scan number (integer) -# cardiac phase number (integer) -# image_type_mr (integer) -# scanning sequence (integer) -# index in REC file (in images) (integer) -# image pixel size (in bits) (integer) -# scan percentage (integer) -# recon resolution (x y) (2*integer) -# rescale intercept (float) -# rescale slope (float) -# scale slope (float) -# window center (integer) -# window width (integer) -# image angulation (ap,fh,rl in degrees ) (3*float) -# image offcentre (ap,fh,rl in mm ) (3*float) -# slice thickness (in mm ) (float) -# slice gap (in mm ) (float) -# image_display_orientation (integer) -# slice orientation ( TRA/SAG/COR ) (integer) -# fmri_status_indication (integer) -# image_type_ed_es (end diast/end syst) (integer) -# pixel spacing (x,y) (in mm) (2*float) -# echo_time (float) -# dyn_scan_begin_time (float) -# trigger_time (float) -# diffusion_b_factor (float) -# number of averages (integer) -# image_flip_angle (in degrees) (float) -# cardiac frequency (bpm) (integer) -# minimum RR-interval (in ms) (integer) -# maximum RR-interval (in ms) (integer) -# TURBO factor <0=no turbo> (integer) -# Inversion delay (in ms) (float) -# diffusion b value number (imagekey!) (integer) -# gradient orientation number (imagekey!) (integer) -# contrast type (string) -# diffusion anisotropy type (string) -# diffusion (ap, fh, rl) (3*float) -# label type (ASL) (imagekey!) (integer) -# -# === IMAGE INFORMATION ========================================================== -# sl ec dyn ph ty idx pix scan% rec size (re)scale window angulation offcentre thick gap info spacing echo dtime ttime diff avg flip freq RR-int turbo delay b grad cont anis diffusion L.ty - - 16 1 1 1 1 2 0 16 98 80 80 -5272.00000 2.57485 4.89549e-005 -847 2280 0.00 -0.00 -0.00 5.09 8.18 -5.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 16 1 1 1 2 2 1 16 98 80 80 -5272.00000 2.57485 4.89549e-005 76 1070 0.00 -0.00 -0.00 5.09 8.18 -5.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 16 1 1 1 0 2 2 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -5.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 16 1 1 1 3 2 3 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -5.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 16 2 1 1 1 2 4 16 98 80 80 -5272.00000 2.57485 4.89549e-005 -689 2071 0.00 -0.00 -0.00 5.09 8.18 -5.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 16 2 1 1 2 2 5 16 98 80 80 -5272.00000 2.57485 4.89549e-005 287 1581 0.00 -0.00 -0.00 5.09 8.18 -5.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 16 2 1 1 0 2 6 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -5.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 16 2 1 1 3 2 7 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -5.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 16 3 1 1 1 2 8 16 98 80 80 -5272.00000 2.57485 4.89549e-005 -321 2252 0.00 -0.00 -0.00 5.09 8.18 -5.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 16 3 1 1 2 2 9 16 98 80 80 -5272.00000 2.57485 4.89549e-005 466 2433 0.00 -0.00 -0.00 5.09 8.18 -5.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 16 3 1 1 0 2 10 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -5.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 16 3 1 1 3 2 11 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -5.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 17 1 1 1 1 2 12 16 98 80 80 -5272.00000 2.57485 4.89549e-005 -978 2484 0.00 -0.00 -0.00 5.09 8.18 -8.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 17 1 1 1 2 2 13 16 98 80 80 -5272.00000 2.57485 4.89549e-005 44 1009 0.00 -0.00 -0.00 5.09 8.18 -8.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 17 1 1 1 0 2 14 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -8.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 17 1 1 1 3 2 15 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -8.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 17 2 1 1 1 2 16 16 98 80 80 -5272.00000 2.57485 4.89549e-005 -797 2248 0.00 -0.00 -0.00 5.09 8.18 -8.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 17 2 1 1 2 2 17 16 98 80 80 -5272.00000 2.57485 4.89549e-005 286 1422 0.00 -0.00 -0.00 5.09 8.18 -8.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 17 2 1 1 0 2 18 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -8.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 17 2 1 1 3 2 19 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -8.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 17 3 1 1 1 2 20 16 98 80 80 -5272.00000 2.57485 4.89549e-005 -440 2288 0.00 -0.00 -0.00 5.09 8.18 -8.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 17 3 1 1 2 2 21 16 98 80 80 -5272.00000 2.57485 4.89549e-005 567 2202 0.00 -0.00 -0.00 5.09 8.18 -8.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 17 3 1 1 0 2 22 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -8.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 17 3 1 1 3 2 23 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -8.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 18 1 1 1 1 2 24 16 98 80 80 -5272.00000 2.57485 4.89549e-005 -879 2292 0.00 -0.00 -0.00 5.09 8.18 -11.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 18 1 1 1 2 2 25 16 98 80 80 -5272.00000 2.57485 4.89549e-005 74 814 0.00 -0.00 -0.00 5.09 8.18 -11.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 18 1 1 1 0 2 26 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -11.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 18 1 1 1 3 2 27 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -11.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 18 2 1 1 1 2 28 16 98 80 80 -5272.00000 2.57485 4.89549e-005 -729 2051 0.00 -0.00 -0.00 5.09 8.18 -11.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 18 2 1 1 2 2 29 16 98 80 80 -5272.00000 2.57485 4.89549e-005 282 1127 0.00 -0.00 -0.00 5.09 8.18 -11.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 18 2 1 1 0 2 30 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -11.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 18 2 1 1 3 2 31 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -11.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 18 3 1 1 1 2 32 16 98 80 80 -5272.00000 2.57485 4.89549e-005 -328 2043 0.00 -0.00 -0.00 5.09 8.18 -11.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 18 3 1 1 2 2 33 16 98 80 80 -5272.00000 2.57485 4.89549e-005 456 1778 0.00 -0.00 -0.00 5.09 8.18 -11.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 18 3 1 1 0 2 34 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -11.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 18 3 1 1 3 2 35 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -11.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 19 1 1 1 1 2 36 16 98 80 80 -5272.00000 2.57485 4.89549e-005 -423 1427 0.00 -0.00 -0.00 5.09 8.18 -14.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 19 1 1 1 2 2 37 16 98 80 80 -5272.00000 2.57485 4.89549e-005 84 775 0.00 -0.00 -0.00 5.09 8.18 -14.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 19 1 1 1 0 2 38 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -14.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 19 1 1 1 3 2 39 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -14.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 19 2 1 1 1 2 40 16 98 80 80 -5272.00000 2.57485 4.89549e-005 -327 1220 0.00 -0.00 -0.00 5.09 8.18 -14.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 19 2 1 1 2 2 41 16 98 80 80 -5272.00000 2.57485 4.89549e-005 157 1021 0.00 -0.00 -0.00 5.09 8.18 -14.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 19 2 1 1 0 2 42 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -14.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 19 2 1 1 3 2 43 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -14.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 19 3 1 1 1 2 44 16 98 80 80 -5272.00000 2.57485 4.89549e-005 -107 1602 0.00 -0.00 -0.00 5.09 8.18 -14.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 19 3 1 1 2 2 45 16 98 80 80 -5272.00000 2.57485 4.89549e-005 172 1277 0.00 -0.00 -0.00 5.09 8.18 -14.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 19 3 1 1 0 2 46 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -14.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 19 3 1 1 3 2 47 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -14.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 20 1 1 1 1 2 48 16 98 80 80 -5272.00000 2.57485 4.89549e-005 -376 1278 0.00 -0.00 -0.00 5.09 8.18 -17.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 20 1 1 1 2 2 49 16 98 80 80 -5272.00000 2.57485 4.89549e-005 85 810 0.00 -0.00 -0.00 5.09 8.18 -17.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 20 1 1 1 0 2 50 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -17.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 20 1 1 1 3 2 51 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -17.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 20 2 1 1 1 2 52 16 98 80 80 -5272.00000 2.57485 4.89549e-005 -337 1141 0.00 -0.00 -0.00 5.09 8.18 -17.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 20 2 1 1 2 2 53 16 98 80 80 -5272.00000 2.57485 4.89549e-005 110 1021 0.00 -0.00 -0.00 5.09 8.18 -17.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 20 2 1 1 0 2 54 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -17.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 20 2 1 1 3 2 55 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -17.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 20 3 1 1 1 2 56 16 98 80 80 -5272.00000 2.57485 4.89549e-005 -43 1568 0.00 -0.00 -0.00 5.09 8.18 -17.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 20 3 1 1 2 2 57 16 98 80 80 -5272.00000 2.57485 4.89549e-005 123 1213 0.00 -0.00 -0.00 5.09 8.18 -17.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 20 3 1 1 0 2 58 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -17.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 20 3 1 1 3 2 59 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -17.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 21 1 1 1 1 2 60 16 98 80 80 -5272.00000 2.57485 4.89549e-005 472 1014 0.00 -0.00 -0.00 5.09 8.18 -20.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 21 1 1 1 2 2 61 16 98 80 80 -5272.00000 2.57485 4.89549e-005 190 704 0.00 -0.00 -0.00 5.09 8.18 -20.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 21 1 1 1 0 2 62 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -20.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 21 1 1 1 3 2 63 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -20.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 21 2 1 1 1 2 64 16 98 80 80 -5272.00000 2.57485 4.89549e-005 400 1246 0.00 -0.00 -0.00 5.09 8.18 -20.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 21 2 1 1 2 2 65 16 98 80 80 -5272.00000 2.57485 4.89549e-005 171 1230 0.00 -0.00 -0.00 5.09 8.18 -20.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 21 2 1 1 0 2 66 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -20.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 21 2 1 1 3 2 67 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -20.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 21 3 1 1 1 2 68 16 98 80 80 -5272.00000 2.57485 4.89549e-005 249 1515 0.00 -0.00 -0.00 5.09 8.18 -20.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 21 3 1 1 2 2 69 16 98 80 80 -5272.00000 2.57485 4.89549e-005 110 1626 0.00 -0.00 -0.00 5.09 8.18 -20.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 21 3 1 1 0 2 70 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -20.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 21 3 1 1 3 2 71 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -20.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 22 1 1 1 1 2 72 16 98 80 80 -5272.00000 2.57485 4.89549e-005 571 1248 0.00 -0.00 -0.00 5.09 8.18 -23.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 22 1 1 1 2 2 73 16 98 80 80 -5272.00000 2.57485 4.89549e-005 211 747 0.00 -0.00 -0.00 5.09 8.18 -23.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 22 1 1 1 0 2 74 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -23.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 22 1 1 1 3 2 75 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -23.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 22 2 1 1 1 2 76 16 98 80 80 -5272.00000 2.57485 4.89549e-005 446 1342 0.00 -0.00 -0.00 5.09 8.18 -23.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 22 2 1 1 2 2 77 16 98 80 80 -5272.00000 2.57485 4.89549e-005 193 1327 0.00 -0.00 -0.00 5.09 8.18 -23.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 22 2 1 1 0 2 78 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -23.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 22 2 1 1 3 2 79 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -23.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 22 3 1 1 1 2 80 16 98 80 80 -5272.00000 2.57485 4.89549e-005 272 1590 0.00 -0.00 -0.00 5.09 8.18 -23.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 22 3 1 1 2 2 81 16 98 80 80 -5272.00000 2.57485 4.89549e-005 112 1726 0.00 -0.00 -0.00 5.09 8.18 -23.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 22 3 1 1 0 2 82 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -23.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 22 3 1 1 3 2 83 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -23.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 23 1 1 1 1 2 84 16 98 80 80 -5272.00000 2.57485 4.89549e-005 642 1363 0.00 -0.00 -0.00 5.09 8.18 -26.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 23 1 1 1 2 2 85 16 98 80 80 -5272.00000 2.57485 4.89549e-005 273 829 0.00 -0.00 -0.00 5.09 8.18 -26.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 23 1 1 1 0 2 86 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -26.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 23 1 1 1 3 2 87 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -26.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 23 2 1 1 1 2 88 16 98 80 80 -5272.00000 2.57485 4.89549e-005 498 1443 0.00 -0.00 -0.00 5.09 8.18 -26.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 23 2 1 1 2 2 89 16 98 80 80 -5272.00000 2.57485 4.89549e-005 242 1372 0.00 -0.00 -0.00 5.09 8.18 -26.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 23 2 1 1 0 2 90 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -26.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 23 2 1 1 3 2 91 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -26.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 23 3 1 1 1 2 92 16 98 80 80 -5272.00000 2.57485 4.89549e-005 308 1640 0.00 -0.00 -0.00 5.09 8.18 -26.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 23 3 1 1 2 2 93 16 98 80 80 -5272.00000 2.57485 4.89549e-005 135 1793 0.00 -0.00 -0.00 5.09 8.18 -26.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 23 3 1 1 0 2 94 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -26.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 23 3 1 1 3 2 95 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -26.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 24 1 1 1 1 2 96 16 98 80 80 -5272.00000 2.57485 4.89549e-005 165 882 0.00 -0.00 -0.00 5.09 8.18 -29.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 24 1 1 1 2 2 97 16 98 80 80 -5272.00000 2.57485 4.89549e-005 682 1445 0.00 -0.00 -0.00 5.09 8.18 -29.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 24 1 1 1 0 2 98 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -29.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 24 1 1 1 3 2 99 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -29.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 24 2 1 1 1 2 100 16 98 80 80 -5272.00000 2.57485 4.89549e-005 217 1570 0.00 -0.00 -0.00 5.09 8.18 -29.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 24 2 1 1 2 2 101 16 98 80 80 -5272.00000 2.57485 4.89549e-005 520 1282 0.00 -0.00 -0.00 5.09 8.18 -29.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 24 2 1 1 0 2 102 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -29.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 24 2 1 1 3 2 103 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -29.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 24 3 1 1 1 2 104 16 98 80 80 -5272.00000 2.57485 4.89549e-005 225 2055 0.00 -0.00 -0.00 5.09 8.18 -29.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 24 3 1 1 2 2 105 16 98 80 80 -5272.00000 2.57485 4.89549e-005 252 1320 0.00 -0.00 -0.00 5.09 8.18 -29.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 24 3 1 1 0 2 106 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -29.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 24 3 1 1 3 2 107 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -29.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 25 1 1 1 1 2 108 16 98 80 80 -5272.00000 2.57485 4.89549e-005 197 920 0.00 -0.00 -0.00 5.09 8.18 -32.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 25 1 1 1 2 2 109 16 98 80 80 -5272.00000 2.57485 4.89549e-005 685 1471 0.00 -0.00 -0.00 5.09 8.18 -32.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 25 1 1 1 0 2 110 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -32.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 25 1 1 1 3 2 111 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -32.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 25 2 1 1 1 2 112 16 98 80 80 -5272.00000 2.57485 4.89549e-005 356 1513 0.00 -0.00 -0.00 5.09 8.18 -32.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 25 2 1 1 2 2 113 16 98 80 80 -5272.00000 2.57485 4.89549e-005 512 1384 0.00 -0.00 -0.00 5.09 8.18 -32.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 25 2 1 1 0 2 114 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -32.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 25 2 1 1 3 2 115 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -32.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 25 3 1 1 1 2 116 16 98 80 80 -5272.00000 2.57485 4.89549e-005 252 1952 0.00 -0.00 -0.00 5.09 8.18 -32.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 25 3 1 1 2 2 117 16 98 80 80 -5272.00000 2.57485 4.89549e-005 257 1490 0.00 -0.00 -0.00 5.09 8.18 -32.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 25 3 1 1 0 2 118 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -32.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 25 3 1 1 3 2 119 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -32.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 26 1 1 1 1 2 120 16 98 80 80 -5272.00000 2.57485 4.89549e-005 231 906 0.00 -0.00 -0.00 5.09 8.18 -35.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 26 1 1 1 2 2 121 16 98 80 80 -5272.00000 2.57485 4.89549e-005 671 1484 0.00 -0.00 -0.00 5.09 8.18 -35.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 26 1 1 1 0 2 122 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -35.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 26 1 1 1 3 2 123 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -35.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 26 2 1 1 1 2 124 16 98 80 80 -5272.00000 2.57485 4.89549e-005 343 1473 0.00 -0.00 -0.00 5.09 8.18 -35.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 26 2 1 1 2 2 125 16 98 80 80 -5272.00000 2.57485 4.89549e-005 540 1340 0.00 -0.00 -0.00 5.09 8.18 -35.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 26 2 1 1 0 2 126 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -35.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 26 2 1 1 3 2 127 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -35.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 26 3 1 1 1 2 128 16 98 80 80 -5272.00000 2.57485 4.89549e-005 262 1936 0.00 -0.00 -0.00 5.09 8.18 -35.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 26 3 1 1 2 2 129 16 98 80 80 -5272.00000 2.57485 4.89549e-005 318 1397 0.00 -0.00 -0.00 5.09 8.18 -35.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 26 3 1 1 0 2 130 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -35.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 26 3 1 1 3 2 131 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -35.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 27 1 1 1 1 2 132 16 98 80 80 -5272.00000 2.57485 4.89549e-005 182 845 0.00 -0.00 -0.00 5.09 8.18 -38.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 27 1 1 1 2 2 133 16 98 80 80 -5272.00000 2.57485 4.89549e-005 725 1562 0.00 -0.00 -0.00 5.09 8.18 -38.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 27 1 1 1 0 2 134 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -38.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 27 1 1 1 3 2 135 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -38.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 27 2 1 1 1 2 136 16 98 80 80 -5272.00000 2.57485 4.89549e-005 324 1312 0.00 -0.00 -0.00 5.09 8.18 -38.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 27 2 1 1 2 2 137 16 98 80 80 -5272.00000 2.57485 4.89549e-005 597 1455 0.00 -0.00 -0.00 5.09 8.18 -38.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 27 2 1 1 0 2 138 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -38.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 27 2 1 1 3 2 139 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -38.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 27 3 1 1 1 2 140 16 98 80 80 -5272.00000 2.57485 4.89549e-005 359 1765 0.00 -0.00 -0.00 5.09 8.18 -38.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 27 3 1 1 2 2 141 16 98 80 80 -5272.00000 2.57485 4.89549e-005 396 1509 0.00 -0.00 -0.00 5.09 8.18 -38.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 27 3 1 1 0 2 142 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -38.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 27 3 1 1 3 2 143 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -38.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 28 1 1 1 1 2 144 16 98 80 80 -5272.00000 2.57485 4.89549e-005 127 680 0.00 -0.00 -0.00 5.09 8.18 -41.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 28 1 1 1 2 2 145 16 98 80 80 -5272.00000 2.57485 4.89549e-005 655 1406 0.00 -0.00 -0.00 5.09 8.18 -41.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 28 1 1 1 0 2 146 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -41.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 28 1 1 1 3 2 147 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -41.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 28 2 1 1 1 2 148 16 98 80 80 -5272.00000 2.57485 4.89549e-005 210 990 0.00 -0.00 -0.00 5.09 8.18 -41.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 28 2 1 1 2 2 149 16 98 80 80 -5272.00000 2.57485 4.89549e-005 499 1244 0.00 -0.00 -0.00 5.09 8.18 -41.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 28 2 1 1 0 2 150 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -41.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 28 2 1 1 3 2 151 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -41.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 28 3 1 1 1 2 152 16 98 80 80 -5272.00000 2.57485 4.89549e-005 201 1473 0.00 -0.00 -0.00 5.09 8.18 -41.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 28 3 1 1 2 2 153 16 98 80 80 -5272.00000 2.57485 4.89549e-005 237 1263 0.00 -0.00 -0.00 5.09 8.18 -41.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 28 3 1 1 0 2 154 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -41.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 28 3 1 1 3 2 155 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -41.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 29 1 1 1 1 2 156 16 98 80 80 -5272.00000 2.57485 4.89549e-005 93 652 0.00 -0.00 -0.00 5.09 8.18 -44.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 29 1 1 1 2 2 157 16 98 80 80 -5272.00000 2.57485 4.89549e-005 335 802 0.00 -0.00 -0.00 5.09 8.18 -44.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 29 1 1 1 0 2 158 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -44.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 29 1 1 1 3 2 159 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -44.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 29 2 1 1 1 2 160 16 98 80 80 -5272.00000 2.57485 4.89549e-005 189 966 0.00 -0.00 -0.00 5.09 8.18 -44.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 29 2 1 1 2 2 161 16 98 80 80 -5272.00000 2.57485 4.89549e-005 218 744 0.00 -0.00 -0.00 5.09 8.18 -44.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 29 2 1 1 0 2 162 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -44.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 29 2 1 1 3 2 163 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -44.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 29 3 1 1 1 2 164 16 98 80 80 -5272.00000 2.57485 4.89549e-005 70 1222 0.00 -0.00 -0.00 5.09 8.18 -44.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 29 3 1 1 2 2 165 16 98 80 80 -5272.00000 2.57485 4.89549e-005 7 902 0.00 -0.00 -0.00 5.09 8.18 -44.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 29 3 1 1 0 2 166 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -44.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 29 3 1 1 3 2 167 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -44.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 30 1 1 1 1 2 168 16 98 80 80 -5272.00000 2.57485 4.89549e-005 85 568 0.00 -0.00 -0.00 5.09 8.18 -47.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 30 1 1 1 2 2 169 16 98 80 80 -5272.00000 2.57485 4.89549e-005 300 755 0.00 -0.00 -0.00 5.09 8.18 -47.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 30 1 1 1 0 2 170 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -47.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 30 1 1 1 3 2 171 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -47.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 30 2 1 1 1 2 172 16 98 80 80 -5272.00000 2.57485 4.89549e-005 178 829 0.00 -0.00 -0.00 5.09 8.18 -47.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 30 2 1 1 2 2 173 16 98 80 80 -5272.00000 2.57485 4.89549e-005 174 784 0.00 -0.00 -0.00 5.09 8.18 -47.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 30 2 1 1 0 2 174 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -47.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 30 2 1 1 3 2 175 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -47.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 30 3 1 1 1 2 176 16 98 80 80 -5272.00000 2.57485 4.89549e-005 80 1087 0.00 -0.00 -0.00 5.09 8.18 -47.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 30 3 1 1 2 2 177 16 98 80 80 -5272.00000 2.57485 4.89549e-005 44 825 0.00 -0.00 -0.00 5.09 8.18 -47.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 30 3 1 1 0 2 178 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -47.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 30 3 1 1 3 2 179 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -47.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 1 1 1 1 1 2 180 16 98 80 80 -5272.00000 2.57485 4.89549e-005 188 537 0.00 -0.00 -0.00 5.09 8.18 39.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 1 1 1 1 2 2 181 16 98 80 80 -5272.00000 2.57485 4.89549e-005 250 673 0.00 -0.00 -0.00 5.09 8.18 39.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 1 1 1 1 0 2 182 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 39.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 1 1 1 1 3 2 183 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 39.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 1 2 1 1 1 2 184 16 98 80 80 -5272.00000 2.57485 4.89549e-005 131 576 0.00 -0.00 -0.00 5.09 8.18 39.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 1 2 1 1 2 2 185 16 98 80 80 -5272.00000 2.57485 4.89549e-005 147 717 0.00 -0.00 -0.00 5.09 8.18 39.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 1 2 1 1 0 2 186 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 39.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 1 2 1 1 3 2 187 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 39.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 1 3 1 1 1 2 188 16 98 80 80 -5272.00000 2.57485 4.89549e-005 90 956 0.00 -0.00 -0.00 5.09 8.18 39.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 1 3 1 1 2 2 189 16 98 80 80 -5272.00000 2.57485 4.89549e-005 56 622 0.00 -0.00 -0.00 5.09 8.18 39.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 1 3 1 1 0 2 190 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 39.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 1 3 1 1 3 2 191 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 39.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 2 1 1 1 1 2 192 16 98 80 80 -5272.00000 2.57485 4.89549e-005 309 737 0.00 -0.00 -0.00 5.09 8.18 36.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 2 1 1 1 2 2 193 16 98 80 80 -5272.00000 2.57485 4.89549e-005 326 797 0.00 -0.00 -0.00 5.09 8.18 36.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 2 1 1 1 0 2 194 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 36.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 2 1 1 1 3 2 195 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 36.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 2 2 1 1 1 2 196 16 98 80 80 -5272.00000 2.57485 4.89549e-005 265 855 0.00 -0.00 -0.00 5.09 8.18 36.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 2 2 1 1 2 2 197 16 98 80 80 -5272.00000 2.57485 4.89549e-005 141 807 0.00 -0.00 -0.00 5.09 8.18 36.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 2 2 1 1 0 2 198 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 36.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 2 2 1 1 3 2 199 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 36.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 2 3 1 1 1 2 200 16 98 80 80 -5272.00000 2.57485 4.89549e-005 144 1083 0.00 -0.00 -0.00 5.09 8.18 36.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 2 3 1 1 2 2 201 16 98 80 80 -5272.00000 2.57485 4.89549e-005 47 666 0.00 -0.00 -0.00 5.09 8.18 36.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 2 3 1 1 0 2 202 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 36.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 2 3 1 1 3 2 203 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 36.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 3 1 1 1 1 2 204 16 98 80 80 -5272.00000 2.57485 4.89549e-005 460 1119 0.00 -0.00 -0.00 5.09 8.18 33.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 3 1 1 1 2 2 205 16 98 80 80 -5272.00000 2.57485 4.89549e-005 414 973 0.00 -0.00 -0.00 5.09 8.18 33.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 3 1 1 1 0 2 206 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 33.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 3 1 1 1 3 2 207 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 33.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 3 2 1 1 1 2 208 16 98 80 80 -5272.00000 2.57485 4.89549e-005 426 1228 0.00 -0.00 -0.00 5.09 8.18 33.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 3 2 1 1 2 2 209 16 98 80 80 -5272.00000 2.57485 4.89549e-005 101 956 0.00 -0.00 -0.00 5.09 8.18 33.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 3 2 1 1 0 2 210 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 33.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 3 2 1 1 3 2 211 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 33.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 3 3 1 1 1 2 212 16 98 80 80 -5272.00000 2.57485 4.89549e-005 344 1387 0.00 -0.00 -0.00 5.09 8.18 33.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 3 3 1 1 2 2 213 16 98 80 80 -5272.00000 2.57485 4.89549e-005 25 761 0.00 -0.00 -0.00 5.09 8.18 33.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 3 3 1 1 0 2 214 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 33.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 3 3 1 1 3 2 215 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 33.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 4 1 1 1 1 2 216 16 98 80 80 -5272.00000 2.57485 4.89549e-005 429 1061 0.00 -0.00 -0.00 5.09 8.18 30.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 4 1 1 1 2 2 217 16 98 80 80 -5272.00000 2.57485 4.89549e-005 401 1143 0.00 -0.00 -0.00 5.09 8.18 30.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 4 1 1 1 0 2 218 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 30.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 4 1 1 1 3 2 219 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 30.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 4 2 1 1 1 2 220 16 98 80 80 -5272.00000 2.57485 4.89549e-005 460 1151 0.00 -0.00 -0.00 5.09 8.18 30.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 4 2 1 1 2 2 221 16 98 80 80 -5272.00000 2.57485 4.89549e-005 187 1033 0.00 -0.00 -0.00 5.09 8.18 30.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 4 2 1 1 0 2 222 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 30.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 4 2 1 1 3 2 223 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 30.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 4 3 1 1 1 2 224 16 98 80 80 -5272.00000 2.57485 4.89549e-005 419 1413 0.00 -0.00 -0.00 5.09 8.18 30.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 4 3 1 1 2 2 225 16 98 80 80 -5272.00000 2.57485 4.89549e-005 22 748 0.00 -0.00 -0.00 5.09 8.18 30.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 4 3 1 1 0 2 226 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 30.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 4 3 1 1 3 2 227 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 30.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 5 1 1 1 1 2 228 16 98 80 80 -5272.00000 2.57485 4.89549e-005 438 1061 0.00 -0.00 -0.00 5.09 8.18 27.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 5 1 1 1 2 2 229 16 98 80 80 -5272.00000 2.57485 4.89549e-005 408 1103 0.00 -0.00 -0.00 5.09 8.18 27.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 5 1 1 1 0 2 230 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 27.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 5 1 1 1 3 2 231 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 27.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 5 2 1 1 1 2 232 16 98 80 80 -5272.00000 2.57485 4.89549e-005 472 1151 0.00 -0.00 -0.00 5.09 8.18 27.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 5 2 1 1 2 2 233 16 98 80 80 -5272.00000 2.57485 4.89549e-005 194 1099 0.00 -0.00 -0.00 5.09 8.18 27.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 5 2 1 1 0 2 234 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 27.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 5 2 1 1 3 2 235 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 27.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 5 3 1 1 1 2 236 16 98 80 80 -5272.00000 2.57485 4.89549e-005 372 1505 0.00 -0.00 -0.00 5.09 8.18 27.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 5 3 1 1 2 2 237 16 98 80 80 -5272.00000 2.57485 4.89549e-005 -39 826 0.00 -0.00 -0.00 5.09 8.18 27.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 5 3 1 1 0 2 238 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 27.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 5 3 1 1 3 2 239 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 27.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 6 1 1 1 1 2 240 16 98 80 80 -5272.00000 2.57485 4.89549e-005 408 926 0.00 -0.00 -0.00 5.09 8.18 24.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 6 1 1 1 2 2 241 16 98 80 80 -5272.00000 2.57485 4.89549e-005 424 1138 0.00 -0.00 -0.00 5.09 8.18 24.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 6 1 1 1 0 2 242 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 24.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 6 1 1 1 3 2 243 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 24.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 6 2 1 1 1 2 244 16 98 80 80 -5272.00000 2.57485 4.89549e-005 503 1123 0.00 -0.00 -0.00 5.09 8.18 24.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 6 2 1 1 2 2 245 16 98 80 80 -5272.00000 2.57485 4.89549e-005 160 1108 0.00 -0.00 -0.00 5.09 8.18 24.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 6 2 1 1 0 2 246 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 24.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 6 2 1 1 3 2 247 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 24.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 6 3 1 1 1 2 248 16 98 80 80 -5272.00000 2.57485 4.89549e-005 373 1493 0.00 -0.00 -0.00 5.09 8.18 24.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 6 3 1 1 2 2 249 16 98 80 80 -5272.00000 2.57485 4.89549e-005 -27 963 0.00 -0.00 -0.00 5.09 8.18 24.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 6 3 1 1 0 2 250 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 24.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 6 3 1 1 3 2 251 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 24.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 7 1 1 1 1 2 252 16 98 80 80 -5272.00000 2.57485 4.89549e-005 396 996 0.00 -0.00 -0.00 5.09 8.18 21.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 7 1 1 1 2 2 253 16 98 80 80 -5272.00000 2.57485 4.89549e-005 485 1233 0.00 -0.00 -0.00 5.09 8.18 21.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 7 1 1 1 0 2 254 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 21.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 7 1 1 1 3 2 255 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 21.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 7 2 1 1 1 2 256 16 98 80 80 -5272.00000 2.57485 4.89549e-005 505 1222 0.00 -0.00 -0.00 5.09 8.18 21.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 7 2 1 1 2 2 257 16 98 80 80 -5272.00000 2.57485 4.89549e-005 170 1109 0.00 -0.00 -0.00 5.09 8.18 21.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 7 2 1 1 0 2 258 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 21.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 7 2 1 1 3 2 259 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 21.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 7 3 1 1 1 2 260 16 98 80 80 -5272.00000 2.57485 4.89549e-005 418 1513 0.00 -0.00 -0.00 5.09 8.18 21.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 7 3 1 1 2 2 261 16 98 80 80 -5272.00000 2.57485 4.89549e-005 30 1005 0.00 -0.00 -0.00 5.09 8.18 21.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 7 3 1 1 0 2 262 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 21.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 7 3 1 1 3 2 263 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 21.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 8 1 1 1 1 2 264 16 98 80 80 -5272.00000 2.57485 4.89549e-005 392 904 0.00 -0.00 -0.00 5.09 8.18 18.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 8 1 1 1 2 2 265 16 98 80 80 -5272.00000 2.57485 4.89549e-005 425 1277 0.00 -0.00 -0.00 5.09 8.18 18.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 8 1 1 1 0 2 266 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 18.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 8 1 1 1 3 2 267 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 18.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 8 2 1 1 1 2 268 16 98 80 80 -5272.00000 2.57485 4.89549e-005 460 1091 0.00 -0.00 -0.00 5.09 8.18 18.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 8 2 1 1 2 2 269 16 98 80 80 -5272.00000 2.57485 4.89549e-005 134 1002 0.00 -0.00 -0.00 5.09 8.18 18.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 8 2 1 1 0 2 270 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 18.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 8 2 1 1 3 2 271 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 18.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 8 3 1 1 1 2 272 16 98 80 80 -5272.00000 2.57485 4.89549e-005 350 1413 0.00 -0.00 -0.00 5.09 8.18 18.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 8 3 1 1 2 2 273 16 98 80 80 -5272.00000 2.57485 4.89549e-005 40 986 0.00 -0.00 -0.00 5.09 8.18 18.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 8 3 1 1 0 2 274 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 18.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 8 3 1 1 3 2 275 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 18.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 9 1 1 1 1 2 276 16 98 80 80 -5272.00000 2.57485 4.89549e-005 391 984 0.00 -0.00 -0.00 5.09 8.18 15.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 9 1 1 1 2 2 277 16 98 80 80 -5272.00000 2.57485 4.89549e-005 264 1039 0.00 -0.00 -0.00 5.09 8.18 15.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 9 1 1 1 0 2 278 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 15.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 9 1 1 1 3 2 279 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 15.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 9 2 1 1 1 2 280 16 98 80 80 -5272.00000 2.57485 4.89549e-005 354 936 0.00 -0.00 -0.00 5.09 8.18 15.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 9 2 1 1 2 2 281 16 98 80 80 -5272.00000 2.57485 4.89549e-005 120 1022 0.00 -0.00 -0.00 5.09 8.18 15.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 9 2 1 1 0 2 282 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 15.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 9 2 1 1 3 2 283 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 15.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 9 3 1 1 1 2 284 16 98 80 80 -5272.00000 2.57485 4.89549e-005 238 1149 0.00 -0.00 -0.00 5.09 8.18 15.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 9 3 1 1 2 2 285 16 98 80 80 -5272.00000 2.57485 4.89549e-005 40 962 0.00 -0.00 -0.00 5.09 8.18 15.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 9 3 1 1 0 2 286 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 15.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 9 3 1 1 3 2 287 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 15.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 10 1 1 1 1 2 288 16 98 80 80 -5272.00000 2.57485 4.89549e-005 357 948 0.00 -0.00 -0.00 5.09 8.18 12.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 10 1 1 1 2 2 289 16 98 80 80 -5272.00000 2.57485 4.89549e-005 243 923 0.00 -0.00 -0.00 5.09 8.18 12.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 10 1 1 1 0 2 290 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 12.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 10 1 1 1 3 2 291 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 12.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 10 2 1 1 1 2 292 16 98 80 80 -5272.00000 2.57485 4.89549e-005 321 888 0.00 -0.00 -0.00 5.09 8.18 12.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 10 2 1 1 2 2 293 16 98 80 80 -5272.00000 2.57485 4.89549e-005 107 1025 0.00 -0.00 -0.00 5.09 8.18 12.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 10 2 1 1 0 2 294 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 12.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 10 2 1 1 3 2 295 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 12.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 10 3 1 1 1 2 296 16 98 80 80 -5272.00000 2.57485 4.89549e-005 225 1071 0.00 -0.00 -0.00 5.09 8.18 12.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 10 3 1 1 2 2 297 16 98 80 80 -5272.00000 2.57485 4.89549e-005 24 975 0.00 -0.00 -0.00 5.09 8.18 12.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 10 3 1 1 0 2 298 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 12.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 10 3 1 1 3 2 299 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 12.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 11 1 1 1 1 2 300 16 98 80 80 -5272.00000 2.57485 4.89549e-005 -135 700 0.00 -0.00 -0.00 5.09 8.18 9.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 11 1 1 1 2 2 301 16 98 80 80 -5272.00000 2.57485 4.89549e-005 -497 1338 0.00 -0.00 -0.00 5.09 8.18 9.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 11 1 1 1 0 2 302 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 9.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 11 1 1 1 3 2 303 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 9.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 11 2 1 1 1 2 304 16 98 80 80 -5272.00000 2.57485 4.89549e-005 -97 1371 0.00 -0.00 -0.00 5.09 8.18 9.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 11 2 1 1 2 2 305 16 98 80 80 -5272.00000 2.57485 4.89549e-005 -419 1299 0.00 -0.00 -0.00 5.09 8.18 9.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 11 2 1 1 0 2 306 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 9.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 11 2 1 1 3 2 307 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 9.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 11 3 1 1 1 2 308 16 98 80 80 -5272.00000 2.57485 4.89549e-005 -76 2013 0.00 -0.00 -0.00 5.09 8.18 9.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 11 3 1 1 2 2 309 16 98 80 80 -5272.00000 2.57485 4.89549e-005 -261 1303 0.00 -0.00 -0.00 5.09 8.18 9.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 11 3 1 1 0 2 310 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 9.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 11 3 1 1 3 2 311 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 9.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 12 1 1 1 1 2 312 16 98 80 80 -5272.00000 2.57485 4.89549e-005 -273 1000 0.00 -0.00 -0.00 5.09 8.18 6.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 12 1 1 1 2 2 313 16 98 80 80 -5272.00000 2.57485 4.89549e-005 -628 1561 0.00 -0.00 -0.00 5.09 8.18 6.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 12 1 1 1 0 2 314 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 6.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 12 1 1 1 3 2 315 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 6.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 12 2 1 1 1 2 316 16 98 80 80 -5272.00000 2.57485 4.89549e-005 -151 1475 0.00 -0.00 -0.00 5.09 8.18 6.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 12 2 1 1 2 2 317 16 98 80 80 -5272.00000 2.57485 4.89549e-005 -431 1479 0.00 -0.00 -0.00 5.09 8.18 6.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 12 2 1 1 0 2 318 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 6.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 12 2 1 1 3 2 319 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 6.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 12 3 1 1 1 2 320 16 98 80 80 -5272.00000 2.57485 4.89549e-005 -83 2180 0.00 -0.00 -0.00 5.09 8.18 6.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 12 3 1 1 2 2 321 16 98 80 80 -5272.00000 2.57485 4.89549e-005 -251 1367 0.00 -0.00 -0.00 5.09 8.18 6.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 12 3 1 1 0 2 322 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 6.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 12 3 1 1 3 2 323 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 6.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 13 1 1 1 1 2 324 16 98 80 80 -5272.00000 2.57485 4.89549e-005 -444 1463 0.00 -0.00 -0.00 5.09 8.18 3.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 13 1 1 1 2 2 325 16 98 80 80 -5272.00000 2.57485 4.89549e-005 -715 1858 0.00 -0.00 -0.00 5.09 8.18 3.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 13 1 1 1 0 2 326 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 3.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 13 1 1 1 3 2 327 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 3.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 13 2 1 1 1 2 328 16 98 80 80 -5272.00000 2.57485 4.89549e-005 -339 2077 0.00 -0.00 -0.00 5.09 8.18 3.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 13 2 1 1 2 2 329 16 98 80 80 -5272.00000 2.57485 4.89549e-005 -411 1757 0.00 -0.00 -0.00 5.09 8.18 3.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 13 2 1 1 0 2 330 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 3.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 13 2 1 1 3 2 331 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 3.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 13 3 1 1 1 2 332 16 98 80 80 -5272.00000 2.57485 4.89549e-005 -318 2606 0.00 -0.00 -0.00 5.09 8.18 3.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 13 3 1 1 2 2 333 16 98 80 80 -5272.00000 2.57485 4.89549e-005 -203 1398 0.00 -0.00 -0.00 5.09 8.18 3.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 13 3 1 1 0 2 334 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 3.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 13 3 1 1 3 2 335 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 3.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 14 1 1 1 1 2 336 16 98 80 80 -5272.00000 2.57485 4.89549e-005 -841 2182 0.00 -0.00 -0.00 5.09 8.18 0.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 14 1 1 1 2 2 337 16 98 80 80 -5272.00000 2.57485 4.89549e-005 -108 1068 0.00 -0.00 -0.00 5.09 8.18 0.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 14 1 1 1 0 2 338 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 0.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 14 1 1 1 3 2 339 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 0.29 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 14 2 1 1 1 2 340 16 98 80 80 -5272.00000 2.57485 4.89549e-005 -671 2077 0.00 -0.00 -0.00 5.09 8.18 0.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 14 2 1 1 2 2 341 16 98 80 80 -5272.00000 2.57485 4.89549e-005 4 1854 0.00 -0.00 -0.00 5.09 8.18 0.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 14 2 1 1 0 2 342 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 0.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 14 2 1 1 3 2 343 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 0.29 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 14 3 1 1 1 2 344 16 98 80 80 -5272.00000 2.57485 4.89549e-005 -412 2149 0.00 -0.00 -0.00 5.09 8.18 0.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 14 3 1 1 2 2 345 16 98 80 80 -5272.00000 2.57485 4.89549e-005 195 2602 0.00 -0.00 -0.00 5.09 8.18 0.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 14 3 1 1 0 2 346 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 0.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 14 3 1 1 3 2 347 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 0.29 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 15 1 1 1 1 2 348 16 98 80 80 -5272.00000 2.57485 4.89549e-005 -874 2258 0.00 -0.00 -0.00 5.09 8.18 -2.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 15 1 1 1 2 2 349 16 98 80 80 -5272.00000 2.57485 4.89549e-005 -44 1176 0.00 -0.00 -0.00 5.09 8.18 -2.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 15 1 1 1 0 2 350 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -2.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 15 1 1 1 3 2 351 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -2.71 3.000 0.000 0 2 0 2 2.800 2.800 1.29 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 15 2 1 1 1 2 352 16 98 80 80 -5272.00000 2.57485 4.89549e-005 -695 2019 0.00 -0.00 -0.00 5.09 8.18 -2.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 15 2 1 1 2 2 353 16 98 80 80 -5272.00000 2.57485 4.89549e-005 321 1543 0.00 -0.00 -0.00 5.09 8.18 -2.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 15 2 1 1 0 2 354 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -2.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 15 2 1 1 3 2 355 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -2.71 3.000 0.000 0 2 0 2 2.800 2.800 3.28 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 15 3 1 1 1 2 356 16 98 80 80 -5272.00000 2.57485 4.89549e-005 -375 2168 0.00 -0.00 -0.00 5.09 8.18 -2.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 15 3 1 1 2 2 357 16 98 80 80 -5272.00000 2.57485 4.89549e-005 379 2546 0.00 -0.00 -0.00 5.09 8.18 -2.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 15 3 1 1 0 2 358 16 98 80 80 0.00000 1.28767 9.78947e-005 1070 1860 0.00 -0.00 -0.00 5.09 8.18 -2.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - 15 3 1 1 3 2 359 16 98 80 80 -3142.00000 1.53455 6.51740e+002 0 6284 0.00 -0.00 -0.00 5.09 8.18 -2.71 3.000 0.000 0 2 0 2 2.800 2.800 5.27 0.00 0.00 0.00 1 8.00 0 0 0 225 0.0 1 1 7 0 0.000 0.000 0.000 1 - -# === END OF DATA DESCRIPTION FILE =============================================== diff --git a/nibabel/tests/data/T1_dual_echo.PAR b/nibabel/tests/data/T1_dual_echo.PAR deleted file mode 100644 index da128ae71a..0000000000 --- a/nibabel/tests/data/T1_dual_echo.PAR +++ /dev/null @@ -1,462 +0,0 @@ -# === DATA DESCRIPTION FILE ====================================================== -# -# CAUTION - Investigational device. -# Limited by Federal Law to investigational use. -# -# Dataset name: E:\\Export\\T1_dual_echo.PAR -# -# CLINICAL TRYOUT Research image export tool V4.2 -# -# === GENERAL INFORMATION ======================================================== -# -. Patient name : anon -. Examination name : anon -. Protocol name : anon -. Examination date/time : anon -. Series Type : Image MRSERIES -. Acquisition nr : 5 -. Reconstruction nr : 1 -. Scan Duration [sec] : 200 -. Max. number of cardiac phases : 1 -. Max. number of echoes : 2 -. Max. number of slices/locations : 180 -. Max. number of dynamics : 1 -. Max. number of mixes : 1 -. Patient position : Head First Supine -. Preparation direction : Anterior-Posterior -. Technique : T1TFE -. Scan resolution (x, y) : 256 256 -. Scan mode : 3D -. Repetition time [ms] : 8.133 -. FOV (ap,fh,rl) [mm] : 224.000 256.000 180.000 -. Water Fat shift [pixels] : 0.854 -. Angulation midslice(ap,fh,rl)[degr]: 0.000 0.000 0.000 -. Off Centre midslice(ap,fh,rl) [mm] : -5.055 12.640 -2.405 -. Flow compensation <0=no 1=yes> ? : 0 -. Presaturation <0=no 1=yes> ? : 0 -. Phase encoding velocity [cm/sec] : 0.000000 0.000000 0.000000 -. MTC <0=no 1=yes> ? : 0 -. SPIR <0=no 1=yes> ? : 0 -. EPI factor <0,1=no EPI> : 1 -. Dynamic scan <0=no 1=yes> ? : 0 -. Diffusion <0=no 1=yes> ? : 0 -. Diffusion echo time [ms] : 0.0000 -. Max. number of diffusion values : 1 -. Max. number of gradient orients : 1 -. Number of label types <0=no ASL> : 0 -# -# === PIXEL VALUES ============================================================= -# PV = pixel value in REC file, FP = floating point value, DV = displayed value on console -# RS = rescale slope, RI = rescale intercept, SS = scale slope -# DV = PV * RS + RI FP = DV / (RS * SS) -# -# === IMAGE INFORMATION DEFINITION ============================================= -# The rest of this file contains ONE line per image, this line contains the following information: -# -# slice number (integer) -# echo number (integer) -# dynamic scan number (integer) -# cardiac phase number (integer) -# image_type_mr (integer) -# scanning sequence (integer) -# index in REC file (in images) (integer) -# image pixel size (in bits) (integer) -# scan percentage (integer) -# recon resolution (x y) (2*integer) -# rescale intercept (float) -# rescale slope (float) -# scale slope (float) -# window center (integer) -# window width (integer) -# image angulation (ap,fh,rl in degrees ) (3*float) -# image offcentre (ap,fh,rl in mm ) (3*float) -# slice thickness (in mm ) (float) -# slice gap (in mm ) (float) -# image_display_orientation (integer) -# slice orientation ( TRA/SAG/COR ) (integer) -# fmri_status_indication (integer) -# image_type_ed_es (end diast/end syst) (integer) -# pixel spacing (x,y) (in mm) (2*float) -# echo_time (float) -# dyn_scan_begin_time (float) -# trigger_time (float) -# diffusion_b_factor (float) -# number of averages (integer) -# image_flip_angle (in degrees) (float) -# cardiac frequency (bpm) (integer) -# minimum RR-interval (in ms) (integer) -# maximum RR-interval (in ms) (integer) -# TURBO factor <0=no turbo> (integer) -# Inversion delay (in ms) (float) -# diffusion b value number (imagekey!) (integer) -# gradient orientation number (imagekey!) (integer) -# contrast type (string) -# diffusion anisotropy type (string) -# diffusion (ap, fh, rl) (3*float) -# label type (ASL) (imagekey!) (integer) -# -# === IMAGE INFORMATION ========================================================== -# sl ec dyn ph ty idx pix scan% rec size (re)scale window angulation offcentre thick gap info spacing echo dtime ttime diff avg flip freq RR-int turbo delay b grad cont anis diffusion L.ty - - 54 1 1 1 0 2 0 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 34.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 91 1 1 1 0 2 1 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -2.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 90 1 1 1 0 2 2 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -1.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 89 1 1 1 0 2 3 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -0.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 92 1 1 1 0 2 4 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -3.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 4 2 1 1 0 2 5 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 84.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 55 1 1 1 0 2 6 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 33.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -175 2 1 1 0 2 7 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -86.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -174 2 1 1 0 2 8 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -85.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -173 2 1 1 0 2 9 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -84.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 88 1 1 1 0 2 10 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 0.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 56 1 1 1 0 2 11 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 32.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 5 2 1 1 0 2 12 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 83.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 94 1 1 1 0 2 13 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -5.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -172 2 1 1 0 2 14 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -83.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 1 1 1 1 0 2 15 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 87.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 57 1 1 1 0 2 16 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 31.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 87 1 1 1 0 2 17 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 1.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 53 1 1 1 0 2 18 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 35.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 86 1 1 1 0 2 19 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 2.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 6 2 1 1 0 2 20 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 82.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 2 1 1 1 0 2 21 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 86.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 3 2 1 1 0 2 22 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 85.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -171 2 1 1 0 2 23 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -82.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 58 1 1 1 0 2 24 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 30.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 3 1 1 1 0 2 25 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 85.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -177 2 1 1 0 2 26 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -88.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 7 2 1 1 0 2 27 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 81.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -170 2 1 1 0 2 28 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -81.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 4 1 1 1 0 2 29 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 84.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 59 1 1 1 0 2 30 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 29.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 5 1 1 1 0 2 31 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 83.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 93 1 1 1 0 2 32 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -4.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 6 1 1 1 0 2 33 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 82.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 8 2 1 1 0 2 34 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 80.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 7 1 1 1 0 2 35 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 81.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -176 2 1 1 0 2 36 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -87.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 85 1 1 1 0 2 37 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 3.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 60 1 1 1 0 2 38 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 28.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 9 2 1 1 0 2 39 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 79.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 8 1 1 1 0 2 40 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 80.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 84 1 1 1 0 2 41 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 4.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -179 2 1 1 0 2 42 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -90.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 61 1 1 1 0 2 43 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 27.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 83 1 1 1 0 2 44 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 5.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 9 1 1 1 0 2 45 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 79.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -178 2 1 1 0 2 46 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -89.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 82 1 1 1 0 2 47 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 6.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 10 2 1 1 0 2 48 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 78.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 10 1 1 1 0 2 49 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 78.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 81 1 1 1 0 2 50 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 7.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 95 1 1 1 0 2 51 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -6.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 11 1 1 1 0 2 52 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 77.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 62 1 1 1 0 2 53 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 26.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 11 2 1 1 0 2 54 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 77.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 2 2 1 1 0 2 55 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 86.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 13 1 1 1 0 2 56 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 75.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 12 1 1 1 0 2 57 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 76.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -169 2 1 1 0 2 58 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -80.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 12 2 1 1 0 2 59 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 76.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 63 1 1 1 0 2 60 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 25.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -180 2 1 1 0 2 61 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -91.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 14 1 1 1 0 2 62 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 74.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -168 2 1 1 0 2 63 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -79.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 80 1 1 1 0 2 64 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 8.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 52 1 1 1 0 2 65 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 36.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 13 2 1 1 0 2 66 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 75.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 64 1 1 1 0 2 67 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 24.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 79 1 1 1 0 2 68 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 9.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 15 1 1 1 0 2 69 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 73.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 1 2 1 1 0 2 70 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 87.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -167 2 1 1 0 2 71 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -78.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 14 2 1 1 0 2 72 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 74.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 78 1 1 1 0 2 73 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 10.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 65 1 1 1 0 2 74 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 23.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -166 2 1 1 0 2 75 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -77.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 97 1 1 1 0 2 76 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -8.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 16 1 1 1 0 2 77 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 72.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 77 1 1 1 0 2 78 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 11.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 66 1 1 1 0 2 79 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 22.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 15 2 1 1 0 2 80 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 73.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 17 1 1 1 0 2 81 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 71.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 96 1 1 1 0 2 82 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -7.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 67 1 1 1 0 2 83 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 21.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 16 2 1 1 0 2 84 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 72.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 76 1 1 1 0 2 85 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 12.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -165 2 1 1 0 2 86 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -76.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -164 2 1 1 0 2 87 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -75.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 68 1 1 1 0 2 88 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 20.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 98 1 1 1 0 2 89 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -9.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 18 1 1 1 0 2 90 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 70.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -163 2 1 1 0 2 91 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -74.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 17 2 1 1 0 2 92 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 71.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -162 2 1 1 0 2 93 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -73.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 99 1 1 1 0 2 94 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -10.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 69 1 1 1 0 2 95 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 19.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 75 1 1 1 0 2 96 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 13.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 18 2 1 1 0 2 97 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 70.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -100 1 1 1 0 2 98 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -11.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -161 2 1 1 0 2 99 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -72.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 19 1 1 1 0 2 100 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 69.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 70 1 1 1 0 2 101 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 18.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 19 2 1 1 0 2 102 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 69.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -160 2 1 1 0 2 103 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -71.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -101 1 1 1 0 2 104 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -12.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 74 1 1 1 0 2 105 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 14.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -159 2 1 1 0 2 106 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -70.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 71 1 1 1 0 2 107 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 17.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 20 2 1 1 0 2 108 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 68.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 51 1 1 1 0 2 109 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 37.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -158 2 1 1 0 2 110 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -69.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -156 2 1 1 0 2 111 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -67.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -157 2 1 1 0 2 112 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -68.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -102 1 1 1 0 2 113 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -13.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 21 2 1 1 0 2 114 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 67.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -155 2 1 1 0 2 115 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -66.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 72 1 1 1 0 2 116 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 16.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -103 1 1 1 0 2 117 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -14.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -154 2 1 1 0 2 118 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -65.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 73 1 1 1 0 2 119 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 15.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 22 2 1 1 0 2 120 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 66.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -152 2 1 1 0 2 121 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -63.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -153 2 1 1 0 2 122 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -64.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -105 1 1 1 0 2 123 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -16.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 20 1 1 1 0 2 124 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 68.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -104 1 1 1 0 2 125 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -15.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -150 2 1 1 0 2 126 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -61.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -151 2 1 1 0 2 127 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -62.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -149 2 1 1 0 2 128 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -60.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 23 2 1 1 0 2 129 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 65.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -106 1 1 1 0 2 130 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -17.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 21 1 1 1 0 2 131 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 67.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -148 2 1 1 0 2 132 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -59.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 23 1 1 1 0 2 133 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 65.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 22 1 1 1 0 2 134 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 66.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 24 2 1 1 0 2 135 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 64.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 24 1 1 1 0 2 136 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 64.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 25 1 1 1 0 2 137 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 63.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 50 1 1 1 0 2 138 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 38.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -180 1 1 1 0 2 139 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -91.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 25 2 1 1 0 2 140 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 63.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 26 1 1 1 0 2 141 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 62.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 27 1 1 1 0 2 142 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 61.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -107 1 1 1 0 2 143 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -18.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -147 2 1 1 0 2 144 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -58.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -146 2 1 1 0 2 145 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -57.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 28 1 1 1 0 2 146 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 60.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 26 2 1 1 0 2 147 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 62.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -145 2 1 1 0 2 148 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -56.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 29 1 1 1 0 2 149 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 59.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -108 1 1 1 0 2 150 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -19.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 30 1 1 1 0 2 151 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 58.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -144 2 1 1 0 2 152 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -55.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 27 2 1 1 0 2 153 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 61.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 31 1 1 1 0 2 154 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 57.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -110 1 1 1 0 2 155 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -21.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -179 1 1 1 0 2 156 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -90.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 32 1 1 1 0 2 157 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 56.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -143 2 1 1 0 2 158 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -54.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 28 2 1 1 0 2 159 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 60.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -178 1 1 1 0 2 160 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -89.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -109 1 1 1 0 2 161 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -20.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -177 1 1 1 0 2 162 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -88.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 33 1 1 1 0 2 163 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 55.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -142 2 1 1 0 2 164 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -53.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 29 2 1 1 0 2 165 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 59.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 49 1 1 1 0 2 166 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 39.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -141 2 1 1 0 2 167 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -52.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -175 1 1 1 0 2 168 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -86.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -176 1 1 1 0 2 169 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -87.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -174 1 1 1 0 2 170 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -85.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -111 1 1 1 0 2 171 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -22.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 30 2 1 1 0 2 172 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 58.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 34 1 1 1 0 2 173 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 54.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -140 2 1 1 0 2 174 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -51.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -173 1 1 1 0 2 175 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -84.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -171 1 1 1 0 2 176 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -82.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -114 1 1 1 0 2 177 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -25.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 31 2 1 1 0 2 178 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 57.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -170 1 1 1 0 2 179 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -81.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -138 2 1 1 0 2 180 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -49.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -139 2 1 1 0 2 181 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -50.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -112 1 1 1 0 2 182 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -23.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 35 1 1 1 0 2 183 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 53.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 32 2 1 1 0 2 184 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 56.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -136 2 1 1 0 2 185 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -47.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -137 2 1 1 0 2 186 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -48.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 48 1 1 1 0 2 187 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 40.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -135 2 1 1 0 2 188 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -46.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -167 1 1 1 0 2 189 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -78.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 33 2 1 1 0 2 190 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 55.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -134 2 1 1 0 2 191 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -45.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -133 2 1 1 0 2 192 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -44.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -169 1 1 1 0 2 193 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -80.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -132 2 1 1 0 2 194 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -43.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -115 1 1 1 0 2 195 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -26.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -131 2 1 1 0 2 196 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -42.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -166 1 1 1 0 2 197 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -77.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 34 2 1 1 0 2 198 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 54.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -113 1 1 1 0 2 199 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -24.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -129 2 1 1 0 2 200 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -40.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -130 2 1 1 0 2 201 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -41.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -127 2 1 1 0 2 202 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -38.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -128 2 1 1 0 2 203 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -39.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -116 1 1 1 0 2 204 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -27.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 35 2 1 1 0 2 205 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 53.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -126 2 1 1 0 2 206 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -37.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -168 1 1 1 0 2 207 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -79.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -125 2 1 1 0 2 208 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -36.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -124 2 1 1 0 2 209 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -35.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 47 1 1 1 0 2 210 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 41.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -172 1 1 1 0 2 211 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -83.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 36 2 1 1 0 2 212 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 52.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -123 2 1 1 0 2 213 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -34.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -118 1 1 1 0 2 214 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -29.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -165 1 1 1 0 2 215 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -76.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -122 2 1 1 0 2 216 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -33.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 36 1 1 1 0 2 217 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 52.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -163 1 1 1 0 2 218 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -74.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -117 1 1 1 0 2 219 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -28.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -121 2 1 1 0 2 220 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -32.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 37 2 1 1 0 2 221 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 51.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 37 1 1 1 0 2 222 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 51.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -161 1 1 1 0 2 223 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -72.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -164 1 1 1 0 2 224 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -75.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -119 1 1 1 0 2 225 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -30.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -162 1 1 1 0 2 226 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -73.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -120 2 1 1 0 2 227 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -31.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -159 1 1 1 0 2 228 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -70.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 38 2 1 1 0 2 229 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 50.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -119 2 1 1 0 2 230 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -30.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 46 1 1 1 0 2 231 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 42.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -158 1 1 1 0 2 232 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -69.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -118 2 1 1 0 2 233 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -29.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 39 2 1 1 0 2 234 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 49.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -160 1 1 1 0 2 235 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -71.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -120 1 1 1 0 2 236 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -31.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -117 2 1 1 0 2 237 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -28.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -115 2 1 1 0 2 238 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -26.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -116 2 1 1 0 2 239 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -27.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -114 2 1 1 0 2 240 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -25.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 40 2 1 1 0 2 241 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 48.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -157 1 1 1 0 2 242 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -68.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -122 1 1 1 0 2 243 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -33.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -113 2 1 1 0 2 244 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -24.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -111 2 1 1 0 2 245 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -22.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -112 2 1 1 0 2 246 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -23.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 41 2 1 1 0 2 247 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 47.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -110 2 1 1 0 2 248 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -21.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -121 1 1 1 0 2 249 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -32.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 38 1 1 1 0 2 250 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 50.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -108 2 1 1 0 2 251 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -19.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -156 1 1 1 0 2 252 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -67.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -109 2 1 1 0 2 253 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -20.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -124 1 1 1 0 2 254 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -35.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 42 2 1 1 0 2 255 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 46.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -105 2 1 1 0 2 256 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -16.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -107 2 1 1 0 2 257 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -18.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 45 1 1 1 0 2 258 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 43.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -106 2 1 1 0 2 259 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -17.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -155 1 1 1 0 2 260 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -66.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -152 1 1 1 0 2 261 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -63.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 43 2 1 1 0 2 262 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 45.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -123 1 1 1 0 2 263 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -34.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -151 1 1 1 0 2 264 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -62.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -104 2 1 1 0 2 265 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -15.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -153 1 1 1 0 2 266 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -64.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -103 2 1 1 0 2 267 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -14.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 44 2 1 1 0 2 268 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 44.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -126 1 1 1 0 2 269 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -37.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -102 2 1 1 0 2 270 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -13.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -101 2 1 1 0 2 271 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -12.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -100 2 1 1 0 2 272 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -11.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 99 2 1 1 0 2 273 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -10.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 45 2 1 1 0 2 274 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 43.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -125 1 1 1 0 2 275 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -36.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -154 1 1 1 0 2 276 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -65.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 98 2 1 1 0 2 277 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -9.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 39 1 1 1 0 2 278 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 49.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 97 2 1 1 0 2 279 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -8.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -149 1 1 1 0 2 280 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -60.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 46 2 1 1 0 2 281 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 42.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 44 1 1 1 0 2 282 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 44.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -148 1 1 1 0 2 283 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -59.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 96 2 1 1 0 2 284 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -7.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -150 1 1 1 0 2 285 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -61.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -147 1 1 1 0 2 286 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -58.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -127 1 1 1 0 2 287 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -38.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 47 2 1 1 0 2 288 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 41.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 95 2 1 1 0 2 289 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -6.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 94 2 1 1 0 2 290 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -5.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 40 1 1 1 0 2 291 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 48.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 93 2 1 1 0 2 292 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -4.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -130 1 1 1 0 2 293 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -41.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -145 1 1 1 0 2 294 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -56.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -128 1 1 1 0 2 295 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -39.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 48 2 1 1 0 2 296 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 40.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 92 2 1 1 0 2 297 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -3.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -146 1 1 1 0 2 298 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -57.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 91 2 1 1 0 2 299 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -2.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -144 1 1 1 0 2 300 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -55.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 49 2 1 1 0 2 301 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 39.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -141 1 1 1 0 2 302 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -52.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -129 1 1 1 0 2 303 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -40.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -143 1 1 1 0 2 304 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -54.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -142 1 1 1 0 2 305 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -53.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 90 2 1 1 0 2 306 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -1.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 50 2 1 1 0 2 307 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 38.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 43 1 1 1 0 2 308 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 45.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 41 1 1 1 0 2 309 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 47.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -138 1 1 1 0 2 310 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -49.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 89 2 1 1 0 2 311 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -0.90 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 88 2 1 1 0 2 312 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 0.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -133 1 1 1 0 2 313 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -44.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 51 2 1 1 0 2 314 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 37.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -139 1 1 1 0 2 315 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -50.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 86 2 1 1 0 2 316 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 2.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 87 2 1 1 0 2 317 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 1.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -140 1 1 1 0 2 318 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -51.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -131 1 1 1 0 2 319 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -42.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -137 1 1 1 0 2 320 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -48.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 52 2 1 1 0 2 321 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 36.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 42 1 1 1 0 2 322 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 46.10 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -135 1 1 1 0 2 323 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -46.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -132 1 1 1 0 2 324 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -43.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -136 1 1 1 0 2 325 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -47.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 -134 1 1 1 0 2 326 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 -45.90 1.000 0.000 0 2 0 2 1.000 1.000 2.30 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 53 2 1 1 0 2 327 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 35.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 85 2 1 1 0 2 328 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 3.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 83 2 1 1 0 2 329 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 5.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 81 2 1 1 0 2 330 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 7.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 84 2 1 1 0 2 331 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 4.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 82 2 1 1 0 2 332 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 6.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 54 2 1 1 0 2 333 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 34.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 80 2 1 1 0 2 334 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 8.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 79 2 1 1 0 2 335 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 9.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 78 2 1 1 0 2 336 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 10.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 77 2 1 1 0 2 337 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 11.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 76 2 1 1 0 2 338 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 12.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 55 2 1 1 0 2 339 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 33.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 75 2 1 1 0 2 340 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 13.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 73 2 1 1 0 2 341 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 15.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 74 2 1 1 0 2 342 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 14.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 72 2 1 1 0 2 343 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 16.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 71 2 1 1 0 2 344 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 17.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 56 2 1 1 0 2 345 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 32.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 70 2 1 1 0 2 346 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 18.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 67 2 1 1 0 2 347 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 21.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 69 2 1 1 0 2 348 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 19.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 66 2 1 1 0 2 349 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 22.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 57 2 1 1 0 2 350 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 31.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 68 2 1 1 0 2 351 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 20.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 65 2 1 1 0 2 352 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 23.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 63 2 1 1 0 2 353 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 25.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 62 2 1 1 0 2 354 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 26.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 64 2 1 1 0 2 355 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 24.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 60 2 1 1 0 2 356 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 28.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 61 2 1 1 0 2 357 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 27.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 58 2 1 1 0 2 358 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 30.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - 59 2 1 1 0 2 359 16 100 256 256 0.00000 9.97143 3.65651e-003 1070 1860 0.00 -0.00 -0.00 -5.06 12.64 29.10 1.000 0.000 0 2 0 2 1.000 1.000 5.76 0.00 0.00 0.00 1 8.00 0 0 0 448 0.0 1 1 7 0 0.000 0.000 0.000 1 - -# === END OF DATA DESCRIPTION FILE =============================================== diff --git a/nibabel/tests/data/T2-interleaved.PAR b/nibabel/tests/data/T2-interleaved.PAR deleted file mode 100644 index da7d3c0032..0000000000 --- a/nibabel/tests/data/T2-interleaved.PAR +++ /dev/null @@ -1,112 +0,0 @@ -# === DATA DESCRIPTION FILE ====================================================== -# -# CAUTION - Investigational device. -# Limited by Federal Law to investigational use. -# -# Dataset name: H:\Export\05aug14_test_samples_8_1 -# -# CLINICAL TRYOUT Research image export tool V4.2 -# -# === GENERAL INFORMATION ======================================================== -# -. Patient name : 05aug14test -. Examination name : test -. Protocol name : T2-interleaved SENSE -. Examination date/time : 2014.08.05 / 11:27:34 -. Series Type : Image MRSERIES -. Acquisition nr : 8 -. Reconstruction nr : 1 -. Scan Duration [sec] : 8 -. Max. number of cardiac phases : 1 -. Max. number of echoes : 1 -. Max. number of slices/locations : 10 -. Max. number of dynamics : 1 -. Max. number of mixes : 1 -. Patient position : Head First Supine -. Preparation direction : Right-Left -. Technique : TSE -. Scan resolution (x, y) : 76 56 -. Scan mode : MS -. Repetition time [ms] : 1000.000 -. FOV (ap,fh,rl) [mm] : 130.000 120.970 154.375 -. Water Fat shift [pixels] : 2.479 -. Angulation midslice(ap,fh,rl)[degr]: -1.979 0.546 0.019 -. Off Centre midslice(ap,fh,rl) [mm] : -18.805 22.157 -17.977 -. Flow compensation <0=no 1=yes> ? : 0 -. Presaturation <0=no 1=yes> ? : 1 -. Phase encoding velocity [cm/sec] : 0.000000 0.000000 0.000000 -. MTC <0=no 1=yes> ? : 0 -. SPIR <0=no 1=yes> ? : 0 -. EPI factor <0,1=no EPI> : 1 -. Dynamic scan <0=no 1=yes> ? : 0 -. Diffusion <0=no 1=yes> ? : 0 -. Diffusion echo time [ms] : 0.0000 -. Max. number of diffusion values : 1 -. Max. number of gradient orients : 1 -. Number of label types <0=no ASL> : 0 -# -# === PIXEL VALUES ============================================================= -# PV = pixel value in REC file, FP = floating point value, DV = displayed value on console -# RS = rescale slope, RI = rescale intercept, SS = scale slope -# DV = PV * RS + RI FP = DV / (RS * SS) -# -# === IMAGE INFORMATION DEFINITION ============================================= -# The rest of this file contains ONE line per image, this line contains the following information: -# -# slice number (integer) -# echo number (integer) -# dynamic scan number (integer) -# cardiac phase number (integer) -# image_type_mr (integer) -# scanning sequence (integer) -# index in REC file (in images) (integer) -# image pixel size (in bits) (integer) -# scan percentage (integer) -# recon resolution (x y) (2*integer) -# rescale intercept (float) -# rescale slope (float) -# scale slope (float) -# window center (integer) -# window width (integer) -# image angulation (ap,fh,rl in degrees ) (3*float) -# image offcentre (ap,fh,rl in mm ) (3*float) -# slice thickness (in mm ) (float) -# slice gap (in mm ) (float) -# image_display_orientation (integer) -# slice orientation ( TRA/SAG/COR ) (integer) -# fmri_status_indication (integer) -# image_type_ed_es (end diast/end syst) (integer) -# pixel spacing (x,y) (in mm) (2*float) -# echo_time (float) -# dyn_scan_begin_time (float) -# trigger_time (float) -# diffusion_b_factor (float) -# number of averages (integer) -# image_flip_angle (in degrees) (float) -# cardiac frequency (bpm) (integer) -# minimum RR-interval (in ms) (integer) -# maximum RR-interval (in ms) (integer) -# TURBO factor <0=no turbo> (integer) -# Inversion delay (in ms) (float) -# diffusion b value number (imagekey!) (integer) -# gradient orientation number (imagekey!) (integer) -# contrast type (string) -# diffusion anisotropy type (string) -# diffusion (ap, fh, rl) (3*float) -# label type (ASL) (imagekey!) (integer) -# -# === IMAGE INFORMATION ========================================================== -# sl ec dyn ph ty idx pix scan% rec size (re)scale window angulation offcentre thick gap info spacing echo dtime ttime diff avg flip freq RR-int turbo delay b grad cont anis diffusion L.ty - - 1 1 1 1 0 1 0 16 81 80 80 0.00000 8.38730 7.95870e-003 1070 1860 -1.98 0.55 0.02 -18.79 -33.29 -16.06 10.000 2.330 0 1 0 2 1.912 1.912 80.00 0.00 0.00 0.00 1 90.00 0 0 0 15 0.0 1 1 8 0 0.000 0.000 0.000 1 - 2 1 1 1 0 1 1 16 81 80 80 0.00000 8.38730 7.95870e-003 11500 19991 -1.98 0.55 0.02 -18.79 -20.97 -16.49 10.000 2.330 0 1 0 2 1.912 1.912 80.00 0.00 0.00 0.00 1 90.00 0 0 0 15 0.0 1 1 8 0 0.000 0.000 0.000 1 - 3 1 1 1 0 1 2 16 81 80 80 0.00000 8.38730 7.95870e-003 16200 28161 -1.98 0.55 0.02 -18.80 -8.65 -16.91 10.000 2.330 0 1 0 2 1.912 1.912 80.00 0.00 0.00 0.00 1 90.00 0 0 0 15 0.0 1 1 8 0 0.000 0.000 0.000 1 - 4 1 1 1 0 1 3 16 81 80 80 0.00000 8.38730 7.95870e-003 67043 116541 -1.98 0.55 0.02 -18.80 3.67 -17.34 10.000 2.330 0 1 0 2 1.912 1.912 80.00 0.00 0.00 0.00 1 90.00 0 0 0 15 0.0 1 1 8 0 0.000 0.000 0.000 1 - 5 1 1 1 0 1 4 16 81 80 80 0.00000 8.38730 7.95870e-003 80065 139178 -1.98 0.55 0.02 -18.80 16.00 -17.76 10.000 2.330 0 1 0 2 1.912 1.912 80.00 0.00 0.00 0.00 1 90.00 0 0 0 15 0.0 1 1 8 0 0.000 0.000 0.000 1 - 6 1 1 1 0 1 5 16 81 80 80 0.00000 8.38730 7.95870e-003 30352 52762 -1.98 0.55 0.02 -18.81 28.32 -18.19 10.000 2.330 0 1 0 2 1.912 1.912 80.00 0.00 0.00 0.00 1 90.00 0 0 0 15 0.0 1 1 8 0 0.000 0.000 0.000 1 - 7 1 1 1 0 1 6 16 81 80 80 0.00000 8.38730 7.95870e-003 11471 19940 -1.98 0.55 0.02 -18.81 40.64 -18.62 10.000 2.330 0 1 0 2 1.912 1.912 80.00 0.00 0.00 0.00 1 90.00 0 0 0 15 0.0 1 1 8 0 0.000 0.000 0.000 1 - 8 1 1 1 0 1 7 16 81 80 80 0.00000 8.38730 7.95870e-003 8085 14055 -1.98 0.55 0.02 -18.82 52.96 -19.04 10.000 2.330 0 1 0 2 1.912 1.912 80.00 0.00 0.00 0.00 1 90.00 0 0 0 15 0.0 1 1 8 0 0.000 0.000 0.000 1 - 9 1 1 1 0 1 8 16 81 80 80 0.00000 8.38730 7.95870e-003 4902 8521 -1.98 0.55 0.02 -18.82 65.29 -19.47 10.000 2.330 0 1 0 2 1.912 1.912 80.00 0.00 0.00 0.00 1 90.00 0 0 0 15 0.0 1 1 8 0 0.000 0.000 0.000 1 - 10 1 1 1 0 1 9 16 81 80 80 0.00000 8.38730 7.95870e-003 4201 7302 -1.98 0.55 0.02 -18.82 77.61 -19.89 10.000 2.330 0 1 0 2 1.912 1.912 80.00 0.00 0.00 0.00 1 90.00 0 0 0 15 0.0 1 1 8 0 0.000 0.000 0.000 1 - -# === END OF DATA DESCRIPTION FILE =============================================== diff --git a/nibabel/tests/data/T2.PAR b/nibabel/tests/data/T2.PAR deleted file mode 100644 index 819b45a185..0000000000 --- a/nibabel/tests/data/T2.PAR +++ /dev/null @@ -1,112 +0,0 @@ -# === DATA DESCRIPTION FILE ====================================================== -# -# CAUTION - Investigational device. -# Limited by Federal Law to investigational use. -# -# Dataset name: H:\Export\05aug14_test_samples_7_1 -# -# CLINICAL TRYOUT Research image export tool V4.2 -# -# === GENERAL INFORMATION ======================================================== -# -. Patient name : 05aug14test -. Examination name : test -. Protocol name : T2 SENSE -. Examination date/time : 2014.08.05 / 11:27:34 -. Series Type : Image MRSERIES -. Acquisition nr : 7 -. Reconstruction nr : 1 -. Scan Duration [sec] : 8 -. Max. number of cardiac phases : 1 -. Max. number of echoes : 1 -. Max. number of slices/locations : 10 -. Max. number of dynamics : 1 -. Max. number of mixes : 1 -. Patient position : Head First Supine -. Preparation direction : Right-Left -. Technique : TSE -. Scan resolution (x, y) : 76 56 -. Scan mode : MS -. Repetition time [ms] : 1000.000 -. FOV (ap,fh,rl) [mm] : 130.000 120.970 154.375 -. Water Fat shift [pixels] : 2.479 -. Angulation midslice(ap,fh,rl)[degr]: -1.979 0.546 0.019 -. Off Centre midslice(ap,fh,rl) [mm] : -18.805 22.157 -17.977 -. Flow compensation <0=no 1=yes> ? : 0 -. Presaturation <0=no 1=yes> ? : 1 -. Phase encoding velocity [cm/sec] : 0.000000 0.000000 0.000000 -. MTC <0=no 1=yes> ? : 0 -. SPIR <0=no 1=yes> ? : 0 -. EPI factor <0,1=no EPI> : 1 -. Dynamic scan <0=no 1=yes> ? : 0 -. Diffusion <0=no 1=yes> ? : 0 -. Diffusion echo time [ms] : 0.0000 -. Max. number of diffusion values : 1 -. Max. number of gradient orients : 1 -. Number of label types <0=no ASL> : 0 -# -# === PIXEL VALUES ============================================================= -# PV = pixel value in REC file, FP = floating point value, DV = displayed value on console -# RS = rescale slope, RI = rescale intercept, SS = scale slope -# DV = PV * RS + RI FP = DV / (RS * SS) -# -# === IMAGE INFORMATION DEFINITION ============================================= -# The rest of this file contains ONE line per image, this line contains the following information: -# -# slice number (integer) -# echo number (integer) -# dynamic scan number (integer) -# cardiac phase number (integer) -# image_type_mr (integer) -# scanning sequence (integer) -# index in REC file (in images) (integer) -# image pixel size (in bits) (integer) -# scan percentage (integer) -# recon resolution (x y) (2*integer) -# rescale intercept (float) -# rescale slope (float) -# scale slope (float) -# window center (integer) -# window width (integer) -# image angulation (ap,fh,rl in degrees ) (3*float) -# image offcentre (ap,fh,rl in mm ) (3*float) -# slice thickness (in mm ) (float) -# slice gap (in mm ) (float) -# image_display_orientation (integer) -# slice orientation ( TRA/SAG/COR ) (integer) -# fmri_status_indication (integer) -# image_type_ed_es (end diast/end syst) (integer) -# pixel spacing (x,y) (in mm) (2*float) -# echo_time (float) -# dyn_scan_begin_time (float) -# trigger_time (float) -# diffusion_b_factor (float) -# number of averages (integer) -# image_flip_angle (in degrees) (float) -# cardiac frequency (bpm) (integer) -# minimum RR-interval (in ms) (integer) -# maximum RR-interval (in ms) (integer) -# TURBO factor <0=no turbo> (integer) -# Inversion delay (in ms) (float) -# diffusion b value number (imagekey!) (integer) -# gradient orientation number (imagekey!) (integer) -# contrast type (string) -# diffusion anisotropy type (string) -# diffusion (ap, fh, rl) (3*float) -# label type (ASL) (imagekey!) (integer) -# -# === IMAGE INFORMATION ========================================================== -# sl ec dyn ph ty idx pix scan% rec size (re)scale window angulation offcentre thick gap info spacing echo dtime ttime diff avg flip freq RR-int turbo delay b grad cont anis diffusion L.ty - - 1 1 1 1 0 1 0 16 81 80 80 0.00000 11.66129 5.47580e-003 1070 1860 -1.98 0.55 0.02 -18.79 -33.29 -16.06 10.000 2.330 0 1 0 2 1.912 1.912 80.00 0.00 0.00 0.00 1 90.00 0 0 0 15 0.0 1 1 8 0 0.000 0.000 0.000 1 - 2 1 1 1 0 1 1 16 81 80 80 0.00000 11.66129 5.47580e-003 11765 20450 -1.98 0.55 0.02 -18.79 -20.97 -16.49 10.000 2.330 0 1 0 2 1.912 1.912 80.00 0.00 0.00 0.00 1 90.00 0 0 0 15 0.0 1 1 8 0 0.000 0.000 0.000 1 - 3 1 1 1 0 1 2 16 81 80 80 0.00000 11.66129 5.47580e-003 16140 28057 -1.98 0.55 0.02 -18.80 -8.65 -16.91 10.000 2.330 0 1 0 2 1.912 1.912 80.00 0.00 0.00 0.00 1 90.00 0 0 0 15 0.0 1 1 8 0 0.000 0.000 0.000 1 - 4 1 1 1 0 1 3 16 81 80 80 0.00000 11.66129 5.47580e-003 70823 123112 -1.98 0.55 0.02 -18.80 3.67 -17.34 10.000 2.330 0 1 0 2 1.912 1.912 80.00 0.00 0.00 0.00 1 90.00 0 0 0 15 0.0 1 1 8 0 0.000 0.000 0.000 1 - 5 1 1 1 0 1 4 16 81 80 80 0.00000 11.66129 5.47580e-003 75089 130529 -1.98 0.55 0.02 -18.80 16.00 -17.76 10.000 2.330 0 1 0 2 1.912 1.912 80.00 0.00 0.00 0.00 1 90.00 0 0 0 15 0.0 1 1 8 0 0.000 0.000 0.000 1 - 6 1 1 1 0 1 5 16 81 80 80 0.00000 11.66129 5.47580e-003 29296 50926 -1.98 0.55 0.02 -18.81 28.32 -18.19 10.000 2.330 0 1 0 2 1.912 1.912 80.00 0.00 0.00 0.00 1 90.00 0 0 0 15 0.0 1 1 8 0 0.000 0.000 0.000 1 - 7 1 1 1 0 1 6 16 81 80 80 0.00000 11.66129 5.47580e-003 12039 20927 -1.98 0.55 0.02 -18.81 40.64 -18.62 10.000 2.330 0 1 0 2 1.912 1.912 80.00 0.00 0.00 0.00 1 90.00 0 0 0 15 0.0 1 1 8 0 0.000 0.000 0.000 1 - 8 1 1 1 0 1 7 16 81 80 80 0.00000 11.66129 5.47580e-003 7482 13006 -1.98 0.55 0.02 -18.82 52.96 -19.04 10.000 2.330 0 1 0 2 1.912 1.912 80.00 0.00 0.00 0.00 1 90.00 0 0 0 15 0.0 1 1 8 0 0.000 0.000 0.000 1 - 9 1 1 1 0 1 8 16 81 80 80 0.00000 11.66129 5.47580e-003 4821 8380 -1.98 0.55 0.02 -18.82 65.29 -19.47 10.000 2.330 0 1 0 2 1.912 1.912 80.00 0.00 0.00 0.00 1 90.00 0 0 0 15 0.0 1 1 8 0 0.000 0.000 0.000 1 - 10 1 1 1 0 1 9 16 81 80 80 0.00000 11.66129 5.47580e-003 4027 7000 -1.98 0.55 0.02 -18.82 77.61 -19.89 10.000 2.330 0 1 0 2 1.912 1.912 80.00 0.00 0.00 0.00 1 90.00 0 0 0 15 0.0 1 1 8 0 0.000 0.000 0.000 1 - -# === END OF DATA DESCRIPTION FILE =============================================== diff --git a/nibabel/tests/data/T2_-interleaved.PAR b/nibabel/tests/data/T2_-interleaved.PAR deleted file mode 100644 index d73ab881d5..0000000000 --- a/nibabel/tests/data/T2_-interleaved.PAR +++ /dev/null @@ -1,122 +0,0 @@ -# === DATA DESCRIPTION FILE ====================================================== -# -# CAUTION - Investigational device. -# Limited by Federal Law to investigational use. -# -# Dataset name: H:\Export\05aug14_test_samples_10_1 -# -# CLINICAL TRYOUT Research image export tool V4.2 -# -# === GENERAL INFORMATION ======================================================== -# -. Patient name : 05aug14test -. Examination name : test -. Protocol name : T2*-interleaved SENSE -. Examination date/time : 2014.08.05 / 11:27:34 -. Series Type : Image MRSERIES -. Acquisition nr : 10 -. Reconstruction nr : 1 -. Scan Duration [sec] : 12 -. Max. number of cardiac phases : 1 -. Max. number of echoes : 1 -. Max. number of slices/locations : 10 -. Max. number of dynamics : 2 -. Max. number of mixes : 1 -. Patient position : Head First Supine -. Preparation direction : Right-Left -. Technique : FEEPI -. Scan resolution (x, y) : 76 62 -. Scan mode : MS -. Repetition time [ms] : 2000.000 -. FOV (ap,fh,rl) [mm] : 130.000 120.970 154.375 -. Water Fat shift [pixels] : 8.014 -. Angulation midslice(ap,fh,rl)[degr]: -1.979 0.546 0.019 -. Off Centre midslice(ap,fh,rl) [mm] : -18.805 22.157 -17.977 -. Flow compensation <0=no 1=yes> ? : 0 -. Presaturation <0=no 1=yes> ? : 0 -. Phase encoding velocity [cm/sec] : 0.000000 0.000000 0.000000 -. MTC <0=no 1=yes> ? : 0 -. SPIR <0=no 1=yes> ? : 1 -. EPI factor <0,1=no EPI> : 27 -. Dynamic scan <0=no 1=yes> ? : 1 -. Diffusion <0=no 1=yes> ? : 0 -. Diffusion echo time [ms] : 0.0000 -. Max. number of diffusion values : 1 -. Max. number of gradient orients : 1 -. Number of label types <0=no ASL> : 0 -# -# === PIXEL VALUES ============================================================= -# PV = pixel value in REC file, FP = floating point value, DV = displayed value on console -# RS = rescale slope, RI = rescale intercept, SS = scale slope -# DV = PV * RS + RI FP = DV / (RS * SS) -# -# === IMAGE INFORMATION DEFINITION ============================================= -# The rest of this file contains ONE line per image, this line contains the following information: -# -# slice number (integer) -# echo number (integer) -# dynamic scan number (integer) -# cardiac phase number (integer) -# image_type_mr (integer) -# scanning sequence (integer) -# index in REC file (in images) (integer) -# image pixel size (in bits) (integer) -# scan percentage (integer) -# recon resolution (x y) (2*integer) -# rescale intercept (float) -# rescale slope (float) -# scale slope (float) -# window center (integer) -# window width (integer) -# image angulation (ap,fh,rl in degrees ) (3*float) -# image offcentre (ap,fh,rl in mm ) (3*float) -# slice thickness (in mm ) (float) -# slice gap (in mm ) (float) -# image_display_orientation (integer) -# slice orientation ( TRA/SAG/COR ) (integer) -# fmri_status_indication (integer) -# image_type_ed_es (end diast/end syst) (integer) -# pixel spacing (x,y) (in mm) (2*float) -# echo_time (float) -# dyn_scan_begin_time (float) -# trigger_time (float) -# diffusion_b_factor (float) -# number of averages (integer) -# image_flip_angle (in degrees) (float) -# cardiac frequency (bpm) (integer) -# minimum RR-interval (in ms) (integer) -# maximum RR-interval (in ms) (integer) -# TURBO factor <0=no turbo> (integer) -# Inversion delay (in ms) (float) -# diffusion b value number (imagekey!) (integer) -# gradient orientation number (imagekey!) (integer) -# contrast type (string) -# diffusion anisotropy type (string) -# diffusion (ap, fh, rl) (3*float) -# label type (ASL) (imagekey!) (integer) -# -# === IMAGE INFORMATION ========================================================== -# sl ec dyn ph ty idx pix scan% rec size (re)scale window angulation offcentre thick gap info spacing echo dtime ttime diff avg flip freq RR-int turbo delay b grad cont anis diffusion L.ty - - 1 1 1 1 0 2 0 16 81 80 80 0.00000 239.84469 1.19452e-003 1070 1860 -1.98 0.55 0.02 -18.79 -33.29 -16.06 10.000 2.330 0 1 1 2 1.912 1.912 35.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 - 2 1 1 1 0 2 1 16 81 80 80 0.00000 239.84469 1.19452e-003 1873 3256 -1.98 0.55 0.02 -18.79 -20.97 -16.49 10.000 2.330 0 1 1 2 1.912 1.912 35.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 - 3 1 1 1 0 2 2 16 81 80 80 0.00000 239.84469 1.19452e-003 3531 6138 -1.98 0.55 0.02 -18.80 -8.65 -16.91 10.000 2.330 0 1 1 2 1.912 1.912 35.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 - 4 1 1 1 0 2 3 16 81 80 80 0.00000 239.84469 1.19452e-003 8062 14015 -1.98 0.55 0.02 -18.80 3.67 -17.34 10.000 2.330 0 1 1 2 1.912 1.912 35.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 - 5 1 1 1 0 2 4 16 81 80 80 0.00000 239.84469 1.19452e-003 4139 7194 -1.98 0.55 0.02 -18.80 16.00 -17.76 10.000 2.330 0 1 1 2 1.912 1.912 35.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 - 6 1 1 1 0 2 5 16 81 80 80 0.00000 239.84469 1.19452e-003 6787 11798 -1.98 0.55 0.02 -18.81 28.32 -18.19 10.000 2.330 0 1 1 2 1.912 1.912 35.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 - 7 1 1 1 0 2 6 16 81 80 80 0.00000 239.84469 1.19452e-003 1906 3314 -1.98 0.55 0.02 -18.81 40.64 -18.62 10.000 2.330 0 1 1 2 1.912 1.912 35.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 - 8 1 1 1 0 2 7 16 81 80 80 0.00000 239.84469 1.19452e-003 1147 1993 -1.98 0.55 0.02 -18.82 52.96 -19.04 10.000 2.330 0 1 1 2 1.912 1.912 35.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 - 9 1 1 1 0 2 8 16 81 80 80 0.00000 239.84469 1.19452e-003 1116 1940 -1.98 0.55 0.02 -18.82 65.29 -19.47 10.000 2.330 0 1 1 2 1.912 1.912 35.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 - 10 1 1 1 0 2 9 16 81 80 80 0.00000 239.84469 1.19452e-003 933 1622 -1.98 0.55 0.02 -18.82 77.61 -19.89 10.000 2.330 0 1 1 2 1.912 1.912 35.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 - 1 1 2 1 0 2 10 16 81 80 80 0.00000 239.84469 1.19452e-003 1089 1892 -1.98 0.55 0.02 -18.79 -33.29 -16.06 10.000 2.330 0 1 2 2 1.912 1.912 35.00 2.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 - 2 1 2 1 0 2 11 16 81 80 80 0.00000 239.84469 1.19452e-003 1826 3175 -1.98 0.55 0.02 -18.79 -20.97 -16.49 10.000 2.330 0 1 2 2 1.912 1.912 35.00 2.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 - 3 1 2 1 0 2 12 16 81 80 80 0.00000 239.84469 1.19452e-003 3655 6353 -1.98 0.55 0.02 -18.80 -8.65 -16.91 10.000 2.330 0 1 2 2 1.912 1.912 35.00 2.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 - 4 1 2 1 0 2 13 16 81 80 80 0.00000 239.84469 1.19452e-003 7595 13203 -1.98 0.55 0.02 -18.80 3.67 -17.34 10.000 2.330 0 1 2 2 1.912 1.912 35.00 2.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 - 5 1 2 1 0 2 14 16 81 80 80 0.00000 239.84469 1.19452e-003 3657 6357 -1.98 0.55 0.02 -18.80 16.00 -17.76 10.000 2.330 0 1 2 2 1.912 1.912 35.00 2.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 - 6 1 2 1 0 2 15 16 81 80 80 0.00000 239.84469 1.19452e-003 7312 12710 -1.98 0.55 0.02 -18.81 28.32 -18.19 10.000 2.330 0 1 2 2 1.912 1.912 35.00 2.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 - 7 1 2 1 0 2 16 16 81 80 80 0.00000 239.84469 1.19452e-003 1663 2891 -1.98 0.55 0.02 -18.81 40.64 -18.62 10.000 2.330 0 1 2 2 1.912 1.912 35.00 2.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 - 8 1 2 1 0 2 17 16 81 80 80 0.00000 239.84469 1.19452e-003 1278 2221 -1.98 0.55 0.02 -18.82 52.96 -19.04 10.000 2.330 0 1 2 2 1.912 1.912 35.00 2.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 - 9 1 2 1 0 2 18 16 81 80 80 0.00000 239.84469 1.19452e-003 1041 1809 -1.98 0.55 0.02 -18.82 65.29 -19.47 10.000 2.330 0 1 2 2 1.912 1.912 35.00 2.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 - 10 1 2 1 0 2 19 16 81 80 80 0.00000 239.84469 1.19452e-003 891 1549 -1.98 0.55 0.02 -18.82 77.61 -19.89 10.000 2.330 0 1 2 2 1.912 1.912 35.00 2.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 - -# === END OF DATA DESCRIPTION FILE =============================================== diff --git a/nibabel/tests/data/T2_.PAR b/nibabel/tests/data/T2_.PAR deleted file mode 100644 index d37ef17f53..0000000000 --- a/nibabel/tests/data/T2_.PAR +++ /dev/null @@ -1,122 +0,0 @@ -# === DATA DESCRIPTION FILE ====================================================== -# -# CAUTION - Investigational device. -# Limited by Federal Law to investigational use. -# -# Dataset name: H:\Export\05aug14_test_samples_9_1 -# -# CLINICAL TRYOUT Research image export tool V4.2 -# -# === GENERAL INFORMATION ======================================================== -# -. Patient name : 05aug14test -. Examination name : test -. Protocol name : T2* SENSE -. Examination date/time : 2014.08.05 / 11:27:34 -. Series Type : Image MRSERIES -. Acquisition nr : 9 -. Reconstruction nr : 1 -. Scan Duration [sec] : 12 -. Max. number of cardiac phases : 1 -. Max. number of echoes : 1 -. Max. number of slices/locations : 10 -. Max. number of dynamics : 2 -. Max. number of mixes : 1 -. Patient position : Head First Supine -. Preparation direction : Right-Left -. Technique : FEEPI -. Scan resolution (x, y) : 76 62 -. Scan mode : MS -. Repetition time [ms] : 2000.000 -. FOV (ap,fh,rl) [mm] : 130.000 120.970 154.375 -. Water Fat shift [pixels] : 8.014 -. Angulation midslice(ap,fh,rl)[degr]: -1.979 0.546 0.019 -. Off Centre midslice(ap,fh,rl) [mm] : -18.805 22.157 -17.977 -. Flow compensation <0=no 1=yes> ? : 0 -. Presaturation <0=no 1=yes> ? : 0 -. Phase encoding velocity [cm/sec] : 0.000000 0.000000 0.000000 -. MTC <0=no 1=yes> ? : 0 -. SPIR <0=no 1=yes> ? : 1 -. EPI factor <0,1=no EPI> : 27 -. Dynamic scan <0=no 1=yes> ? : 1 -. Diffusion <0=no 1=yes> ? : 0 -. Diffusion echo time [ms] : 0.0000 -. Max. number of diffusion values : 1 -. Max. number of gradient orients : 1 -. Number of label types <0=no ASL> : 0 -# -# === PIXEL VALUES ============================================================= -# PV = pixel value in REC file, FP = floating point value, DV = displayed value on console -# RS = rescale slope, RI = rescale intercept, SS = scale slope -# DV = PV * RS + RI FP = DV / (RS * SS) -# -# === IMAGE INFORMATION DEFINITION ============================================= -# The rest of this file contains ONE line per image, this line contains the following information: -# -# slice number (integer) -# echo number (integer) -# dynamic scan number (integer) -# cardiac phase number (integer) -# image_type_mr (integer) -# scanning sequence (integer) -# index in REC file (in images) (integer) -# image pixel size (in bits) (integer) -# scan percentage (integer) -# recon resolution (x y) (2*integer) -# rescale intercept (float) -# rescale slope (float) -# scale slope (float) -# window center (integer) -# window width (integer) -# image angulation (ap,fh,rl in degrees ) (3*float) -# image offcentre (ap,fh,rl in mm ) (3*float) -# slice thickness (in mm ) (float) -# slice gap (in mm ) (float) -# image_display_orientation (integer) -# slice orientation ( TRA/SAG/COR ) (integer) -# fmri_status_indication (integer) -# image_type_ed_es (end diast/end syst) (integer) -# pixel spacing (x,y) (in mm) (2*float) -# echo_time (float) -# dyn_scan_begin_time (float) -# trigger_time (float) -# diffusion_b_factor (float) -# number of averages (integer) -# image_flip_angle (in degrees) (float) -# cardiac frequency (bpm) (integer) -# minimum RR-interval (in ms) (integer) -# maximum RR-interval (in ms) (integer) -# TURBO factor <0=no turbo> (integer) -# Inversion delay (in ms) (float) -# diffusion b value number (imagekey!) (integer) -# gradient orientation number (imagekey!) (integer) -# contrast type (string) -# diffusion anisotropy type (string) -# diffusion (ap, fh, rl) (3*float) -# label type (ASL) (imagekey!) (integer) -# -# === IMAGE INFORMATION ========================================================== -# sl ec dyn ph ty idx pix scan% rec size (re)scale window angulation offcentre thick gap info spacing echo dtime ttime diff avg flip freq RR-int turbo delay b grad cont anis diffusion L.ty - - 1 1 1 1 0 2 0 16 81 80 80 0.00000 251.05495 1.18150e-003 1070 1860 -1.98 0.55 0.02 -18.79 -33.29 -16.06 10.000 2.330 0 1 1 2 1.912 1.912 35.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 - 2 1 1 1 0 2 1 16 81 80 80 0.00000 251.05495 1.18150e-003 1697 2951 -1.98 0.55 0.02 -18.79 -20.97 -16.49 10.000 2.330 0 1 1 2 1.912 1.912 35.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 - 3 1 1 1 0 2 2 16 81 80 80 0.00000 251.05495 1.18150e-003 5912 10277 -1.98 0.55 0.02 -18.80 -8.65 -16.91 10.000 2.330 0 1 1 2 1.912 1.912 35.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 - 4 1 1 1 0 2 3 16 81 80 80 0.00000 251.05495 1.18150e-003 11675 20295 -1.98 0.55 0.02 -18.80 3.67 -17.34 10.000 2.330 0 1 1 2 1.912 1.912 35.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 - 5 1 1 1 0 2 4 16 81 80 80 0.00000 251.05495 1.18150e-003 3596 6251 -1.98 0.55 0.02 -18.80 16.00 -17.76 10.000 2.330 0 1 1 2 1.912 1.912 35.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 - 6 1 1 1 0 2 5 16 81 80 80 0.00000 251.05495 1.18150e-003 7385 12838 -1.98 0.55 0.02 -18.81 28.32 -18.19 10.000 2.330 0 1 1 2 1.912 1.912 35.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 - 7 1 1 1 0 2 6 16 81 80 80 0.00000 251.05495 1.18150e-003 1846 3209 -1.98 0.55 0.02 -18.81 40.64 -18.62 10.000 2.330 0 1 1 2 1.912 1.912 35.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 - 8 1 1 1 0 2 7 16 81 80 80 0.00000 251.05495 1.18150e-003 1121 1948 -1.98 0.55 0.02 -18.82 52.96 -19.04 10.000 2.330 0 1 1 2 1.912 1.912 35.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 - 9 1 1 1 0 2 8 16 81 80 80 0.00000 251.05495 1.18150e-003 1001 1741 -1.98 0.55 0.02 -18.82 65.29 -19.47 10.000 2.330 0 1 1 2 1.912 1.912 35.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 - 10 1 1 1 0 2 9 16 81 80 80 0.00000 251.05495 1.18150e-003 912 1586 -1.98 0.55 0.02 -18.82 77.61 -19.89 10.000 2.330 0 1 1 2 1.912 1.912 35.00 0.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 - 1 1 2 1 0 2 10 16 81 80 80 0.00000 251.05495 1.18150e-003 1129 1963 -1.98 0.55 0.02 -18.79 -33.29 -16.06 10.000 2.330 0 1 2 2 1.912 1.912 35.00 2.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 - 2 1 2 1 0 2 11 16 81 80 80 0.00000 251.05495 1.18150e-003 1682 2924 -1.98 0.55 0.02 -18.79 -20.97 -16.49 10.000 2.330 0 1 2 2 1.912 1.912 35.00 2.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 - 3 1 2 1 0 2 12 16 81 80 80 0.00000 251.05495 1.18150e-003 6115 10629 -1.98 0.55 0.02 -18.80 -8.65 -16.91 10.000 2.330 0 1 2 2 1.912 1.912 35.00 2.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 - 4 1 2 1 0 2 13 16 81 80 80 0.00000 251.05495 1.18150e-003 10693 18587 -1.98 0.55 0.02 -18.80 3.67 -17.34 10.000 2.330 0 1 2 2 1.912 1.912 35.00 2.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 - 5 1 2 1 0 2 14 16 81 80 80 0.00000 251.05495 1.18150e-003 3571 6208 -1.98 0.55 0.02 -18.80 16.00 -17.76 10.000 2.330 0 1 2 2 1.912 1.912 35.00 2.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 - 6 1 2 1 0 2 15 16 81 80 80 0.00000 251.05495 1.18150e-003 6788 11799 -1.98 0.55 0.02 -18.81 28.32 -18.19 10.000 2.330 0 1 2 2 1.912 1.912 35.00 2.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 - 7 1 2 1 0 2 16 16 81 80 80 0.00000 251.05495 1.18150e-003 1840 3198 -1.98 0.55 0.02 -18.81 40.64 -18.62 10.000 2.330 0 1 2 2 1.912 1.912 35.00 2.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 - 8 1 2 1 0 2 17 16 81 80 80 0.00000 251.05495 1.18150e-003 1095 1903 -1.98 0.55 0.02 -18.82 52.96 -19.04 10.000 2.330 0 1 2 2 1.912 1.912 35.00 2.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 - 9 1 2 1 0 2 18 16 81 80 80 0.00000 251.05495 1.18150e-003 1092 1898 -1.98 0.55 0.02 -18.82 65.29 -19.47 10.000 2.330 0 1 2 2 1.912 1.912 35.00 2.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 - 10 1 2 1 0 2 19 16 81 80 80 0.00000 251.05495 1.18150e-003 957 1664 -1.98 0.55 0.02 -18.82 77.61 -19.89 10.000 2.330 0 1 2 2 1.912 1.912 35.00 2.00 0.00 0.00 1 90.00 0 0 0 27 0.0 1 1 8 0 0.000 0.000 0.000 1 - -# === END OF DATA DESCRIPTION FILE =============================================== diff --git a/nibabel/tests/data/analyze.hdr b/nibabel/tests/data/analyze.hdr deleted file mode 100644 index 95cec25966..0000000000 Binary files a/nibabel/tests/data/analyze.hdr and /dev/null differ diff --git a/nibabel/tests/data/anatomical.nii b/nibabel/tests/data/anatomical.nii deleted file mode 100644 index 2d48e4770d..0000000000 Binary files a/nibabel/tests/data/anatomical.nii and /dev/null differ diff --git a/nibabel/tests/data/bad_attribute+orig.HEAD b/nibabel/tests/data/bad_attribute+orig.HEAD deleted file mode 100644 index 95fbdeb309..0000000000 --- a/nibabel/tests/data/bad_attribute+orig.HEAD +++ /dev/null @@ -1,133 +0,0 @@ - -type = string-attribute -name = DATASET_NAME -count = 5 -'none~ - -type = string-attribute -name = TYPESTRING -count = 15 -'3DIM_HEAD_ANAT~ - -type = string-attribute -name = IDCODE_STRING -count = 27 -'AFN_-zxZ0OyZs8eEtm9syGBNdA~ - -type = string-attribute -name = IDCODE_DATE -count = 25 -'Sun Oct 1 21:13:09 2017~ - -type = integer-attribute -name = SCENE_DATA -count = 8 - 0 2 0 -999 -999 - -999 -999 -999 - -type = string-attribute -name = LABEL_1 -count = 5 -'none~ - -type = string-attribute -name = LABEL_2 -count = 5 -'none~ - -type = integer-attribute -name = ORIENT_SPECIFIC -count = 3 - 0 3 4 - -type = float-attribute -name = ORIGIN -count = 3 - -49.5 -82.312 -52.3511 - -type = float-attribute -name = DELTA -count = 3 - 3 3 3 - -type = float-attribute -name = IJK_TO_DICOM -count = 12 - 3 0 0 -49.5 0 - 3 0 -82.312 0 0 - 3 -52.3511 - -type = float-attribute -name = IJK_TO_DICOM_REAL -count = 12 - 3 0 0 -49.5 0 - 3 0 -82.312 0 0 - 3 -52.3511 - -type = float-attribute -name = BRICK_STATS -count = 6 - 0 13722 0 10051 0 - 9968 - -type = integer-attribute -name = TAXIS_NUMS -count = 8 - 3 25 77002 -999 -999 - -999 -999 -999 - -type = float-attribute -name = TAXIS_FLOATS -count = 8 - 0 3 0 -52.3511 3 - -999999 -999999 -999999 - -type = float-attribute -name = TAXIS_OFFSETS -count = 25 - 0.3260869 1.826087 0.3913043 1.891304 0.4565217 - 1.956521 0.5217391 2.021739 0.5869564 2.086956 - 0.6521738 2.152174 0.7173912 2.217391 0.7826086 - 2.282609 0.8478259 2.347826 0.9130433 2.413044 - 0.9782607 2.478261 1.043478 2.543479 1.108696 - -type = integer-attribute -name = DATASET_RANK -count = 8 - 3 3 0 0 0 - 0 0 0 - -type = integer-attribute -name = DATASET_DIMENSIONS -count = 5 - 33 41 25 0 0 - -type = integer-attribute -name = BRICK_TYPES -count = 3 - 1 1 1 - -type = float-attribute -name = BRICK_FLOAT_FACS -count = 3 - 0 0 0 - -type = string-attribute -name = TEMPLATE_SPACE -count = 5 -'ORIG~ - -type = integer-attribute -name = INT_CMAP -count = 1 - 0 - -type = integer-attribute -name = BYTEORDER_STRING -count = 10 -'LSB_FIRST~ - -type = string-attribute -name = BRICK_LABS -count = 9 -'#0~#1~#2~ diff --git a/nibabel/tests/data/bad_datatype+orig.HEAD b/nibabel/tests/data/bad_datatype+orig.HEAD deleted file mode 100644 index 27b3a56abb..0000000000 --- a/nibabel/tests/data/bad_datatype+orig.HEAD +++ /dev/null @@ -1,133 +0,0 @@ - -type = string-attribute -name = DATASET_NAME -count = 5 -'none~ - -type = string-attribute -name = TYPESTRING -count = 15 -'3DIM_HEAD_ANAT~ - -type = string-attribute -name = IDCODE_STRING -count = 27 -'AFN_-zxZ0OyZs8eEtm9syGBNdA~ - -type = string-attribute -name = IDCODE_DATE -count = 25 -'Sun Oct 1 21:13:09 2017~ - -type = integer-attribute -name = SCENE_DATA -count = 8 - 0 2 0 -999 -999 - -999 -999 -999 - -type = string-attribute -name = LABEL_1 -count = 5 -'none~ - -type = string-attribute -name = LABEL_2 -count = 5 -'none~ - -type = integer-attribute -name = ORIENT_SPECIFIC -count = 3 - 0 3 4 - -type = float-attribute -name = ORIGIN -count = 3 - -49.5 -82.312 -52.3511 - -type = float-attribute -name = DELTA -count = 3 - 3 3 3 - -type = float-attribute -name = IJK_TO_DICOM -count = 12 - 3 0 0 -49.5 0 - 3 0 -82.312 0 0 - 3 -52.3511 - -type = float-attribute -name = IJK_TO_DICOM_REAL -count = 12 - 3 0 0 -49.5 0 - 3 0 -82.312 0 0 - 3 -52.3511 - -type = float-attribute -name = BRICK_STATS -count = 6 - 0 13722 0 10051 0 - 9968 - -type = integer-attribute -name = TAXIS_NUMS -count = 8 - 3 25 77002 -999 -999 - -999 -999 -999 - -type = float-attribute -name = TAXIS_FLOATS -count = 8 - 0 3 0 -52.3511 3 - -999999 -999999 -999999 - -type = float-attribute -name = TAXIS_OFFSETS -count = 25 - 0.3260869 1.826087 0.3913043 1.891304 0.4565217 - 1.956521 0.5217391 2.021739 0.5869564 2.086956 - 0.6521738 2.152174 0.7173912 2.217391 0.7826086 - 2.282609 0.8478259 2.347826 0.9130433 2.413044 - 0.9782607 2.478261 1.043478 2.543479 1.108696 - -type = integer-attribute -name = DATASET_RANK -count = 8 - 3 3 0 0 0 - 0 0 0 - -type = integer-attribute -name = DATASET_DIMENSIONS -count = 5 - 33 41 25 0 0 - -type = integer-attribute -name = BRICK_TYPES -count = 3 - 1 3 5 - -type = float-attribute -name = BRICK_FLOAT_FACS -count = 3 - 0 0 0 - -type = string-attribute -name = TEMPLATE_SPACE -count = 5 -'ORIG~ - -type = integer-attribute -name = INT_CMAP -count = 1 - 0 - -type = string-attribute -name = BYTEORDER_STRING -count = 10 -'LSB_FIRST~ - -type = string-attribute -name = BRICK_LABS -count = 9 -'#0~#1~#2~ diff --git a/nibabel/tests/data/check_parrec_reslice.py b/nibabel/tests/data/check_parrec_reslice.py deleted file mode 100644 index b22a869090..0000000000 --- a/nibabel/tests/data/check_parrec_reslice.py +++ /dev/null @@ -1,72 +0,0 @@ -"""Test differences in affines by reslicing - -Should be run from directory containing .PAR _and_ matching .REC files from -Michael's PAR / REC dataset at: - - http://psydata.ovgu.de/philips_achieva_testfiles/conversion2 - -Gives output something like: - -RMS of standard image Phantom_EPI_3mm_tra_SENSE_6_1.PAR : 148.619965177 -RMS resliced Phantom_EPI_3mm_cor_20APtrans_15RLrot_SENSE_15_1.PAR : 22.0706570007 -RMS resliced Phantom_EPI_3mm_cor_SENSE_8_1.PAR : 47.5762615987 -RMS resliced Phantom_EPI_3mm_sag_15AP_SENSE_13_1.PAR : 25.0972161667 -RMS resliced Phantom_EPI_3mm_sag_15FH_SENSE_12_1.PAR : 28.7508166372 -RMS resliced Phantom_EPI_3mm_sag_15RL_SENSE_11_1.PAR : 29.0544513507 -RMS resliced Phantom_EPI_3mm_sag_SENSE_7_1.PAR : 25.7621452929 -RMS resliced Phantom_EPI_3mm_tra_-30AP_10RL_20FH_SENSE_14_1.PAR : 32.0602533689 -RMS resliced Phantom_EPI_3mm_tra_15FH_SENSE_9_1.PAR : 28.8953071672 -RMS resliced Phantom_EPI_3mm_tra_15RL_SENSE_10_1.PAR : 29.0793602478 - -The *_cor_SENSE* image has a higher RMS because the back of the phantom is out -of the field of view. -""" - -import glob - -import numpy as np -import numpy.linalg as npl - -import nibabel as nib -from nibabel import parrec -from nibabel.affines import to_matvec -from nibabel.optpkg import optional_package - -_, have_scipy, _ = optional_package('scipy') - - -def resample_img2img(img_to, img_from, order=1, out_class=nib.Nifti1Image): - if not have_scipy: - raise Exception('Scipy must be installed to run resample_img2img.') - - from scipy import ndimage as spnd - - vox2vox = npl.inv(img_from.affine).dot(img_to.affine) - rzs, trans = to_matvec(vox2vox) - data = spnd.affine_transform(img_from.get_fdata(), rzs, trans, img_to.shape, order=order) - return out_class(data, img_to.affine) - - -def gmean_norm(data): - in_data = data > np.mean(data) * 0.8 - gmean = np.mean(data[in_data]) - return data / gmean - - -if __name__ == '__main__': - np.set_printoptions(suppress=True, precision=4) - normal_fname = 'Phantom_EPI_3mm_tra_SENSE_6_1.PAR' - normal_img = parrec.load(normal_fname) - normal_data = normal_img.get_fdata() - normal_normed = gmean_norm(normal_data) - - print(f'RMS of standard image {normal_fname:<44}: {np.sqrt(np.sum(normal_normed**2))}') - - for parfile in glob.glob('*.PAR'): - if parfile == normal_fname: - continue - funny_img = parrec.load(parfile) - fixed_img = resample_img2img(normal_img, funny_img) - fixed_data = fixed_img.get_fdata() - difference_data = normal_normed - gmean_norm(fixed_data) - print(f'RMS resliced {parfile:<52} : {np.sqrt(np.sum(difference_data**2))}') diff --git a/nibabel/tests/data/complex.trk b/nibabel/tests/data/complex.trk deleted file mode 100644 index e2860ee95a..0000000000 Binary files a/nibabel/tests/data/complex.trk and /dev/null differ diff --git a/nibabel/tests/data/complex_big_endian.trk b/nibabel/tests/data/complex_big_endian.trk deleted file mode 100644 index 0f5b9e71ba..0000000000 Binary files a/nibabel/tests/data/complex_big_endian.trk and /dev/null differ diff --git a/nibabel/tests/data/empty.tck b/nibabel/tests/data/empty.tck deleted file mode 100644 index d53162a92c..0000000000 Binary files a/nibabel/tests/data/empty.tck and /dev/null differ diff --git a/nibabel/tests/data/empty.trk b/nibabel/tests/data/empty.trk deleted file mode 100644 index fbe0871807..0000000000 Binary files a/nibabel/tests/data/empty.trk and /dev/null differ diff --git a/nibabel/tests/data/example4d+orig.BRIK.gz b/nibabel/tests/data/example4d+orig.BRIK.gz deleted file mode 100644 index 79296cb94a..0000000000 Binary files a/nibabel/tests/data/example4d+orig.BRIK.gz and /dev/null differ diff --git a/nibabel/tests/data/example4d+orig.HEAD b/nibabel/tests/data/example4d+orig.HEAD deleted file mode 100644 index a43b839d0a..0000000000 --- a/nibabel/tests/data/example4d+orig.HEAD +++ /dev/null @@ -1,133 +0,0 @@ - -type = string-attribute -name = DATASET_NAME -count = 5 -'none~ - -type = string-attribute -name = TYPESTRING -count = 15 -'3DIM_HEAD_ANAT~ - -type = string-attribute -name = IDCODE_STRING -count = 27 -'AFN_-zxZ0OyZs8eEtm9syGBNdA~ - -type = string-attribute -name = IDCODE_DATE -count = 25 -'Sun Oct 1 21:13:09 2017~ - -type = integer-attribute -name = SCENE_DATA -count = 8 - 0 2 0 -999 -999 - -999 -999 -999 - -type = string-attribute -name = LABEL_1 -count = 5 -'none~ - -type = string-attribute -name = LABEL_2 -count = 5 -'none~ - -type = integer-attribute -name = ORIENT_SPECIFIC -count = 3 - 0 3 4 - -type = float-attribute -name = ORIGIN -count = 3 - -49.5 -82.312 -52.3511 - -type = float-attribute -name = DELTA -count = 3 - 3 3 3 - -type = float-attribute -name = IJK_TO_DICOM -count = 12 - 3 0 0 -49.5 0 - 3 0 -82.312 0 0 - 3 -52.3511 - -type = float-attribute -name = IJK_TO_DICOM_REAL -count = 12 - 3 0 0 -49.5 0 - 3 0 -82.312 0 0 - 3 -52.3511 - -type = float-attribute -name = BRICK_STATS -count = 6 - 0 13722 0 10051 0 - 9968 - -type = integer-attribute -name = TAXIS_NUMS -count = 8 - 3 25 77002 -999 -999 - -999 -999 -999 - -type = float-attribute -name = TAXIS_FLOATS -count = 8 - 0 3 0 -52.3511 3 - -999999 -999999 -999999 - -type = float-attribute -name = TAXIS_OFFSETS -count = 25 - 0.3260869 1.826087 0.3913043 1.891304 0.4565217 - 1.956521 0.5217391 2.021739 0.5869564 2.086956 - 0.6521738 2.152174 0.7173912 2.217391 0.7826086 - 2.282609 0.8478259 2.347826 0.9130433 2.413044 - 0.9782607 2.478261 1.043478 2.543479 1.108696 - -type = integer-attribute -name = DATASET_RANK -count = 8 - 3 3 0 0 0 - 0 0 0 - -type = integer-attribute -name = DATASET_DIMENSIONS -count = 5 - 33 41 25 0 0 - -type = integer-attribute -name = BRICK_TYPES -count = 3 - 1 1 1 - -type = float-attribute -name = BRICK_FLOAT_FACS -count = 3 - 0 0 0 - -type = string-attribute -name = TEMPLATE_SPACE -count = 5 -'ORIG~ - -type = integer-attribute -name = INT_CMAP -count = 1 - 0 - -type = string-attribute -name = BYTEORDER_STRING -count = 10 -'LSB_FIRST~ - -type = string-attribute -name = BRICK_LABS -count = 9 -'#0~#1~#2~ diff --git a/nibabel/tests/data/example4d.nii.gz b/nibabel/tests/data/example4d.nii.gz deleted file mode 100644 index 4b258414e6..0000000000 Binary files a/nibabel/tests/data/example4d.nii.gz and /dev/null differ diff --git a/nibabel/tests/data/example_nifti2.nii.gz b/nibabel/tests/data/example_nifti2.nii.gz deleted file mode 100644 index a0d9e408f4..0000000000 Binary files a/nibabel/tests/data/example_nifti2.nii.gz and /dev/null differ diff --git a/nibabel/tests/data/fieldmap.PAR b/nibabel/tests/data/fieldmap.PAR deleted file mode 100644 index 099871a718..0000000000 --- a/nibabel/tests/data/fieldmap.PAR +++ /dev/null @@ -1,122 +0,0 @@ -# === DATA DESCRIPTION FILE ====================================================== -# -# CAUTION - Investigational device. -# Limited by Federal Law to investigational use. -# -# Dataset name: H:\Export\05aug14_test_samples_11_1 -# -# CLINICAL TRYOUT Research image export tool V4.2 -# -# === GENERAL INFORMATION ======================================================== -# -. Patient name : 05aug14test -. Examination name : test -. Protocol name : WIP fieldmap SENSE -. Examination date/time : 2014.08.05 / 11:27:34 -. Series Type : Image MRSERIES -. Acquisition nr : 11 -. Reconstruction nr : 1 -. Scan Duration [sec] : 11.3 -. Max. number of cardiac phases : 1 -. Max. number of echoes : 1 -. Max. number of slices/locations : 10 -. Max. number of dynamics : 1 -. Max. number of mixes : 1 -. Patient position : Head First Supine -. Preparation direction : Right-Left -. Technique : FFE -. Scan resolution (x, y) : 76 62 -. Scan mode : MS -. Repetition time [ms] : 188.384 -. FOV (ap,fh,rl) [mm] : 130.000 120.970 154.375 -. Water Fat shift [pixels] : 0.347 -. Angulation midslice(ap,fh,rl)[degr]: -1.979 0.546 0.019 -. Off Centre midslice(ap,fh,rl) [mm] : -18.805 22.157 -17.977 -. Flow compensation <0=no 1=yes> ? : 0 -. Presaturation <0=no 1=yes> ? : 0 -. Phase encoding velocity [cm/sec] : 0.000000 0.000000 0.000000 -. MTC <0=no 1=yes> ? : 0 -. SPIR <0=no 1=yes> ? : 1 -. EPI factor <0,1=no EPI> : 1 -. Dynamic scan <0=no 1=yes> ? : 0 -. Diffusion <0=no 1=yes> ? : 0 -. Diffusion echo time [ms] : 0.0000 -. Max. number of diffusion values : 1 -. Max. number of gradient orients : 1 -. Number of label types <0=no ASL> : 0 -# -# === PIXEL VALUES ============================================================= -# PV = pixel value in REC file, FP = floating point value, DV = displayed value on console -# RS = rescale slope, RI = rescale intercept, SS = scale slope -# DV = PV * RS + RI FP = DV / (RS * SS) -# -# === IMAGE INFORMATION DEFINITION ============================================= -# The rest of this file contains ONE line per image, this line contains the following information: -# -# slice number (integer) -# echo number (integer) -# dynamic scan number (integer) -# cardiac phase number (integer) -# image_type_mr (integer) -# scanning sequence (integer) -# index in REC file (in images) (integer) -# image pixel size (in bits) (integer) -# scan percentage (integer) -# recon resolution (x y) (2*integer) -# rescale intercept (float) -# rescale slope (float) -# scale slope (float) -# window center (integer) -# window width (integer) -# image angulation (ap,fh,rl in degrees ) (3*float) -# image offcentre (ap,fh,rl in mm ) (3*float) -# slice thickness (in mm ) (float) -# slice gap (in mm ) (float) -# image_display_orientation (integer) -# slice orientation ( TRA/SAG/COR ) (integer) -# fmri_status_indication (integer) -# image_type_ed_es (end diast/end syst) (integer) -# pixel spacing (x,y) (in mm) (2*float) -# echo_time (float) -# dyn_scan_begin_time (float) -# trigger_time (float) -# diffusion_b_factor (float) -# number of averages (integer) -# image_flip_angle (in degrees) (float) -# cardiac frequency (bpm) (integer) -# minimum RR-interval (in ms) (integer) -# maximum RR-interval (in ms) (integer) -# TURBO factor <0=no turbo> (integer) -# Inversion delay (in ms) (float) -# diffusion b value number (imagekey!) (integer) -# gradient orientation number (imagekey!) (integer) -# contrast type (string) -# diffusion anisotropy type (string) -# diffusion (ap, fh, rl) (3*float) -# label type (ASL) (imagekey!) (integer) -# -# === IMAGE INFORMATION ========================================================== -# sl ec dyn ph ty idx pix scan% rec size (re)scale window angulation offcentre thick gap info spacing echo dtime ttime diff avg flip freq RR-int turbo delay b grad cont anis diffusion L.ty - - 1 1 1 1 0 2 0 16 81 80 80 0.00000 10.40049 1.18623e-003 1070 1860 -1.98 0.55 0.02 -18.79 -33.29 -16.06 10.000 2.330 0 1 0 2 1.912 1.912 7.00 0.00 0.00 0.00 1 55.00 0 0 0 1 0.0 1 1 7 0 0.000 0.000 0.000 1 - 2 1 1 1 0 2 1 16 81 80 80 0.00000 10.40049 1.18623e-003 3042 5288 -1.98 0.55 0.02 -18.79 -20.97 -16.49 10.000 2.330 0 1 0 2 1.912 1.912 7.00 0.00 0.00 0.00 1 55.00 0 0 0 1 0.0 1 1 7 0 0.000 0.000 0.000 1 - 3 1 1 1 0 2 2 16 81 80 80 0.00000 10.40049 1.18623e-003 2802 4871 -1.98 0.55 0.02 -18.80 -8.65 -16.91 10.000 2.330 0 1 0 2 1.912 1.912 7.00 0.00 0.00 0.00 1 55.00 0 0 0 1 0.0 1 1 7 0 0.000 0.000 0.000 1 - 4 1 1 1 0 2 3 16 81 80 80 0.00000 10.40049 1.18623e-003 3542 6157 -1.98 0.55 0.02 -18.80 3.67 -17.34 10.000 2.330 0 1 0 2 1.912 1.912 7.00 0.00 0.00 0.00 1 55.00 0 0 0 1 0.0 1 1 7 0 0.000 0.000 0.000 1 - 5 1 1 1 0 2 4 16 81 80 80 0.00000 10.40049 1.18623e-003 3267 5679 -1.98 0.55 0.02 -18.80 16.00 -17.76 10.000 2.330 0 1 0 2 1.912 1.912 7.00 0.00 0.00 0.00 1 55.00 0 0 0 1 0.0 1 1 7 0 0.000 0.000 0.000 1 - 6 1 1 1 0 2 5 16 81 80 80 0.00000 10.40049 1.18623e-003 1350 2346 -1.98 0.55 0.02 -18.81 28.32 -18.19 10.000 2.330 0 1 0 2 1.912 1.912 7.00 0.00 0.00 0.00 1 55.00 0 0 0 1 0.0 1 1 7 0 0.000 0.000 0.000 1 - 7 1 1 1 0 2 6 16 81 80 80 0.00000 10.40049 1.18623e-003 536 931 -1.98 0.55 0.02 -18.81 40.64 -18.62 10.000 2.330 0 1 0 2 1.912 1.912 7.00 0.00 0.00 0.00 1 55.00 0 0 0 1 0.0 1 1 7 0 0.000 0.000 0.000 1 - 8 1 1 1 0 2 7 16 81 80 80 0.00000 10.40049 1.18623e-003 516 897 -1.98 0.55 0.02 -18.82 52.96 -19.04 10.000 2.330 0 1 0 2 1.912 1.912 7.00 0.00 0.00 0.00 1 55.00 0 0 0 1 0.0 1 1 7 0 0.000 0.000 0.000 1 - 9 1 1 1 0 2 8 16 81 80 80 0.00000 10.40049 1.18623e-003 464 807 -1.98 0.55 0.02 -18.82 65.29 -19.47 10.000 2.330 0 1 0 2 1.912 1.912 7.00 0.00 0.00 0.00 1 55.00 0 0 0 1 0.0 1 1 7 0 0.000 0.000 0.000 1 - 10 1 1 1 0 2 9 16 81 80 80 0.00000 10.40049 1.18623e-003 265 461 -1.98 0.55 0.02 -18.82 77.61 -19.89 10.000 2.330 0 1 0 2 1.912 1.912 7.00 0.00 0.00 0.00 1 55.00 0 0 0 1 0.0 1 1 7 0 0.000 0.000 0.000 1 - 1 1 1 1 3 4 10 16 81 80 80 -500.00000 0.24420 4.09500e+000 0 1000 -1.98 0.55 0.02 -18.79 -33.29 -16.06 10.000 2.330 0 1 0 2 1.912 1.912 7.00 0.00 0.00 0.00 1 55.00 0 0 0 1 0.0 1 1 7 0 0.000 0.000 0.000 1 - 2 1 1 1 3 4 11 16 81 80 80 -500.00000 0.24420 4.09500e+000 0 1000 -1.98 0.55 0.02 -18.79 -20.97 -16.49 10.000 2.330 0 1 0 2 1.912 1.912 7.00 0.00 0.00 0.00 1 55.00 0 0 0 1 0.0 1 1 7 0 0.000 0.000 0.000 1 - 3 1 1 1 3 4 12 16 81 80 80 -500.00000 0.24420 4.09500e+000 0 1000 -1.98 0.55 0.02 -18.80 -8.65 -16.91 10.000 2.330 0 1 0 2 1.912 1.912 7.00 0.00 0.00 0.00 1 55.00 0 0 0 1 0.0 1 1 7 0 0.000 0.000 0.000 1 - 4 1 1 1 3 4 13 16 81 80 80 -500.00000 0.24420 4.09500e+000 0 1000 -1.98 0.55 0.02 -18.80 3.67 -17.34 10.000 2.330 0 1 0 2 1.912 1.912 7.00 0.00 0.00 0.00 1 55.00 0 0 0 1 0.0 1 1 7 0 0.000 0.000 0.000 1 - 5 1 1 1 3 4 14 16 81 80 80 -500.00000 0.24420 4.09500e+000 0 1000 -1.98 0.55 0.02 -18.80 16.00 -17.76 10.000 2.330 0 1 0 2 1.912 1.912 7.00 0.00 0.00 0.00 1 55.00 0 0 0 1 0.0 1 1 7 0 0.000 0.000 0.000 1 - 6 1 1 1 3 4 15 16 81 80 80 -500.00000 0.24420 4.09500e+000 0 1000 -1.98 0.55 0.02 -18.81 28.32 -18.19 10.000 2.330 0 1 0 2 1.912 1.912 7.00 0.00 0.00 0.00 1 55.00 0 0 0 1 0.0 1 1 7 0 0.000 0.000 0.000 1 - 7 1 1 1 3 4 16 16 81 80 80 -500.00000 0.24420 4.09500e+000 0 1000 -1.98 0.55 0.02 -18.81 40.64 -18.62 10.000 2.330 0 1 0 2 1.912 1.912 7.00 0.00 0.00 0.00 1 55.00 0 0 0 1 0.0 1 1 7 0 0.000 0.000 0.000 1 - 8 1 1 1 3 4 17 16 81 80 80 -500.00000 0.24420 4.09500e+000 0 1000 -1.98 0.55 0.02 -18.82 52.96 -19.04 10.000 2.330 0 1 0 2 1.912 1.912 7.00 0.00 0.00 0.00 1 55.00 0 0 0 1 0.0 1 1 7 0 0.000 0.000 0.000 1 - 9 1 1 1 3 4 18 16 81 80 80 -500.00000 0.24420 4.09500e+000 0 1000 -1.98 0.55 0.02 -18.82 65.29 -19.47 10.000 2.330 0 1 0 2 1.912 1.912 7.00 0.00 0.00 0.00 1 55.00 0 0 0 1 0.0 1 1 7 0 0.000 0.000 0.000 1 - 10 1 1 1 3 4 19 16 81 80 80 -500.00000 0.24420 4.09500e+000 0 1000 -1.98 0.55 0.02 -18.82 77.61 -19.89 10.000 2.330 0 1 0 2 1.912 1.912 7.00 0.00 0.00 0.00 1 55.00 0 0 0 1 0.0 1 1 7 0 0.000 0.000 0.000 1 - -# === END OF DATA DESCRIPTION FILE =============================================== diff --git a/nibabel/tests/data/functional.nii b/nibabel/tests/data/functional.nii deleted file mode 100644 index 2768d4d391..0000000000 Binary files a/nibabel/tests/data/functional.nii and /dev/null differ diff --git a/nibabel/tests/data/gen_standard.py b/nibabel/tests/data/gen_standard.py deleted file mode 100644 index 7fd05d936e..0000000000 --- a/nibabel/tests/data/gen_standard.py +++ /dev/null @@ -1,89 +0,0 @@ -"""Generate mask and testing tractogram in known formats: - -* mask: standard.nii.gz -* tractogram: - - * standard.trk -""" - -import numpy as np - -import nibabel as nib -from nibabel.streamlines import FORMATS -from nibabel.streamlines.header import Field - - -def mark_the_spot(mask): - """Marks every nonzero voxel using streamlines to form a 3D 'X' inside. - - Generates streamlines forming a 3D 'X' inside every nonzero voxel. - - Parameters - ---------- - mask : ndarray - Mask containing the spots to be marked. - - Returns - ------- - list of ndarrays - All streamlines needed to mark every nonzero voxel in the `mask`. - """ - - def _gen_straight_streamline(start, end, steps=3): - coords = [] - for s, e in zip(start, end): - coords.append(np.linspace(s, e, steps)) - - return np.array(coords).T - - # Generate a 3D 'X' template fitting inside the voxel centered at (0,0,0). - X = [ - _gen_straight_streamline((-0.5, -0.5, -0.5), (0.5, 0.5, 0.5)), - _gen_straight_streamline((-0.5, 0.5, -0.5), (0.5, -0.5, 0.5)), - _gen_straight_streamline((-0.5, 0.5, 0.5), (0.5, -0.5, -0.5)), - _gen_straight_streamline((-0.5, -0.5, 0.5), (0.5, 0.5, -0.5)), - ] - - # Get the coordinates of voxels 'on' in the mask. - coords = np.array(zip(*np.where(mask))) - - streamlines = [(line + c) * voxel_size for c in coords for line in X] - - return streamlines - - -if __name__ == '__main__': - rng = np.random.RandomState(42) - - width = 4 # Coronal - height = 5 # Sagittal - depth = 7 # Axial - - voxel_size = np.array((1.0, 3.0, 2.0)) - - # Generate a random mask with voxel order RAS+. - mask = rng.rand(width, height, depth) > 0.8 - mask = (255 * mask).astype(np.uint8) - - # Build tractogram - streamlines = mark_the_spot(mask) - tractogram = nib.streamlines.Tractogram(streamlines) - - # Build header - affine = np.eye(4) - affine[range(3), range(3)] = voxel_size - header = { - Field.DIMENSIONS: (width, height, depth), - Field.VOXEL_SIZES: voxel_size, - Field.VOXEL_TO_RASMM: affine, - Field.VOXEL_ORDER: 'RAS', - } - - # Save the standard mask. - nii = nib.Nifti1Image(mask, affine=affine) - nib.save(nii, 'standard.nii.gz') - - # Save the standard tractogram in every available file format. - for ext, cls in FORMATS.items(): - tfile = cls(tractogram, header) - nib.streamlines.save(tfile, 'standard' + ext) diff --git a/nibabel/tests/data/make_moved_anat.py b/nibabel/tests/data/make_moved_anat.py deleted file mode 100644 index 678b5dfdeb..0000000000 --- a/nibabel/tests/data/make_moved_anat.py +++ /dev/null @@ -1,21 +0,0 @@ -"""Make anatomical image with altered affine - -* Add some rotations and translations to affine; -* Save as ``.nii`` file so SPM can read it. - -See ``resample_using_spm.m`` for processing of this generated image by SPM. -""" - -import numpy as np - -import nibabel as nib -from nibabel.affines import from_matvec -from nibabel.eulerangles import euler2mat - -if __name__ == '__main__': - img = nib.load('anatomical.nii') - some_rotations = euler2mat(0.1, 0.2, 0.3) - extra_affine = from_matvec(some_rotations, [3, 4, 5]) - moved_anat = nib.Nifti1Image(img.dataobj, extra_affine.dot(img.affine), img.header) - moved_anat.set_data_dtype(np.float32) - nib.save(moved_anat, 'anat_moved.nii') diff --git a/nibabel/tests/data/matlab_nan.tck b/nibabel/tests/data/matlab_nan.tck deleted file mode 100644 index 6afc9af60c..0000000000 Binary files a/nibabel/tests/data/matlab_nan.tck and /dev/null differ diff --git a/nibabel/tests/data/minc1-no-att.mnc b/nibabel/tests/data/minc1-no-att.mnc deleted file mode 100644 index b1ce938403..0000000000 Binary files a/nibabel/tests/data/minc1-no-att.mnc and /dev/null differ diff --git a/nibabel/tests/data/minc1_1_scale.mnc b/nibabel/tests/data/minc1_1_scale.mnc deleted file mode 100644 index c2c97a488d..0000000000 Binary files a/nibabel/tests/data/minc1_1_scale.mnc and /dev/null differ diff --git a/nibabel/tests/data/minc1_4d.mnc b/nibabel/tests/data/minc1_4d.mnc deleted file mode 100644 index 7c7f4cf21b..0000000000 Binary files a/nibabel/tests/data/minc1_4d.mnc and /dev/null differ diff --git a/nibabel/tests/data/minc2-4d-d.mnc b/nibabel/tests/data/minc2-4d-d.mnc deleted file mode 100644 index 0000fa49e2..0000000000 Binary files a/nibabel/tests/data/minc2-4d-d.mnc and /dev/null differ diff --git a/nibabel/tests/data/minc2-no-att.mnc b/nibabel/tests/data/minc2-no-att.mnc deleted file mode 100644 index 15052c271d..0000000000 Binary files a/nibabel/tests/data/minc2-no-att.mnc and /dev/null differ diff --git a/nibabel/tests/data/minc2_1_scale.mnc b/nibabel/tests/data/minc2_1_scale.mnc deleted file mode 100644 index 37319be132..0000000000 Binary files a/nibabel/tests/data/minc2_1_scale.mnc and /dev/null differ diff --git a/nibabel/tests/data/minc2_4d.mnc b/nibabel/tests/data/minc2_4d.mnc deleted file mode 100644 index a0bc0b9c6b..0000000000 Binary files a/nibabel/tests/data/minc2_4d.mnc and /dev/null differ diff --git a/nibabel/tests/data/minc2_baddim.mnc b/nibabel/tests/data/minc2_baddim.mnc deleted file mode 100644 index c7de97bd5e..0000000000 Binary files a/nibabel/tests/data/minc2_baddim.mnc and /dev/null differ diff --git a/nibabel/tests/data/multiline_header_field.tck b/nibabel/tests/data/multiline_header_field.tck deleted file mode 100644 index 42ebedc43a..0000000000 Binary files a/nibabel/tests/data/multiline_header_field.tck and /dev/null differ diff --git a/nibabel/tests/data/nifti1.hdr b/nibabel/tests/data/nifti1.hdr deleted file mode 100644 index 988b843481..0000000000 Binary files a/nibabel/tests/data/nifti1.hdr and /dev/null differ diff --git a/nibabel/tests/data/nifti2.hdr b/nibabel/tests/data/nifti2.hdr deleted file mode 100644 index 885a59dc24..0000000000 Binary files a/nibabel/tests/data/nifti2.hdr and /dev/null differ diff --git a/nibabel/tests/data/no_header_end.tck b/nibabel/tests/data/no_header_end.tck deleted file mode 100644 index 2304f41921..0000000000 Binary files a/nibabel/tests/data/no_header_end.tck and /dev/null differ diff --git a/nibabel/tests/data/no_header_end_eof.tck b/nibabel/tests/data/no_header_end_eof.tck deleted file mode 100644 index ceb79ebfb7..0000000000 --- a/nibabel/tests/data/no_header_end_eof.tck +++ /dev/null @@ -1,4 +0,0 @@ -mrtrix tracks -count: 0000000000 -datatype: Float32LE -file: . 67 \ No newline at end of file diff --git a/nibabel/tests/data/no_magic_number.tck b/nibabel/tests/data/no_magic_number.tck deleted file mode 100644 index 3a4fe7de72..0000000000 Binary files a/nibabel/tests/data/no_magic_number.tck and /dev/null differ diff --git a/nibabel/tests/data/phantom_EPI_asc_CLEAR_2_1.PAR b/nibabel/tests/data/phantom_EPI_asc_CLEAR_2_1.PAR deleted file mode 100644 index 1bbe83fbd6..0000000000 --- a/nibabel/tests/data/phantom_EPI_asc_CLEAR_2_1.PAR +++ /dev/null @@ -1,129 +0,0 @@ -# === DATA DESCRIPTION FILE ====================================================== -# -# CAUTION - Investigational device. -# Limited by Federal Law to investigational use. -# -# Dataset name: E:\\Export\phantom_EPI_asc_CLEAR_2_1 -# -# CLINICAL TRYOUT Research image export tool V4.2 -# -# === GENERAL INFORMATION ======================================================== -# -. Patient name : phantom -. Examination name : Konvertertest -. Protocol name : EPI_asc CLEAR -. Examination date/time : 2014.02.14 / 09:00:57 -. Series Type : Image MRSERIES -. Acquisition nr : 2 -. Reconstruction nr : 1 -. Scan Duration [sec] : 14 -. Max. number of cardiac phases : 1 -. Max. number of echoes : 1 -. Max. number of slices/locations : 9 -. Max. number of dynamics : 3 -. Max. number of mixes : 1 -. Patient position : Head First Supine -. Preparation direction : Anterior-Posterior -. Technique : FEEPI -. Scan resolution (x, y) : 64 39 -. Scan mode : MS -. Repetition time [ms] : 2000.000 -. FOV (ap,fh,rl) [mm] : 240.000 70.000 240.000 -. Water Fat shift [pixels] : 11.050 -. Angulation midslice(ap,fh,rl)[degr]: -13.265 0.000 0.000 -. Off Centre midslice(ap,fh,rl) [mm] : 2.508 30.339 -16.032 -. Flow compensation <0=no 1=yes> ? : 0 -. Presaturation <0=no 1=yes> ? : 0 -. Phase encoding velocity [cm/sec] : 0.000000 0.000000 0.000000 -. MTC <0=no 1=yes> ? : 0 -. SPIR <0=no 1=yes> ? : 1 -. EPI factor <0,1=no EPI> : 39 -. Dynamic scan <0=no 1=yes> ? : 1 -. Diffusion <0=no 1=yes> ? : 0 -. Diffusion echo time [ms] : 0.0000 -. Max. number of diffusion values : 1 -. Max. number of gradient orients : 1 -. Number of label types <0=no ASL> : 0 -# -# === PIXEL VALUES ============================================================= -# PV = pixel value in REC file, FP = floating point value, DV = displayed value on console -# RS = rescale slope, RI = rescale intercept, SS = scale slope -# DV = PV * RS + RI FP = DV / (RS * SS) -# -# === IMAGE INFORMATION DEFINITION ============================================= -# The rest of this file contains ONE line per image, this line contains the following information: -# -# slice number (integer) -# echo number (integer) -# dynamic scan number (integer) -# cardiac phase number (integer) -# image_type_mr (integer) -# scanning sequence (integer) -# index in REC file (in images) (integer) -# image pixel size (in bits) (integer) -# scan percentage (integer) -# recon resolution (x y) (2*integer) -# rescale intercept (float) -# rescale slope (float) -# scale slope (float) -# window center (integer) -# window width (integer) -# image angulation (ap,fh,rl in degrees ) (3*float) -# image offcentre (ap,fh,rl in mm ) (3*float) -# slice thickness (in mm ) (float) -# slice gap (in mm ) (float) -# image_display_orientation (integer) -# slice orientation ( TRA/SAG/COR ) (integer) -# fmri_status_indication (integer) -# image_type_ed_es (end diast/end syst) (integer) -# pixel spacing (x,y) (in mm) (2*float) -# echo_time (float) -# dyn_scan_begin_time (float) -# trigger_time (float) -# diffusion_b_factor (float) -# number of averages (integer) -# image_flip_angle (in degrees) (float) -# cardiac frequency (bpm) (integer) -# minimum RR-interval (in ms) (integer) -# maximum RR-interval (in ms) (integer) -# TURBO factor <0=no turbo> (integer) -# Inversion delay (in ms) (float) -# diffusion b value number (imagekey!) (integer) -# gradient orientation number (imagekey!) (integer) -# contrast type (string) -# diffusion anisotropy type (string) -# diffusion (ap, fh, rl) (3*float) -# label type (ASL) (imagekey!) (integer) -# -# === IMAGE INFORMATION ========================================================== -# sl ec dyn ph ty idx pix scan% rec size (re)scale window angulation offcentre thick gap info spacing echo dtime ttime diff avg flip freq RR-int turbo delay b grad cont anis diffusion L.ty - - 1 1 1 1 0 2 0 16 62 64 64 0.00000 1.29035 4.28404e-003 1070 1860 -13.26 -0.00 -0.00 2.51 -0.81 -8.69 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 2 1 1 1 0 2 1 16 62 64 64 0.00000 1.29035 4.28404e-003 1122 1951 -13.26 -0.00 -0.00 2.51 6.98 -10.53 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 3 1 1 1 0 2 2 16 62 64 64 0.00000 1.29035 4.28404e-003 1137 1977 -13.26 -0.00 -0.00 2.51 14.77 -12.36 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 4 1 1 1 0 2 3 16 62 64 64 0.00000 1.29035 4.28404e-003 1217 2116 -13.26 -0.00 -0.00 2.51 22.55 -14.20 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 5 1 1 1 0 2 4 16 62 64 64 0.00000 1.29035 4.28404e-003 1216 2113 -13.26 -0.00 -0.00 2.51 30.34 -16.03 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 6 1 1 1 0 2 5 16 62 64 64 0.00000 1.29035 4.28404e-003 1141 1983 -13.26 -0.00 -0.00 2.51 38.13 -17.87 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 7 1 1 1 0 2 6 16 62 64 64 0.00000 1.29035 4.28404e-003 1119 1945 -13.26 -0.00 -0.00 2.51 45.91 -19.70 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 8 1 1 1 0 2 7 16 62 64 64 0.00000 1.29035 4.28404e-003 1097 1907 -13.26 -0.00 -0.00 2.51 53.70 -21.54 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 9 1 1 1 0 2 8 16 62 64 64 0.00000 1.29035 4.28404e-003 1146 1991 -13.26 -0.00 -0.00 2.51 61.49 -23.37 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 1 1 2 1 0 2 9 16 62 64 64 0.00000 1.29035 4.28404e-003 1071 1863 -13.26 -0.00 -0.00 2.51 -0.81 -8.69 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 2 1 2 1 0 2 10 16 62 64 64 0.00000 1.29035 4.28404e-003 1123 1953 -13.26 -0.00 -0.00 2.51 6.98 -10.53 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 3 1 2 1 0 2 11 16 62 64 64 0.00000 1.29035 4.28404e-003 1135 1973 -13.26 -0.00 -0.00 2.51 14.77 -12.36 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 4 1 2 1 0 2 12 16 62 64 64 0.00000 1.29035 4.28404e-003 1209 2101 -13.26 -0.00 -0.00 2.51 22.55 -14.20 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 5 1 2 1 0 2 13 16 62 64 64 0.00000 1.29035 4.28404e-003 1215 2113 -13.26 -0.00 -0.00 2.51 30.34 -16.03 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 6 1 2 1 0 2 14 16 62 64 64 0.00000 1.29035 4.28404e-003 1145 1990 -13.26 -0.00 -0.00 2.51 38.13 -17.87 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 7 1 2 1 0 2 15 16 62 64 64 0.00000 1.29035 4.28404e-003 1119 1945 -13.26 -0.00 -0.00 2.51 45.91 -19.70 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 8 1 2 1 0 2 16 16 62 64 64 0.00000 1.29035 4.28404e-003 1093 1899 -13.26 -0.00 -0.00 2.51 53.70 -21.54 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 9 1 2 1 0 2 17 16 62 64 64 0.00000 1.29035 4.28404e-003 1150 1999 -13.26 -0.00 -0.00 2.51 61.49 -23.37 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 1 1 3 1 0 2 18 16 62 64 64 0.00000 1.29035 4.28404e-003 1070 1860 -13.26 -0.00 -0.00 2.51 -0.81 -8.69 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 2 1 3 1 0 2 19 16 62 64 64 0.00000 1.29035 4.28404e-003 1125 1955 -13.26 -0.00 -0.00 2.51 6.98 -10.53 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 3 1 3 1 0 2 20 16 62 64 64 0.00000 1.29035 4.28404e-003 1135 1973 -13.26 -0.00 -0.00 2.51 14.77 -12.36 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 4 1 3 1 0 2 21 16 62 64 64 0.00000 1.29035 4.28404e-003 1211 2105 -13.26 -0.00 -0.00 2.51 22.55 -14.20 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 5 1 3 1 0 2 22 16 62 64 64 0.00000 1.29035 4.28404e-003 1218 2118 -13.26 -0.00 -0.00 2.51 30.34 -16.03 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 6 1 3 1 0 2 23 16 62 64 64 0.00000 1.29035 4.28404e-003 1143 1987 -13.26 -0.00 -0.00 2.51 38.13 -17.87 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 7 1 3 1 0 2 24 16 62 64 64 0.00000 1.29035 4.28404e-003 1120 1947 -13.26 -0.00 -0.00 2.51 45.91 -19.70 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 8 1 3 1 0 2 25 16 62 64 64 0.00000 1.29035 4.28404e-003 1093 1901 -13.26 -0.00 -0.00 2.51 53.70 -21.54 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 9 1 3 1 0 2 26 16 62 64 64 0.00000 1.29035 4.28404e-003 1151 2001 -13.26 -0.00 -0.00 2.51 61.49 -23.37 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - -# === END OF DATA DESCRIPTION FILE =============================================== diff --git a/nibabel/tests/data/phantom_EPI_asc_CLEAR_2_1.REC b/nibabel/tests/data/phantom_EPI_asc_CLEAR_2_1.REC deleted file mode 100644 index 958095f2a8..0000000000 Binary files a/nibabel/tests/data/phantom_EPI_asc_CLEAR_2_1.REC and /dev/null differ diff --git a/nibabel/tests/data/phantom_fake_dualTR.PAR b/nibabel/tests/data/phantom_fake_dualTR.PAR deleted file mode 100644 index 00c652978d..0000000000 --- a/nibabel/tests/data/phantom_fake_dualTR.PAR +++ /dev/null @@ -1,127 +0,0 @@ -# === DATA DESCRIPTION FILE ====================================================== -# -# CAUTION - Investigational device. -# Limited by Federal Law to investigational use. -# -# Dataset name: E:\\Export\phantom_EPI_asc_CLEAR_2_1 -# -# CLINICAL TRYOUT Research image export tool V4.1 -# -# === GENERAL INFORMATION ======================================================== -# -. Patient name : phantom -. Examination name : Konvertertest -. Protocol name : EPI_asc CLEAR -. Examination date/time : 2014.02.14 / 09:00:57 -. Series Type : Image MRSERIES -. Acquisition nr : 2 -. Reconstruction nr : 1 -. Scan Duration [sec] : 14 -. Max. number of cardiac phases : 1 -. Max. number of echoes : 1 -. Max. number of slices/locations : 9 -. Max. number of dynamics : 3 -. Max. number of mixes : 1 -. Patient position : Head First Supine -. Preparation direction : Anterior-Posterior -. Technique : FEEPI -. Scan resolution (x, y) : 64 39 -. Scan mode : MS -. Repetition time [ms] : 2000.000 500.00 -. FOV (ap,fh,rl) [mm] : 240.000 70.000 240.000 -. Water Fat shift [pixels] : 11.050 -. Angulation midslice(ap,fh,rl)[degr]: -13.265 0.000 0.000 -. Off Centre midslice(ap,fh,rl) [mm] : 2.508 30.339 -16.032 -. Flow compensation <0=no 1=yes> ? : 0 -. Presaturation <0=no 1=yes> ? : 0 -. Phase encoding velocity [cm/sec] : 0.000000 0.000000 0.000000 -. MTC <0=no 1=yes> ? : 0 -. SPIR <0=no 1=yes> ? : 1 -. EPI factor <0,1=no EPI> : 39 -. Dynamic scan <0=no 1=yes> ? : 1 -. Diffusion <0=no 1=yes> ? : 0 -. Diffusion echo time [ms] : 0.0000 -. Max. number of diffusion values : 1 -. Max. number of gradient orients : 1 -# -# === PIXEL VALUES ============================================================= -# PV = pixel value in REC file, FP = floating point value, DV = displayed value on console -# RS = rescale slope, RI = rescale intercept, SS = scale slope -# DV = PV * RS + RI FP = DV / (RS * SS) -# -# === IMAGE INFORMATION DEFINITION ============================================= -# The rest of this file contains ONE line per image, this line contains the following information: -# -# slice number (integer) -# echo number (integer) -# dynamic scan number (integer) -# cardiac phase number (integer) -# image_type_mr (integer) -# scanning sequence (integer) -# index in REC file (in images) (integer) -# image pixel size (in bits) (integer) -# scan percentage (integer) -# recon resolution (x y) (2*integer) -# rescale intercept (float) -# rescale slope (float) -# scale slope (float) -# window center (integer) -# window width (integer) -# image angulation (ap,fh,rl in degrees ) (3*float) -# image offcentre (ap,fh,rl in mm ) (3*float) -# slice thickness (in mm ) (float) -# slice gap (in mm ) (float) -# image_display_orientation (integer) -# slice orientation ( TRA/SAG/COR ) (integer) -# fmri_status_indication (integer) -# image_type_ed_es (end diast/end syst) (integer) -# pixel spacing (x,y) (in mm) (2*float) -# echo_time (float) -# dyn_scan_begin_time (float) -# trigger_time (float) -# diffusion_b_factor (float) -# number of averages (integer) -# image_flip_angle (in degrees) (float) -# cardiac frequency (bpm) (integer) -# minimum RR-interval (in ms) (integer) -# maximum RR-interval (in ms) (integer) -# TURBO factor <0=no turbo> (integer) -# Inversion delay (in ms) (float) -# diffusion b value number (imagekey!) (integer) -# gradient orientation number (imagekey!) (integer) -# contrast type (string) -# diffusion anisotropy type (string) -# diffusion (ap, fh, rl) (3*float) -# -# === IMAGE INFORMATION ========================================================== -# sl ec dyn ph ty idx pix scan% rec size (re)scale window angulation offcentre thick gap info spacing echo dtime ttime diff avg flip freq RR-int turbo delay b grad cont anis diffusion - - 1 1 1 1 0 2 0 16 62 64 64 0.00000 1.29035 4.28404e-003 1070 1860 -13.26 -0.00 -0.00 2.51 -0.81 -8.69 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 2 1 1 1 0 2 1 16 62 64 64 0.00000 1.29035 4.28404e-003 1122 1951 -13.26 -0.00 -0.00 2.51 6.98 -10.53 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 3 1 1 1 0 2 2 16 62 64 64 0.00000 1.29035 4.28404e-003 1137 1977 -13.26 -0.00 -0.00 2.51 14.77 -12.36 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 4 1 1 1 0 2 3 16 62 64 64 0.00000 1.29035 4.28404e-003 1217 2116 -13.26 -0.00 -0.00 2.51 22.55 -14.20 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 5 1 1 1 0 2 4 16 62 64 64 0.00000 1.29035 4.28404e-003 1216 2113 -13.26 -0.00 -0.00 2.51 30.34 -16.03 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 6 1 1 1 0 2 5 16 62 64 64 0.00000 1.29035 4.28404e-003 1141 1983 -13.26 -0.00 -0.00 2.51 38.13 -17.87 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 7 1 1 1 0 2 6 16 62 64 64 0.00000 1.29035 4.28404e-003 1119 1945 -13.26 -0.00 -0.00 2.51 45.91 -19.70 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 8 1 1 1 0 2 7 16 62 64 64 0.00000 1.29035 4.28404e-003 1097 1907 -13.26 -0.00 -0.00 2.51 53.70 -21.54 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 9 1 1 1 0 2 8 16 62 64 64 0.00000 1.29035 4.28404e-003 1146 1991 -13.26 -0.00 -0.00 2.51 61.49 -23.37 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 1 1 2 1 0 2 9 16 62 64 64 0.00000 1.29035 4.28404e-003 1071 1863 -13.26 -0.00 -0.00 2.51 -0.81 -8.69 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 2 1 2 1 0 2 10 16 62 64 64 0.00000 1.29035 4.28404e-003 1123 1953 -13.26 -0.00 -0.00 2.51 6.98 -10.53 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 3 1 2 1 0 2 11 16 62 64 64 0.00000 1.29035 4.28404e-003 1135 1973 -13.26 -0.00 -0.00 2.51 14.77 -12.36 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 4 1 2 1 0 2 12 16 62 64 64 0.00000 1.29035 4.28404e-003 1209 2101 -13.26 -0.00 -0.00 2.51 22.55 -14.20 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 5 1 2 1 0 2 13 16 62 64 64 0.00000 1.29035 4.28404e-003 1215 2113 -13.26 -0.00 -0.00 2.51 30.34 -16.03 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 6 1 2 1 0 2 14 16 62 64 64 0.00000 1.29035 4.28404e-003 1145 1990 -13.26 -0.00 -0.00 2.51 38.13 -17.87 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 7 1 2 1 0 2 15 16 62 64 64 0.00000 1.29035 4.28404e-003 1119 1945 -13.26 -0.00 -0.00 2.51 45.91 -19.70 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 8 1 2 1 0 2 16 16 62 64 64 0.00000 1.29035 4.28404e-003 1093 1899 -13.26 -0.00 -0.00 2.51 53.70 -21.54 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 9 1 2 1 0 2 17 16 62 64 64 0.00000 1.29035 4.28404e-003 1150 1999 -13.26 -0.00 -0.00 2.51 61.49 -23.37 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 1 1 3 1 0 2 18 16 62 64 64 0.00000 1.29035 4.28404e-003 1070 1860 -13.26 -0.00 -0.00 2.51 -0.81 -8.69 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 2 1 3 1 0 2 19 16 62 64 64 0.00000 1.29035 4.28404e-003 1125 1955 -13.26 -0.00 -0.00 2.51 6.98 -10.53 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 3 1 3 1 0 2 20 16 62 64 64 0.00000 1.29035 4.28404e-003 1135 1973 -13.26 -0.00 -0.00 2.51 14.77 -12.36 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 4 1 3 1 0 2 21 16 62 64 64 0.00000 1.29035 4.28404e-003 1211 2105 -13.26 -0.00 -0.00 2.51 22.55 -14.20 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 5 1 3 1 0 2 22 16 62 64 64 0.00000 1.29035 4.28404e-003 1218 2118 -13.26 -0.00 -0.00 2.51 30.34 -16.03 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 6 1 3 1 0 2 23 16 62 64 64 0.00000 1.29035 4.28404e-003 1143 1987 -13.26 -0.00 -0.00 2.51 38.13 -17.87 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 7 1 3 1 0 2 24 16 62 64 64 0.00000 1.29035 4.28404e-003 1120 1947 -13.26 -0.00 -0.00 2.51 45.91 -19.70 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 8 1 3 1 0 2 25 16 62 64 64 0.00000 1.29035 4.28404e-003 1093 1901 -13.26 -0.00 -0.00 2.51 53.70 -21.54 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 9 1 3 1 0 2 26 16 62 64 64 0.00000 1.29035 4.28404e-003 1151 2001 -13.26 -0.00 -0.00 2.51 61.49 -23.37 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - -# === END OF DATA DESCRIPTION FILE =============================================== diff --git a/nibabel/tests/data/phantom_fake_v4.PAR b/nibabel/tests/data/phantom_fake_v4.PAR deleted file mode 100644 index eba59afe05..0000000000 --- a/nibabel/tests/data/phantom_fake_v4.PAR +++ /dev/null @@ -1,120 +0,0 @@ -# === DATA DESCRIPTION FILE ====================================================== -# -# CAUTION - Investigational device. -# Limited by Federal Law to investigational use. -# -# Dataset name: E:\\Export\phantom_EPI_asc_CLEAR_2_1 -# -# CLINICAL TRYOUT Research image export tool V4 -# -# === GENERAL INFORMATION ======================================================== -# -. Patient name : phantom -. Examination name : Konvertertest -. Protocol name : EPI_asc CLEAR -. Examination date/time : 2014.02.14 / 09:00:57 -. Series Type : Image MRSERIES -. Acquisition nr : 2 -. Reconstruction nr : 1 -. Scan Duration [sec] : 14 -. Max. number of cardiac phases : 1 -. Max. number of echoes : 1 -. Max. number of slices/locations : 9 -. Max. number of dynamics : 3 -. Max. number of mixes : 1 -. Patient position : Head First Supine -. Preparation direction : Anterior-Posterior -. Technique : FEEPI -. Scan resolution (x, y) : 64 39 -. Scan mode : MS -. Repetition time [ms] : 2000.000 -. FOV (ap,fh,rl) [mm] : 240.000 70.000 240.000 -. Water Fat shift [pixels] : 11.050 -. Angulation midslice(ap,fh,rl)[degr]: -13.265 0.000 0.000 -. Off Centre midslice(ap,fh,rl) [mm] : 2.508 30.339 -16.032 -. Flow compensation <0=no 1=yes> ? : 0 -. Presaturation <0=no 1=yes> ? : 0 -. Phase encoding velocity [cm/sec] : 0.000000 0.000000 0.000000 -. MTC <0=no 1=yes> ? : 0 -. SPIR <0=no 1=yes> ? : 1 -. EPI factor <0,1=no EPI> : 39 -. Dynamic scan <0=no 1=yes> ? : 1 -. Diffusion <0=no 1=yes> ? : 0 -. Diffusion echo time [ms] : 0.0000 -# -# === PIXEL VALUES ============================================================= -# PV = pixel value in REC file, FP = floating point value, DV = displayed value on console -# RS = rescale slope, RI = rescale intercept, SS = scale slope -# DV = PV * RS + RI FP = DV / (RS * SS) -# -# === IMAGE INFORMATION DEFINITION ============================================= -# The rest of this file contains ONE line per image, this line contains the following information: -# -# slice number (integer) -# echo number (integer) -# dynamic scan number (integer) -# cardiac phase number (integer) -# image_type_mr (integer) -# scanning sequence (integer) -# index in REC file (in images) (integer) -# image pixel size (in bits) (integer) -# scan percentage (integer) -# recon resolution (x y) (2*integer) -# rescale intercept (float) -# rescale slope (float) -# scale slope (float) -# window center (integer) -# window width (integer) -# image angulation (ap,fh,rl in degrees ) (3*float) -# image offcentre (ap,fh,rl in mm ) (3*float) -# slice thickness (in mm ) (float) -# slice gap (in mm ) (float) -# image_display_orientation (integer) -# slice orientation ( TRA/SAG/COR ) (integer) -# fmri_status_indication (integer) -# image_type_ed_es (end diast/end syst) (integer) -# pixel spacing (x,y) (in mm) (2*float) -# echo_time (float) -# dyn_scan_begin_time (float) -# trigger_time (float) -# diffusion_b_factor (float) -# number of averages (integer) -# image_flip_angle (in degrees) (float) -# cardiac frequency (bpm) (integer) -# minimum RR-interval (in ms) (integer) -# maximum RR-interval (in ms) (integer) -# TURBO factor <0=no turbo> (integer) -# Inversion delay (in ms) (float) -# -# === IMAGE INFORMATION ========================================================== -# sl ec dyn ph ty idx pix scan% rec size (re)scale window angulation offcentre thick gap info spacing echo dtime ttime diff avg flip freq RR-int turbo delay - - 1 1 1 1 0 2 0 16 62 64 64 0.00000 1.29035 4.28404e-003 1070 1860 -13.26 -0.00 -0.00 2.51 -0.81 -8.69 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 - 2 1 1 1 0 2 1 16 62 64 64 0.00000 1.29035 4.28404e-003 1122 1951 -13.26 -0.00 -0.00 2.51 6.98 -10.53 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 - 3 1 1 1 0 2 2 16 62 64 64 0.00000 1.29035 4.28404e-003 1137 1977 -13.26 -0.00 -0.00 2.51 14.77 -12.36 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 - 4 1 1 1 0 2 3 16 62 64 64 0.00000 1.29035 4.28404e-003 1217 2116 -13.26 -0.00 -0.00 2.51 22.55 -14.20 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 - 5 1 1 1 0 2 4 16 62 64 64 0.00000 1.29035 4.28404e-003 1216 2113 -13.26 -0.00 -0.00 2.51 30.34 -16.03 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 - 6 1 1 1 0 2 5 16 62 64 64 0.00000 1.29035 4.28404e-003 1141 1983 -13.26 -0.00 -0.00 2.51 38.13 -17.87 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 - 7 1 1 1 0 2 6 16 62 64 64 0.00000 1.29035 4.28404e-003 1119 1945 -13.26 -0.00 -0.00 2.51 45.91 -19.70 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 - 8 1 1 1 0 2 7 16 62 64 64 0.00000 1.29035 4.28404e-003 1097 1907 -13.26 -0.00 -0.00 2.51 53.70 -21.54 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 - 9 1 1 1 0 2 8 16 62 64 64 0.00000 1.29035 4.28404e-003 1146 1991 -13.26 -0.00 -0.00 2.51 61.49 -23.37 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 - 1 1 2 1 0 2 9 16 62 64 64 0.00000 1.29035 4.28404e-003 1071 1863 -13.26 -0.00 -0.00 2.51 -0.81 -8.69 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 - 2 1 2 1 0 2 10 16 62 64 64 0.00000 1.29035 4.28404e-003 1123 1953 -13.26 -0.00 -0.00 2.51 6.98 -10.53 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 - 3 1 2 1 0 2 11 16 62 64 64 0.00000 1.29035 4.28404e-003 1135 1973 -13.26 -0.00 -0.00 2.51 14.77 -12.36 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 - 4 1 2 1 0 2 12 16 62 64 64 0.00000 1.29035 4.28404e-003 1209 2101 -13.26 -0.00 -0.00 2.51 22.55 -14.20 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 - 5 1 2 1 0 2 13 16 62 64 64 0.00000 1.29035 4.28404e-003 1215 2113 -13.26 -0.00 -0.00 2.51 30.34 -16.03 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 - 6 1 2 1 0 2 14 16 62 64 64 0.00000 1.29035 4.28404e-003 1145 1990 -13.26 -0.00 -0.00 2.51 38.13 -17.87 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 - 7 1 2 1 0 2 15 16 62 64 64 0.00000 1.29035 4.28404e-003 1119 1945 -13.26 -0.00 -0.00 2.51 45.91 -19.70 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 - 8 1 2 1 0 2 16 16 62 64 64 0.00000 1.29035 4.28404e-003 1093 1899 -13.26 -0.00 -0.00 2.51 53.70 -21.54 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 - 9 1 2 1 0 2 17 16 62 64 64 0.00000 1.29035 4.28404e-003 1150 1999 -13.26 -0.00 -0.00 2.51 61.49 -23.37 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 - 1 1 3 1 0 2 18 16 62 64 64 0.00000 1.29035 4.28404e-003 1070 1860 -13.26 -0.00 -0.00 2.51 -0.81 -8.69 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 - 2 1 3 1 0 2 19 16 62 64 64 0.00000 1.29035 4.28404e-003 1125 1955 -13.26 -0.00 -0.00 2.51 6.98 -10.53 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 - 3 1 3 1 0 2 20 16 62 64 64 0.00000 1.29035 4.28404e-003 1135 1973 -13.26 -0.00 -0.00 2.51 14.77 -12.36 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 - 4 1 3 1 0 2 21 16 62 64 64 0.00000 1.29035 4.28404e-003 1211 2105 -13.26 -0.00 -0.00 2.51 22.55 -14.20 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 - 5 1 3 1 0 2 22 16 62 64 64 0.00000 1.29035 4.28404e-003 1218 2118 -13.26 -0.00 -0.00 2.51 30.34 -16.03 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 - 6 1 3 1 0 2 23 16 62 64 64 0.00000 1.29035 4.28404e-003 1143 1987 -13.26 -0.00 -0.00 2.51 38.13 -17.87 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 - 7 1 3 1 0 2 24 16 62 64 64 0.00000 1.29035 4.28404e-003 1120 1947 -13.26 -0.00 -0.00 2.51 45.91 -19.70 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 - 8 1 3 1 0 2 25 16 62 64 64 0.00000 1.29035 4.28404e-003 1093 1901 -13.26 -0.00 -0.00 2.51 53.70 -21.54 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 - 9 1 3 1 0 2 26 16 62 64 64 0.00000 1.29035 4.28404e-003 1151 2001 -13.26 -0.00 -0.00 2.51 61.49 -23.37 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 - -# === END OF DATA DESCRIPTION FILE =============================================== diff --git a/nibabel/tests/data/phantom_fake_v4_1.PAR b/nibabel/tests/data/phantom_fake_v4_1.PAR deleted file mode 100644 index ba5b57f6d9..0000000000 --- a/nibabel/tests/data/phantom_fake_v4_1.PAR +++ /dev/null @@ -1,127 +0,0 @@ -# === DATA DESCRIPTION FILE ====================================================== -# -# CAUTION - Investigational device. -# Limited by Federal Law to investigational use. -# -# Dataset name: E:\\Export\phantom_EPI_asc_CLEAR_2_1 -# -# CLINICAL TRYOUT Research image export tool V4.1 -# -# === GENERAL INFORMATION ======================================================== -# -. Patient name : phantom -. Examination name : Konvertertest -. Protocol name : EPI_asc CLEAR -. Examination date/time : 2014.02.14 / 09:00:57 -. Series Type : Image MRSERIES -. Acquisition nr : 2 -. Reconstruction nr : 1 -. Scan Duration [sec] : 14 -. Max. number of cardiac phases : 1 -. Max. number of echoes : 1 -. Max. number of slices/locations : 9 -. Max. number of dynamics : 3 -. Max. number of mixes : 1 -. Patient position : Head First Supine -. Preparation direction : Anterior-Posterior -. Technique : FEEPI -. Scan resolution (x, y) : 64 39 -. Scan mode : MS -. Repetition time [ms] : 2000.000 -. FOV (ap,fh,rl) [mm] : 240.000 70.000 240.000 -. Water Fat shift [pixels] : 11.050 -. Angulation midslice(ap,fh,rl)[degr]: -13.265 0.000 0.000 -. Off Centre midslice(ap,fh,rl) [mm] : 2.508 30.339 -16.032 -. Flow compensation <0=no 1=yes> ? : 0 -. Presaturation <0=no 1=yes> ? : 0 -. Phase encoding velocity [cm/sec] : 0.000000 0.000000 0.000000 -. MTC <0=no 1=yes> ? : 0 -. SPIR <0=no 1=yes> ? : 1 -. EPI factor <0,1=no EPI> : 39 -. Dynamic scan <0=no 1=yes> ? : 1 -. Diffusion <0=no 1=yes> ? : 0 -. Diffusion echo time [ms] : 0.0000 -. Max. number of diffusion values : 1 -. Max. number of gradient orients : 1 -# -# === PIXEL VALUES ============================================================= -# PV = pixel value in REC file, FP = floating point value, DV = displayed value on console -# RS = rescale slope, RI = rescale intercept, SS = scale slope -# DV = PV * RS + RI FP = DV / (RS * SS) -# -# === IMAGE INFORMATION DEFINITION ============================================= -# The rest of this file contains ONE line per image, this line contains the following information: -# -# slice number (integer) -# echo number (integer) -# dynamic scan number (integer) -# cardiac phase number (integer) -# image_type_mr (integer) -# scanning sequence (integer) -# index in REC file (in images) (integer) -# image pixel size (in bits) (integer) -# scan percentage (integer) -# recon resolution (x y) (2*integer) -# rescale intercept (float) -# rescale slope (float) -# scale slope (float) -# window center (integer) -# window width (integer) -# image angulation (ap,fh,rl in degrees ) (3*float) -# image offcentre (ap,fh,rl in mm ) (3*float) -# slice thickness (in mm ) (float) -# slice gap (in mm ) (float) -# image_display_orientation (integer) -# slice orientation ( TRA/SAG/COR ) (integer) -# fmri_status_indication (integer) -# image_type_ed_es (end diast/end syst) (integer) -# pixel spacing (x,y) (in mm) (2*float) -# echo_time (float) -# dyn_scan_begin_time (float) -# trigger_time (float) -# diffusion_b_factor (float) -# number of averages (integer) -# image_flip_angle (in degrees) (float) -# cardiac frequency (bpm) (integer) -# minimum RR-interval (in ms) (integer) -# maximum RR-interval (in ms) (integer) -# TURBO factor <0=no turbo> (integer) -# Inversion delay (in ms) (float) -# diffusion b value number (imagekey!) (integer) -# gradient orientation number (imagekey!) (integer) -# contrast type (string) -# diffusion anisotropy type (string) -# diffusion (ap, fh, rl) (3*float) -# -# === IMAGE INFORMATION ========================================================== -# sl ec dyn ph ty idx pix scan% rec size (re)scale window angulation offcentre thick gap info spacing echo dtime ttime diff avg flip freq RR-int turbo delay b grad cont anis diffusion - - 1 1 1 1 0 2 0 16 62 64 64 0.00000 1.29035 4.28404e-003 1070 1860 -13.26 -0.00 -0.00 2.51 -0.81 -8.69 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 2 1 1 1 0 2 1 16 62 64 64 0.00000 1.29035 4.28404e-003 1122 1951 -13.26 -0.00 -0.00 2.51 6.98 -10.53 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 3 1 1 1 0 2 2 16 62 64 64 0.00000 1.29035 4.28404e-003 1137 1977 -13.26 -0.00 -0.00 2.51 14.77 -12.36 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 4 1 1 1 0 2 3 16 62 64 64 0.00000 1.29035 4.28404e-003 1217 2116 -13.26 -0.00 -0.00 2.51 22.55 -14.20 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 5 1 1 1 0 2 4 16 62 64 64 0.00000 1.29035 4.28404e-003 1216 2113 -13.26 -0.00 -0.00 2.51 30.34 -16.03 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 6 1 1 1 0 2 5 16 62 64 64 0.00000 1.29035 4.28404e-003 1141 1983 -13.26 -0.00 -0.00 2.51 38.13 -17.87 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 7 1 1 1 0 2 6 16 62 64 64 0.00000 1.29035 4.28404e-003 1119 1945 -13.26 -0.00 -0.00 2.51 45.91 -19.70 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 8 1 1 1 0 2 7 16 62 64 64 0.00000 1.29035 4.28404e-003 1097 1907 -13.26 -0.00 -0.00 2.51 53.70 -21.54 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 9 1 1 1 0 2 8 16 62 64 64 0.00000 1.29035 4.28404e-003 1146 1991 -13.26 -0.00 -0.00 2.51 61.49 -23.37 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 1 1 2 1 0 2 9 16 62 64 64 0.00000 1.29035 4.28404e-003 1071 1863 -13.26 -0.00 -0.00 2.51 -0.81 -8.69 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 2 1 2 1 0 2 10 16 62 64 64 0.00000 1.29035 4.28404e-003 1123 1953 -13.26 -0.00 -0.00 2.51 6.98 -10.53 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 3 1 2 1 0 2 11 16 62 64 64 0.00000 1.29035 4.28404e-003 1135 1973 -13.26 -0.00 -0.00 2.51 14.77 -12.36 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 4 1 2 1 0 2 12 16 62 64 64 0.00000 1.29035 4.28404e-003 1209 2101 -13.26 -0.00 -0.00 2.51 22.55 -14.20 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 5 1 2 1 0 2 13 16 62 64 64 0.00000 1.29035 4.28404e-003 1215 2113 -13.26 -0.00 -0.00 2.51 30.34 -16.03 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 6 1 2 1 0 2 14 16 62 64 64 0.00000 1.29035 4.28404e-003 1145 1990 -13.26 -0.00 -0.00 2.51 38.13 -17.87 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 7 1 2 1 0 2 15 16 62 64 64 0.00000 1.29035 4.28404e-003 1119 1945 -13.26 -0.00 -0.00 2.51 45.91 -19.70 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 8 1 2 1 0 2 16 16 62 64 64 0.00000 1.29035 4.28404e-003 1093 1899 -13.26 -0.00 -0.00 2.51 53.70 -21.54 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 9 1 2 1 0 2 17 16 62 64 64 0.00000 1.29035 4.28404e-003 1150 1999 -13.26 -0.00 -0.00 2.51 61.49 -23.37 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 1 1 3 1 0 2 18 16 62 64 64 0.00000 1.29035 4.28404e-003 1070 1860 -13.26 -0.00 -0.00 2.51 -0.81 -8.69 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 2 1 3 1 0 2 19 16 62 64 64 0.00000 1.29035 4.28404e-003 1125 1955 -13.26 -0.00 -0.00 2.51 6.98 -10.53 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 3 1 3 1 0 2 20 16 62 64 64 0.00000 1.29035 4.28404e-003 1135 1973 -13.26 -0.00 -0.00 2.51 14.77 -12.36 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 4 1 3 1 0 2 21 16 62 64 64 0.00000 1.29035 4.28404e-003 1211 2105 -13.26 -0.00 -0.00 2.51 22.55 -14.20 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 5 1 3 1 0 2 22 16 62 64 64 0.00000 1.29035 4.28404e-003 1218 2118 -13.26 -0.00 -0.00 2.51 30.34 -16.03 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 6 1 3 1 0 2 23 16 62 64 64 0.00000 1.29035 4.28404e-003 1143 1987 -13.26 -0.00 -0.00 2.51 38.13 -17.87 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 7 1 3 1 0 2 24 16 62 64 64 0.00000 1.29035 4.28404e-003 1120 1947 -13.26 -0.00 -0.00 2.51 45.91 -19.70 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 8 1 3 1 0 2 25 16 62 64 64 0.00000 1.29035 4.28404e-003 1093 1901 -13.26 -0.00 -0.00 2.51 53.70 -21.54 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - 9 1 3 1 0 2 26 16 62 64 64 0.00000 1.29035 4.28404e-003 1151 2001 -13.26 -0.00 -0.00 2.51 61.49 -23.37 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 - -# === END OF DATA DESCRIPTION FILE =============================================== diff --git a/nibabel/tests/data/phantom_truncated.PAR b/nibabel/tests/data/phantom_truncated.PAR deleted file mode 100644 index 3013c81972..0000000000 --- a/nibabel/tests/data/phantom_truncated.PAR +++ /dev/null @@ -1,129 +0,0 @@ -# === DATA DESCRIPTION FILE ====================================================== -# -# CAUTION - Investigational device. -# Limited by Federal Law to investigational use. -# -# Dataset name: E:\\Export\phantom_EPI_asc_CLEAR_2_1 -# -# CLINICAL TRYOUT Research image export tool V4.2 -# -# === GENERAL INFORMATION ======================================================== -# -. Patient name : phantom -. Examination name : Konvertertest -. Protocol name : EPI_asc CLEAR -. Examination date/time : 2014.02.14 / 09:00:57 -. Series Type : Image MRSERIES -. Acquisition nr : 2 -. Reconstruction nr : 1 -. Scan Duration [sec] : 14 -. Max. number of cardiac phases : 1 -. Max. number of echoes : 1 -. Max. number of slices/locations : 9 -. Max. number of dynamics : 4 -. Max. number of mixes : 1 -. Patient position : Head First Supine -. Preparation direction : Anterior-Posterior -. Technique : FEEPI -. Scan resolution (x, y) : 64 39 -. Scan mode : MS -. Repetition time [ms] : 2000.000 -. FOV (ap,fh,rl) [mm] : 240.000 70.000 240.000 -. Water Fat shift [pixels] : 11.050 -. Angulation midslice(ap,fh,rl)[degr]: -13.265 0.000 0.000 -. Off Centre midslice(ap,fh,rl) [mm] : 2.508 30.339 -16.032 -. Flow compensation <0=no 1=yes> ? : 0 -. Presaturation <0=no 1=yes> ? : 0 -. Phase encoding velocity [cm/sec] : 0.000000 0.000000 0.000000 -. MTC <0=no 1=yes> ? : 0 -. SPIR <0=no 1=yes> ? : 1 -. EPI factor <0,1=no EPI> : 39 -. Dynamic scan <0=no 1=yes> ? : 1 -. Diffusion <0=no 1=yes> ? : 0 -. Diffusion echo time [ms] : 0.0000 -. Max. number of diffusion values : 1 -. Max. number of gradient orients : 1 -. Number of label types <0=no ASL> : 0 -# -# === PIXEL VALUES ============================================================= -# PV = pixel value in REC file, FP = floating point value, DV = displayed value on console -# RS = rescale slope, RI = rescale intercept, SS = scale slope -# DV = PV * RS + RI FP = DV / (RS * SS) -# -# === IMAGE INFORMATION DEFINITION ============================================= -# The rest of this file contains ONE line per image, this line contains the following information: -# -# slice number (integer) -# echo number (integer) -# dynamic scan number (integer) -# cardiac phase number (integer) -# image_type_mr (integer) -# scanning sequence (integer) -# index in REC file (in images) (integer) -# image pixel size (in bits) (integer) -# scan percentage (integer) -# recon resolution (x y) (2*integer) -# rescale intercept (float) -# rescale slope (float) -# scale slope (float) -# window center (integer) -# window width (integer) -# image angulation (ap,fh,rl in degrees ) (3*float) -# image offcentre (ap,fh,rl in mm ) (3*float) -# slice thickness (in mm ) (float) -# slice gap (in mm ) (float) -# image_display_orientation (integer) -# slice orientation ( TRA/SAG/COR ) (integer) -# fmri_status_indication (integer) -# image_type_ed_es (end diast/end syst) (integer) -# pixel spacing (x,y) (in mm) (2*float) -# echo_time (float) -# dyn_scan_begin_time (float) -# trigger_time (float) -# diffusion_b_factor (float) -# number of averages (integer) -# image_flip_angle (in degrees) (float) -# cardiac frequency (bpm) (integer) -# minimum RR-interval (in ms) (integer) -# maximum RR-interval (in ms) (integer) -# TURBO factor <0=no turbo> (integer) -# Inversion delay (in ms) (float) -# diffusion b value number (imagekey!) (integer) -# gradient orientation number (imagekey!) (integer) -# contrast type (string) -# diffusion anisotropy type (string) -# diffusion (ap, fh, rl) (3*float) -# label type (ASL) (imagekey!) (integer) -# -# === IMAGE INFORMATION ========================================================== -# sl ec dyn ph ty idx pix scan% rec size (re)scale window angulation offcentre thick gap info spacing echo dtime ttime diff avg flip freq RR-int turbo delay b grad cont anis diffusion L.ty - - 1 1 1 1 0 2 0 16 62 64 64 0.00000 1.29035 4.28404e-003 1070 1860 -13.26 -0.00 -0.00 2.51 -0.81 -8.69 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 2 1 1 1 0 2 1 16 62 64 64 0.00000 1.29035 4.28404e-003 1122 1951 -13.26 -0.00 -0.00 2.51 6.98 -10.53 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 3 1 1 1 0 2 2 16 62 64 64 0.00000 1.29035 4.28404e-003 1137 1977 -13.26 -0.00 -0.00 2.51 14.77 -12.36 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 4 1 1 1 0 2 3 16 62 64 64 0.00000 1.29035 4.28404e-003 1217 2116 -13.26 -0.00 -0.00 2.51 22.55 -14.20 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 5 1 1 1 0 2 4 16 62 64 64 0.00000 1.29035 4.28404e-003 1216 2113 -13.26 -0.00 -0.00 2.51 30.34 -16.03 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 6 1 1 1 0 2 5 16 62 64 64 0.00000 1.29035 4.28404e-003 1141 1983 -13.26 -0.00 -0.00 2.51 38.13 -17.87 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 7 1 1 1 0 2 6 16 62 64 64 0.00000 1.29035 4.28404e-003 1119 1945 -13.26 -0.00 -0.00 2.51 45.91 -19.70 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 8 1 1 1 0 2 7 16 62 64 64 0.00000 1.29035 4.28404e-003 1097 1907 -13.26 -0.00 -0.00 2.51 53.70 -21.54 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 9 1 1 1 0 2 8 16 62 64 64 0.00000 1.29035 4.28404e-003 1146 1991 -13.26 -0.00 -0.00 2.51 61.49 -23.37 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 1 1 2 1 0 2 9 16 62 64 64 0.00000 1.29035 4.28404e-003 1071 1863 -13.26 -0.00 -0.00 2.51 -0.81 -8.69 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 2 1 2 1 0 2 10 16 62 64 64 0.00000 1.29035 4.28404e-003 1123 1953 -13.26 -0.00 -0.00 2.51 6.98 -10.53 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 3 1 2 1 0 2 11 16 62 64 64 0.00000 1.29035 4.28404e-003 1135 1973 -13.26 -0.00 -0.00 2.51 14.77 -12.36 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 4 1 2 1 0 2 12 16 62 64 64 0.00000 1.29035 4.28404e-003 1209 2101 -13.26 -0.00 -0.00 2.51 22.55 -14.20 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 5 1 2 1 0 2 13 16 62 64 64 0.00000 1.29035 4.28404e-003 1215 2113 -13.26 -0.00 -0.00 2.51 30.34 -16.03 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 6 1 2 1 0 2 14 16 62 64 64 0.00000 1.29035 4.28404e-003 1145 1990 -13.26 -0.00 -0.00 2.51 38.13 -17.87 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 7 1 2 1 0 2 15 16 62 64 64 0.00000 1.29035 4.28404e-003 1119 1945 -13.26 -0.00 -0.00 2.51 45.91 -19.70 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 8 1 2 1 0 2 16 16 62 64 64 0.00000 1.29035 4.28404e-003 1093 1899 -13.26 -0.00 -0.00 2.51 53.70 -21.54 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 9 1 2 1 0 2 17 16 62 64 64 0.00000 1.29035 4.28404e-003 1150 1999 -13.26 -0.00 -0.00 2.51 61.49 -23.37 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 1 1 3 1 0 2 18 16 62 64 64 0.00000 1.29035 4.28404e-003 1070 1860 -13.26 -0.00 -0.00 2.51 -0.81 -8.69 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 2 1 3 1 0 2 19 16 62 64 64 0.00000 1.29035 4.28404e-003 1125 1955 -13.26 -0.00 -0.00 2.51 6.98 -10.53 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 3 1 3 1 0 2 20 16 62 64 64 0.00000 1.29035 4.28404e-003 1135 1973 -13.26 -0.00 -0.00 2.51 14.77 -12.36 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 4 1 3 1 0 2 21 16 62 64 64 0.00000 1.29035 4.28404e-003 1211 2105 -13.26 -0.00 -0.00 2.51 22.55 -14.20 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 5 1 3 1 0 2 22 16 62 64 64 0.00000 1.29035 4.28404e-003 1218 2118 -13.26 -0.00 -0.00 2.51 30.34 -16.03 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 6 1 3 1 0 2 23 16 62 64 64 0.00000 1.29035 4.28404e-003 1143 1987 -13.26 -0.00 -0.00 2.51 38.13 -17.87 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 7 1 3 1 0 2 24 16 62 64 64 0.00000 1.29035 4.28404e-003 1120 1947 -13.26 -0.00 -0.00 2.51 45.91 -19.70 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 8 1 3 1 0 2 25 16 62 64 64 0.00000 1.29035 4.28404e-003 1093 1901 -13.26 -0.00 -0.00 2.51 53.70 -21.54 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 9 1 3 1 0 2 26 16 62 64 64 0.00000 1.29035 4.28404e-003 1151 2001 -13.26 -0.00 -0.00 2.51 61.49 -23.37 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - -# === END OF DATA DESCRIPTION FILE =============================================== diff --git a/nibabel/tests/data/phantom_truncated.REC b/nibabel/tests/data/phantom_truncated.REC deleted file mode 100644 index 958095f2a8..0000000000 Binary files a/nibabel/tests/data/phantom_truncated.REC and /dev/null differ diff --git a/nibabel/tests/data/phantom_varscale.PAR b/nibabel/tests/data/phantom_varscale.PAR deleted file mode 100644 index 56f146cbb8..0000000000 --- a/nibabel/tests/data/phantom_varscale.PAR +++ /dev/null @@ -1,129 +0,0 @@ -# === DATA DESCRIPTION FILE ====================================================== -# -# CAUTION - Investigational device. -# Limited by Federal Law to investigational use. -# -# Dataset name: E:\\Export\phantom_EPI_asc_CLEAR_2_1 -# -# CLINICAL TRYOUT Research image export tool V4.2 -# -# === GENERAL INFORMATION ======================================================== -# -. Patient name : phantom -. Examination name : Konvertertest -. Protocol name : EPI_asc CLEAR -. Examination date/time : 2014.02.14 / 09:00:57 -. Series Type : Image MRSERIES -. Acquisition nr : 2 -. Reconstruction nr : 1 -. Scan Duration [sec] : 14 -. Max. number of cardiac phases : 1 -. Max. number of echoes : 1 -. Max. number of slices/locations : 9 -. Max. number of dynamics : 3 -. Max. number of mixes : 1 -. Patient position : Head First Supine -. Preparation direction : Anterior-Posterior -. Technique : FEEPI -. Scan resolution (x, y) : 64 39 -. Scan mode : MS -. Repetition time [ms] : 2000.000 -. FOV (ap,fh,rl) [mm] : 240.000 70.000 240.000 -. Water Fat shift [pixels] : 11.050 -. Angulation midslice(ap,fh,rl)[degr]: -13.265 0.000 0.000 -. Off Centre midslice(ap,fh,rl) [mm] : 2.508 30.339 -16.032 -. Flow compensation <0=no 1=yes> ? : 0 -. Presaturation <0=no 1=yes> ? : 0 -. Phase encoding velocity [cm/sec] : 0.000000 0.000000 0.000000 -. MTC <0=no 1=yes> ? : 0 -. SPIR <0=no 1=yes> ? : 1 -. EPI factor <0,1=no EPI> : 39 -. Dynamic scan <0=no 1=yes> ? : 1 -. Diffusion <0=no 1=yes> ? : 0 -. Diffusion echo time [ms] : 0.0000 -. Max. number of diffusion values : 1 -. Max. number of gradient orients : 1 -. Number of label types <0=no ASL> : 0 -# -# === PIXEL VALUES ============================================================= -# PV = pixel value in REC file, FP = floating point value, DV = displayed value on console -# RS = rescale slope, RI = rescale intercept, SS = scale slope -# DV = PV * RS + RI FP = DV / (RS * SS) -# -# === IMAGE INFORMATION DEFINITION ============================================= -# The rest of this file contains ONE line per image, this line contains the following information: -# -# slice number (integer) -# echo number (integer) -# dynamic scan number (integer) -# cardiac phase number (integer) -# image_type_mr (integer) -# scanning sequence (integer) -# index in REC file (in images) (integer) -# image pixel size (in bits) (integer) -# scan percentage (integer) -# recon resolution (x y) (2*integer) -# rescale intercept (float) -# rescale slope (float) -# scale slope (float) -# window center (integer) -# window width (integer) -# image angulation (ap,fh,rl in degrees ) (3*float) -# image offcentre (ap,fh,rl in mm ) (3*float) -# slice thickness (in mm ) (float) -# slice gap (in mm ) (float) -# image_display_orientation (integer) -# slice orientation ( TRA/SAG/COR ) (integer) -# fmri_status_indication (integer) -# image_type_ed_es (end diast/end syst) (integer) -# pixel spacing (x,y) (in mm) (2*float) -# echo_time (float) -# dyn_scan_begin_time (float) -# trigger_time (float) -# diffusion_b_factor (float) -# number of averages (integer) -# image_flip_angle (in degrees) (float) -# cardiac frequency (bpm) (integer) -# minimum RR-interval (in ms) (integer) -# maximum RR-interval (in ms) (integer) -# TURBO factor <0=no turbo> (integer) -# Inversion delay (in ms) (float) -# diffusion b value number (imagekey!) (integer) -# gradient orientation number (imagekey!) (integer) -# contrast type (string) -# diffusion anisotropy type (string) -# diffusion (ap, fh, rl) (3*float) -# label type (ASL) (imagekey!) (integer) -# -# === IMAGE INFORMATION ========================================================== -# sl ec dyn ph ty idx pix scan% rec size (re)scale window angulation offcentre thick gap info spacing echo dtime ttime diff avg flip freq RR-int turbo delay b grad cont anis diffusion L.ty - - 1 1 1 1 0 2 0 16 62 64 64 -0.69352 0.65184 4.78462e-003 1070 1860 -13.26 -0.00 -0.00 2.51 -0.81 -8.69 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 2 1 1 1 0 2 1 16 62 64 64 -0.28395 0.18528 5.01287e-003 1122 1951 -13.26 -0.00 -0.00 2.51 6.98 -10.53 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 3 1 1 1 0 2 2 16 62 64 64 0.60964 0.48545 3.20504e-003 1137 1977 -13.26 -0.00 -0.00 2.51 14.77 -12.36 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 4 1 1 1 0 2 3 16 62 64 64 0.23940 0.58869 5.70364e-003 1217 2116 -13.26 -0.00 -0.00 2.51 22.55 -14.20 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 5 1 1 1 0 2 4 16 62 64 64 -0.56774 0.54425 4.19357e-003 1216 2113 -13.26 -0.00 -0.00 2.51 30.34 -16.03 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 6 1 1 1 0 2 5 16 62 64 64 0.84933 0.61451 4.51471e-003 1141 1983 -13.26 -0.00 -0.00 2.51 38.13 -17.87 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 7 1 1 1 0 2 6 16 62 64 64 -0.23580 2.07194 4.81163e-003 1119 1945 -13.26 -0.00 -0.00 2.51 45.91 -19.70 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 8 1 1 1 0 2 7 16 62 64 64 -0.45610 1.75542 3.01393e-003 1097 1907 -13.26 -0.00 -0.00 2.51 53.70 -21.54 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 9 1 1 1 0 2 8 16 62 64 64 -0.33958 0.41352 3.31281e-003 1146 1991 -13.26 -0.00 -0.00 2.51 61.49 -23.37 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 1 1 2 1 0 2 9 16 62 64 64 -0.50054 0.02978 3.02487e-003 1071 1863 -13.26 -0.00 -0.00 2.51 -0.81 -8.69 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 2 1 2 1 0 2 10 16 62 64 64 1.09595 2.68028 4.32900e-003 1123 1953 -13.26 -0.00 -0.00 2.51 6.98 -10.53 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 3 1 2 1 0 2 11 16 62 64 64 0.66260 -0.01198 5.25907e-003 1135 1973 -13.26 -0.00 -0.00 2.51 14.77 -12.36 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 4 1 2 1 0 2 12 16 62 64 64 0.49692 0.62467 3.65960e-003 1209 2101 -13.26 -0.00 -0.00 2.51 22.55 -14.20 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 5 1 2 1 0 2 13 16 62 64 64 -0.81913 0.05637 2.96656e-003 1215 2113 -13.26 -0.00 -0.00 2.51 30.34 -16.03 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 6 1 2 1 0 2 14 16 62 64 64 -0.46862 0.88504 5.71452e-003 1145 1990 -13.26 -0.00 -0.00 2.51 38.13 -17.87 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 7 1 2 1 0 2 15 16 62 64 64 0.24446 3.78608 4.86407e-003 1119 1945 -13.26 -0.00 -0.00 2.51 45.91 -19.70 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 8 1 2 1 0 2 16 16 62 64 64 0.07351 -1.06477 2.57365e-003 1093 1899 -13.26 -0.00 -0.00 2.51 53.70 -21.54 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 9 1 2 1 0 2 17 16 62 64 64 -1.52431 2.45537 5.97195e-003 1150 1999 -13.26 -0.00 -0.00 2.51 61.49 -23.37 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 1 1 3 1 0 2 18 16 62 64 64 0.74747 0.98210 4.98308e-003 1070 1860 -13.26 -0.00 -0.00 2.51 -0.81 -8.69 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 2 1 3 1 0 2 19 16 62 64 64 -0.15021 0.52770 3.56176e-003 1125 1955 -13.26 -0.00 -0.00 2.51 6.98 -10.53 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 3 1 3 1 0 2 20 16 62 64 64 1.68669 0.46589 4.71663e-003 1135 1973 -13.26 -0.00 -0.00 2.51 14.77 -12.36 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 4 1 3 1 0 2 21 16 62 64 64 0.51405 0.88194 4.60270e-003 1211 2105 -13.26 -0.00 -0.00 2.51 22.55 -14.20 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 5 1 3 1 0 2 22 16 62 64 64 -1.47991 2.03474 4.01615e-003 1218 2118 -13.26 -0.00 -0.00 2.51 30.34 -16.03 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 6 1 3 1 0 2 23 16 62 64 64 -1.66364 3.21846 4.54139e-003 1143 1987 -13.26 -0.00 -0.00 2.51 38.13 -17.87 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 7 1 3 1 0 2 24 16 62 64 64 0.60690 -0.41266 2.81800e-003 1120 1947 -13.26 -0.00 -0.00 2.51 45.91 -19.70 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 8 1 3 1 0 2 25 16 62 64 64 1.01725 0.91765 3.19469e-003 1093 1901 -13.26 -0.00 -0.00 2.51 53.70 -21.54 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 9 1 3 1 0 2 26 16 62 64 64 0.79204 1.65725 3.95777e-003 1151 2001 -13.26 -0.00 -0.00 2.51 61.49 -23.37 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - -# === END OF DATA DESCRIPTION FILE =============================================== diff --git a/nibabel/tests/data/phantom_varscale.REC b/nibabel/tests/data/phantom_varscale.REC deleted file mode 100644 index 958095f2a8..0000000000 Binary files a/nibabel/tests/data/phantom_varscale.REC and /dev/null differ diff --git a/nibabel/tests/data/reoriented_anat_moved.nii b/nibabel/tests/data/reoriented_anat_moved.nii deleted file mode 100644 index 2f2411d115..0000000000 Binary files a/nibabel/tests/data/reoriented_anat_moved.nii and /dev/null differ diff --git a/nibabel/tests/data/resample_using_spm.m b/nibabel/tests/data/resample_using_spm.m deleted file mode 100644 index 350fcb8382..0000000000 --- a/nibabel/tests/data/resample_using_spm.m +++ /dev/null @@ -1,15 +0,0 @@ -% Script uses SPM to resample moved anatomical image. -% -% Run `python make_moved_anat.py` to generate file to work on. -% -% Run from the directory containing this file. -% Works with Octave or MATLAB. -% Needs SPM (5, 8 or 12) on the MATLAB path. -P = {'functional.nii', 'anat_moved.nii'}; -% Resample without masking -flags = struct('mask', false, 'mean', false, ... - 'interp', 1, 'which', 1, ... - 'prefix', 'resampled_'); -spm_reslice(P, flags); -% Reorient to canonical orientation at 4mm resolution, polynomial interpolation -to_canonical({'anat_moved.nii'}, 4, 'reoriented_', 1); diff --git a/nibabel/tests/data/resampled_anat_moved.nii b/nibabel/tests/data/resampled_anat_moved.nii deleted file mode 100644 index c6b4549c21..0000000000 Binary files a/nibabel/tests/data/resampled_anat_moved.nii and /dev/null differ diff --git a/nibabel/tests/data/row_major.dconn.nii b/nibabel/tests/data/row_major.dconn.nii deleted file mode 100644 index a2aa2afd1e..0000000000 Binary files a/nibabel/tests/data/row_major.dconn.nii and /dev/null differ diff --git a/nibabel/tests/data/scaled+tlrc.BRIK b/nibabel/tests/data/scaled+tlrc.BRIK deleted file mode 100644 index 4bec3547ee..0000000000 Binary files a/nibabel/tests/data/scaled+tlrc.BRIK and /dev/null differ diff --git a/nibabel/tests/data/scaled+tlrc.HEAD b/nibabel/tests/data/scaled+tlrc.HEAD deleted file mode 100644 index a13b054e2d..0000000000 --- a/nibabel/tests/data/scaled+tlrc.HEAD +++ /dev/null @@ -1,116 +0,0 @@ - -type = string-attribute -name = TYPESTRING -count = 15 -'3DIM_HEAD_ANAT~ - -type = string-attribute -name = IDCODE_STRING -count = 27 -'AFN_vLKn9e5VumKelWXNeq4SWA~ - -type = string-attribute -name = IDCODE_DATE -count = 25 -'Tue Jan 23 20:05:10 2018~ - -type = integer-attribute -name = SCENE_DATA -count = 8 - 2 2 0 -999 -999 - -999 -999 -999 - -type = string-attribute -name = LABEL_1 -count = 5 -'zyxt~ - -type = string-attribute -name = LABEL_2 -count = 5 -'zyxt~ - -type = string-attribute -name = DATASET_NAME -count = 5 -'zyxt~ - -type = integer-attribute -name = ORIENT_SPECIFIC -count = 3 - 1 2 4 - -type = float-attribute -name = ORIGIN -count = 3 - 66 87 -54 - -type = float-attribute -name = DELTA -count = 3 - -3 -3 3 - -type = float-attribute -name = IJK_TO_DICOM -count = 12 - -3 0 0 66 0 - -3 0 87 0 0 - 3 -54 - -type = float-attribute -name = IJK_TO_DICOM_REAL -count = 12 - -3 0 0 66 0 - -3 0 87 0 0 - 3 -54 - -type = float-attribute -name = BRICK_STATS -count = 2 - 1.941682e-07 0.001272461 - -type = integer-attribute -name = DATASET_RANK -count = 8 - 3 1 0 0 0 - 0 0 0 - -type = integer-attribute -name = DATASET_DIMENSIONS -count = 5 - 47 54 43 0 0 - -type = integer-attribute -name = BRICK_TYPES -count = 1 - 1 - -type = float-attribute -name = BRICK_FLOAT_FACS -count = 1 - 3.883363e-08 - -type = string-attribute -name = BRICK_LABS -count = 3 -'#0~ - -type = string-attribute -name = BRICK_KEYWORDS -count = 1 -'~ - -type = string-attribute -name = TEMPLATE_SPACE -count = 5 -'TLRC~ - -type = integer-attribute -name = INT_CMAP -count = 1 - 0 - -type = string-attribute -name = BYTEORDER_STRING -count = 10 -'LSB_FIRST~ diff --git a/nibabel/tests/data/simple.tck b/nibabel/tests/data/simple.tck deleted file mode 100644 index 87b5743ea7..0000000000 Binary files a/nibabel/tests/data/simple.tck and /dev/null differ diff --git a/nibabel/tests/data/simple.trk b/nibabel/tests/data/simple.trk deleted file mode 100644 index df601e29a7..0000000000 Binary files a/nibabel/tests/data/simple.trk and /dev/null differ diff --git a/nibabel/tests/data/simple_big_endian.tck b/nibabel/tests/data/simple_big_endian.tck deleted file mode 100644 index 71ccf57284..0000000000 Binary files a/nibabel/tests/data/simple_big_endian.tck and /dev/null differ diff --git a/nibabel/tests/data/small.mnc b/nibabel/tests/data/small.mnc deleted file mode 100644 index 755a1ca01d..0000000000 Binary files a/nibabel/tests/data/small.mnc and /dev/null differ diff --git a/nibabel/tests/data/standard.LPS.trk b/nibabel/tests/data/standard.LPS.trk deleted file mode 100644 index ebda71bdb8..0000000000 Binary files a/nibabel/tests/data/standard.LPS.trk and /dev/null differ diff --git a/nibabel/tests/data/standard.nii.gz b/nibabel/tests/data/standard.nii.gz deleted file mode 100644 index 98bb31a778..0000000000 Binary files a/nibabel/tests/data/standard.nii.gz and /dev/null differ diff --git a/nibabel/tests/data/standard.tck b/nibabel/tests/data/standard.tck deleted file mode 100644 index 309f8fa4c7..0000000000 Binary files a/nibabel/tests/data/standard.tck and /dev/null differ diff --git a/nibabel/tests/data/standard.trk b/nibabel/tests/data/standard.trk deleted file mode 100644 index 01ea01744a..0000000000 Binary files a/nibabel/tests/data/standard.trk and /dev/null differ diff --git a/nibabel/tests/data/test.mgz b/nibabel/tests/data/test.mgz deleted file mode 100644 index f54f3c80b8..0000000000 Binary files a/nibabel/tests/data/test.mgz and /dev/null differ diff --git a/nibabel/tests/data/tiny.mnc b/nibabel/tests/data/tiny.mnc deleted file mode 100644 index 3ab29e70e3..0000000000 Binary files a/nibabel/tests/data/tiny.mnc and /dev/null differ diff --git a/nibabel/tests/data/tinypet.v b/nibabel/tests/data/tinypet.v deleted file mode 100644 index c58cb00246..0000000000 Binary files a/nibabel/tests/data/tinypet.v and /dev/null differ diff --git a/nibabel/tests/data/to_canonical.m b/nibabel/tests/data/to_canonical.m deleted file mode 100644 index 08d5abd327..0000000000 --- a/nibabel/tests/data/to_canonical.m +++ /dev/null @@ -1,61 +0,0 @@ -function to_canonical(imgs, vox_sizes, prefix, hold) -% Resample images to canonical (transverse) orientation with given voxel sizes -% -% Inspired by ``reorient.m`` by John Ashburner: -% http://blogs.warwick.ac.uk/files/nichols/reorient.m -% -% Parameters -% ---------- -% imgs : char or cell array or struct array -% Images to resample to canonical orientation. -% vox_sizes : vector (3, 1), optional -% Voxel sizes for output image. -% prefix : char, optional -% Prefix for output resampled images, default = 'r' -% hold : float, optional -% Hold (resampling method) value, default = 3. - -if ~isstruct(imgs) - imgs = spm_vol(imgs); -end -if nargin < 2 - vox_sizes = [1 1 1]; -elseif numel(vox_sizes) == 1 - vox_sizes = [vox_sizes vox_sizes vox_sizes]; -end -vox_sizes = vox_sizes(:); -if nargin < 3 - prefix = 'r'; -end -if nargin < 4 - hold = 3; -end - -for vol_no = 1:numel(imgs) - vol = imgs{vol_no}(1); - % From: - % http://stackoverflow.com/questions/4165859/generate-all-possible-combinations-of-the-elements-of-some-vectors-cartesian-pr - sets = {[1, vol.dim(1)], [1, vol.dim(2)], [1, vol.dim(3)]}; - [x y z] = ndgrid(sets{:}); - corners = [x(:) y(:) z(:)]; - corner_coords = [corners ones(length(corners), 1)]'; - corner_mm = vol.mat * corner_coords; - min_xyz = min(corner_mm(1:3, :), [], 2); - max_xyz = max(corner_mm(1:3, :), [], 2); - % Make output volume - out_vol = vol; - out_vol.private = []; - out_vol.mat = diag([vox_sizes' 1]); - out_vol.mat(1:3, 4) = min_xyz - vox_sizes; - out_vol.dim(1:3) = ceil((max_xyz - min_xyz) ./ vox_sizes) + 1; - [dpath, froot, ext] = fileparts(vol.fname); - out_vol.fname = fullfile(dpath, [prefix froot ext]); - out_vol = spm_create_vol(out_vol); - % Resample original volume at output volume grid - plane_size = out_vol.dim(1:2); - for slice_no = 1:out_vol.dim(3) - resamp_affine = inv(spm_matrix([0 0 -slice_no]) * inv(out_vol.mat) * vol.mat); - slice_vals = spm_slice_vol(vol, resamp_affine, plane_size, hold); - out_vol = spm_write_plane(out_vol, slice_vals, slice_no); - end -end diff --git a/nibabel/tests/data/umass_anonymized.PAR b/nibabel/tests/data/umass_anonymized.PAR deleted file mode 100755 index 5a200ee906..0000000000 --- a/nibabel/tests/data/umass_anonymized.PAR +++ /dev/null @@ -1,176 +0,0 @@ -# === DATA DESCRIPTION FILE ====================================================== -# -# CAUTION - Investigational device. -# Limited by Federal Law to investigational use. -# -# Dataset name: Dump-0000 -# -# CLINICAL TRYOUT Research image export tool V4.2 -# -# === GENERAL INFORMATION ======================================================== -# -. Patient name : -. Examination name : -. Protocol name : -. Examination date/time : -. Series Type : Image MRSERIES -. Acquisition nr : 1 -. Reconstruction nr : 0 -. Scan Duration [sec] : 0 -. Max. number of cardiac phases : 1 -. Max. number of echoes : 1 -. Max. number of slices/locations : 37 -. Max. number of dynamics : 1 -. Max. number of mixes : 2 -. Patient position : -. Preparation direction : -. Technique : -. Scan resolution (x, y) : 0 0 -. Scan mode : -. Repetition time [ms] : 2.00000170898437 -. FOV (ap,fh,rl) [mm] : 0 0 0 -. Water Fat shift [pixels] : 0 -. Angulation midslice(ap,fh,rl)[degr]: 0 0 0 -. Off Centre midslice(ap,fh,rl) [mm] : 0 0 0 -. Flow compensation <0=no 1=yes> ? : 0 -. Presaturation <0=no 1=yes> ? : 0 -. Phase encoding velocity [cm/sec] : 0 0 0 -. MTC <0=no 1=yes> ? : 0 -. SPIR <0=no 1=yes> ? : 0 -. EPI factor <0,1=no EPI> : 0 -. Dynamic scan <0=no 1=yes> ? : 0 -. Diffusion <0=no 1=yes> ? : 0 -. Diffusion echo time [ms] : 0 -. Max. number of diffusion values : 1 -. Max. number of gradient orients : 1 -. Number of label types <0=no ASL> : 1 -# -# === PIXEL VALUES ============================================================= -# PV = pixel value in REC file, FP = floating point value, DV = displayed value on console -# RS = rescale slope, RI = rescale intercept, SS = scale slope -# DV = PV * RS + RI FP = DV / (RS * SS) -# -# === IMAGE INFORMATION DEFINITION ============================================= -# The rest of this file contains ONE line per image, this line contains the following information: -# -# slice number (integer) -# echo number (integer) -# dynamic scan number (integer) -# cardiac phase number (integer) -# image_type_mr (integer) -# scanning sequence (integer) -# index in REC file (in images) (integer) -# image pixel size (in bits) (integer) -# scan percentage (integer) -# recon resolution (x y) (2*integer) -# rescale intercept (float) -# rescale slope (float) -# scale slope (float) -# window center (integer) -# window width (integer) -# image angulation (ap,fh,rl in degrees ) (3*float) -# image offcentre (ap,fh,rl in mm ) (3*float) -# slice thickness (in mm ) (float) -# slice gap (in mm ) (float) -# image_display_orientation (integer) -# slice orientation ( TRA/SAG/COR ) (integer) -# fmri_status_indication (integer) -# image_type_ed_es (end diast/end syst) (integer) -# pixel spacing (x,y) (in mm) (2*float) -# echo_time (float) -# dyn_scan_begin_time (float) -# trigger_time (float) -# diffusion_b_factor (float) -# number of averages (integer) -# image_flip_angle (in degrees) (float) -# cardiac frequency (bpm) (integer) -# minimum RR-interval (in ms) (integer) -# maximum RR-interval (in ms) (integer) -# TURBO factor <0=no turbo> (integer) -# Inversion delay (in ms) (float) -# diffusion b value number (imagekey!) (integer) -# gradient orientation number (imagekey!) (integer) -# contrast type (string) -# diffusion anisotropy type (string) -# diffusion (ap, fh, rl) (3*float) -# label type (ASL) (imagekey!) (integer) -# -# === IMAGE INFORMATION ========================================================== -# sl ec dyn ph ty idx pix scan% rec size (re)scale window angulation offcentre thick gap info spacing echo dtime ttime diff avg flip freq RR-int turbo delay b grad cont anis diffusion L.ty - -1 1 1 0 17 0 0 16 0 80 80 -3142 1.53455433455433 651.739501953125 -2374.72283272283 767.277167277167 -3.9000002422219 -2.2999999682654 -29.9999985510007 -28.1000003814697 -55.5999984741211 1.29999995231628 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -2 1 1 0 17 0 1 16 0 80 80 -3142 1.53455433455433 651.739501953125 -2374.72283272283 767.277167277167 -3.9000002422219 -2.2999999682654 -29.9999985510007 -26.2999992370605 -52.5999984741211 1.10000002384186 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -3 1 1 0 17 0 2 16 0 80 80 -3142 1.53455433455433 651.739501953125 -2374.72283272283 767.277167277167 -3.9000002422219 -2.2999999682654 -29.9999985510007 -24.6000003814697 -49.5999984741211 0.800000011920929 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -4 1 1 0 17 0 3 16 0 80 80 -3142 1.53455433455433 651.739501953125 -2374.72283272283 767.277167277167 -3.9000002422219 -2.2999999682654 -29.9999985510007 -22.7999992370605 -46.5999984741211 0.600000023841858 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -5 1 1 0 17 0 4 16 0 80 80 -3142 1.53455433455433 651.739501953125 -2374.72283272283 767.277167277167 -3.9000002422219 -2.2999999682654 -29.9999985510007 -21.1000003814697 -43.5 0.400000005960464 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -6 1 1 0 17 0 5 16 0 80 80 -3142 1.53455433455433 651.739501953125 -2374.72283272283 767.277167277167 -3.9000002422219 -2.2999999682654 -29.9999985510007 -19.2999992370605 -40.5 0.100000001490116 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -7 1 1 0 17 0 6 16 0 80 80 -3142 1.53455433455433 651.739501953125 -2374.72283272283 767.277167277167 -3.9000002422219 -2.2999999682654 -29.9999985510007 -17.6000003814697 -37.5 -0.100000001490116 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -8 1 1 0 17 0 7 16 0 80 80 -3142 1.53455433455433 651.739501953125 -2374.72283272283 767.277167277167 -3.9000002422219 -2.2999999682654 -29.9999985510007 -15.8000001907349 -34.5 -0.400000005960464 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -9 1 1 0 17 0 8 16 0 80 80 -3142 1.53455433455433 651.739501953125 -2374.72283272283 767.277167277167 -3.9000002422219 -2.2999999682654 -29.9999985510007 -14.1000003814697 -31.3999996185303 -0.600000023841858 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -10 1 1 0 17 0 9 16 0 80 80 -3142 1.53455433455433 651.739501953125 -2374.72283272283 767.277167277167 -3.9000002422219 -2.2999999682654 -29.9999985510007 -12.3000001907349 -28.3999996185303 -0.800000011920929 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -11 1 1 0 17 0 10 16 0 80 80 -3142 1.53455433455433 651.739501953125 -2374.72283272283 767.277167277167 -3.9000002422219 -2.2999999682654 -29.9999985510007 -10.6000003814697 -25.3999996185303 -1.10000002384186 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -12 1 1 0 17 0 11 16 0 80 80 -3142 1.53455433455433 651.739501953125 -2374.72283272283 767.277167277167 -3.9000002422219 -2.2999999682654 -29.9999985510007 -8.89999961853027 -22.3999996185303 -1.29999995231628 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -13 1 1 0 17 0 12 16 0 80 80 -3142 1.53455433455433 651.739501953125 -2374.72283272283 767.277167277167 -3.9000002422219 -2.2999999682654 -29.9999985510007 -7.09999990463257 -19.2999992370605 -1.5 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -14 1 1 0 17 0 13 16 0 80 80 -3142 1.53455433455433 651.739501953125 -2374.72283272283 767.277167277167 -3.9000002422219 -2.2999999682654 -29.9999985510007 -5.40000009536743 -16.2999992370605 -1.79999995231628 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -15 1 1 0 17 0 14 16 0 80 80 -3142 1.53455433455433 651.739501953125 -2374.72283272283 767.277167277167 -3.9000002422219 -2.2999999682654 -29.9999985510007 -3.59999990463257 -13.3000001907349 -2 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -16 1 1 0 17 0 15 16 0 80 80 -3142 1.53455433455433 651.739501953125 -2374.72283272283 767.277167277167 -3.9000002422219 -2.2999999682654 -29.9999985510007 -1.89999997615814 -10.3000001907349 -2.29999995231628 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -17 1 1 0 17 0 16 16 0 80 80 -3142 1.53455433455433 651.739501953125 -2374.72283272283 767.277167277167 -3.9000002422219 -2.2999999682654 -29.9999985510007 -0.100000001490116 -7.19999980926514 -2.5 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -18 1 1 0 17 0 17 16 0 80 80 -3142 1.53455433455433 651.739501953125 -2374.72283272283 767.277167277167 -3.9000002422219 -2.2999999682654 -29.9999985510007 1.60000002384186 -4.19999980926514 -2.70000004768372 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -19 1 1 0 17 0 18 16 0 80 80 -3142 1.53455433455433 651.739501953125 -2374.72283272283 767.277167277167 -3.9000002422219 -2.2999999682654 -29.9999985510007 3.40000009536743 -1.20000004768372 -3 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -20 1 1 0 17 0 19 16 0 80 80 -3142 1.53455433455433 651.739501953125 -2374.72283272283 767.277167277167 -3.9000002422219 -2.2999999682654 -29.9999985510007 5.09999990463257 1.79999995231628 -3.20000004768372 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -21 1 1 0 17 0 20 16 0 80 80 -3142 1.53455433455433 651.739501953125 -2374.72283272283 767.277167277167 -3.9000002422219 -2.2999999682654 -29.9999985510007 6.90000009536743 4.90000009536743 -3.5 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -22 1 1 0 17 0 21 16 0 80 80 -3142 1.53455433455433 651.739501953125 -2374.72283272283 767.277167277167 -3.9000002422219 -2.2999999682654 -29.9999985510007 8.60000038146973 7.90000009536743 -3.70000004768372 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -23 1 1 0 17 0 22 16 0 80 80 -3142 1.53455433455433 651.739501953125 -2374.72283272283 767.277167277167 -3.9000002422219 -2.2999999682654 -29.9999985510007 10.3999996185303 10.8999996185303 -3.90000009536743 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -24 1 1 0 17 0 23 16 0 80 80 -3142 1.53455433455433 651.739501953125 -2374.72283272283 767.277167277167 -3.9000002422219 -2.2999999682654 -29.9999985510007 12.1000003814697 13.8999996185303 -4.19999980926514 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -25 1 1 0 17 0 24 16 0 80 80 -3142 1.53455433455433 651.739501953125 -2374.72283272283 767.277167277167 -3.9000002422219 -2.2999999682654 -29.9999985510007 13.8000001907349 17 -4.40000009536743 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -26 1 1 0 17 0 25 16 0 80 80 -3142 1.53455433455433 651.739501953125 -2374.72283272283 767.277167277167 -3.9000002422219 -2.2999999682654 -29.9999985510007 15.6000003814697 20 -4.69999980926514 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -27 1 1 0 17 0 26 16 0 80 80 -3142 1.53455433455433 651.739501953125 -2374.72283272283 767.277167277167 -3.9000002422219 -2.2999999682654 -29.9999985510007 17.2999992370605 23 -4.90000009536743 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -28 1 1 0 17 0 27 16 0 80 80 -3142 1.53455433455433 651.739501953125 -2374.72283272283 767.277167277167 -3.9000002422219 -2.2999999682654 -29.9999985510007 19.1000003814697 26 -5.09999990463257 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -29 1 1 0 17 0 28 16 0 80 80 -3142 1.53455433455433 651.739501953125 -2374.72283272283 767.277167277167 -3.9000002422219 -2.2999999682654 -29.9999985510007 20.7999992370605 29.1000003814697 -5.40000009536743 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -30 1 1 0 17 0 29 16 0 80 80 -3142 1.53455433455433 651.739501953125 -2374.72283272283 767.277167277167 -3.9000002422219 -2.2999999682654 -29.9999985510007 22.6000003814697 32.0999984741211 -5.59999990463257 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -31 1 1 0 17 0 30 16 0 80 80 -3142 1.53455433455433 651.739501953125 -2374.72283272283 767.277167277167 -3.9000002422219 -2.2999999682654 -29.9999985510007 24.2999992370605 35.0999984741211 -5.80000019073486 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -32 1 1 0 17 0 31 16 0 80 80 -3142 1.53455433455433 651.739501953125 -2374.72283272283 767.277167277167 -3.9000002422219 -2.2999999682654 -29.9999985510007 26.1000003814697 38.0999984741211 -6.09999990463257 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -33 1 1 0 17 0 32 16 0 80 80 -3142 1.53455433455433 651.739501953125 -2374.72283272283 767.277167277167 -3.9000002422219 -2.2999999682654 -29.9999985510007 27.7999992370605 41.0999984741211 -6.30000019073486 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -34 1 1 0 17 0 33 16 0 80 80 -3142 1.53455433455433 651.739501953125 -2374.72283272283 767.277167277167 -3.9000002422219 -2.2999999682654 -29.9999985510007 29.6000003814697 44.2000007629395 -6.59999990463257 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -35 1 1 0 17 0 34 16 0 80 80 -3142 1.53455433455433 651.739501953125 -2374.72283272283 767.277167277167 -3.9000002422219 -2.2999999682654 -29.9999985510007 31.2999992370605 47.2000007629395 -6.80000019073486 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -36 1 1 0 17 0 35 16 0 80 80 -3142 1.53455433455433 651.739501953125 -2374.72283272283 767.277167277167 -3.9000002422219 -2.2999999682654 -29.9999985510007 33.0999984741211 50.2000007629395 -7 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -37 1 1 0 17 0 36 16 0 80 80 -3142 1.53455433455433 651.739501953125 -2374.72283272283 767.277167277167 -3.9000002422219 -2.2999999682654 -29.9999985510007 34.7999992370605 53.2000007629395 -7.30000019073486 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -1 1 1 0 16 0 37 16 0 80 80 0 0.488400488400488 0.00991736631840467 244.200244200244 244.200244200244 -3.9000002422219 -2.2999999682654 -29.9999985510007 -28.1000003814697 -55.5999984741211 1.29999995231628 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -2 1 1 0 16 0 38 16 0 80 80 0 0.489377289377289 0.0098956935107708 244.688644688645 244.688644688645 -3.9000002422219 -2.2999999682654 -29.9999985510007 -26.2999992370605 -52.5999984741211 1.10000002384186 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -3 1 1 0 16 0 39 16 0 80 80 0 0.492063492063492 0.00984135083854198 246.031746031746 246.031746031746 -3.9000002422219 -2.2999999682654 -29.9999985510007 -24.6000003814697 -49.5999984741211 0.800000011920929 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -4 1 1 0 16 0 40 16 0 80 80 0 0.508180708180708 0.00953279715031385 254.090354090354 254.090354090354 -3.9000002422219 -2.2999999682654 -29.9999985510007 -22.7999992370605 -46.5999984741211 0.600000023841858 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -5 1 1 0 16 0 41 16 0 80 80 0 0.538461538461538 0.00899406615644693 269.230769230769 269.230769230769 -3.9000002422219 -2.2999999682654 -29.9999985510007 -21.1000003814697 -43.5 0.400000005960464 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -6 1 1 0 16 0 42 16 0 80 80 0 0.582417582417582 0.00831656809896231 291.208791208791 291.208791208791 -3.9000002422219 -2.2999999682654 -29.9999985510007 -19.2999992370605 -40.5 0.100000001490116 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -7 1 1 0 16 0 43 16 0 80 80 0 0.563125763125763 0.00859958119690418 281.562881562882 281.562881562882 -3.9000002422219 -2.2999999682654 -29.9999985510007 -17.6000003814697 -37.5 -0.100000001490116 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -8 1 1 0 16 0 44 16 0 80 80 0 0.50964590964591 0.00950406584888697 254.822954822955 254.822954822955 -3.9000002422219 -2.2999999682654 -29.9999985510007 -15.8000001907349 -34.5 -0.400000005960464 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -9 1 1 0 16 0 45 16 0 80 80 0 0.47032967032967 0.0103007201105356 235.164835164835 235.164835164835 -3.9000002422219 -2.2999999682654 -29.9999985510007 -14.1000003814697 -31.3999996185303 -0.600000023841858 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -10 1 1 0 16 0 46 16 0 80 80 0 0.470573870573871 0.0102931028231978 235.286935286935 235.286935286935 -3.9000002422219 -2.2999999682654 -29.9999985510007 -12.3000001907349 -28.3999996185303 -0.800000011920929 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -11 1 1 0 16 0 47 16 0 80 80 0 0.457631257631258 0.0105842854827642 228.815628815629 228.815628815629 -3.9000002422219 -2.2999999682654 -29.9999985510007 -10.6000003814697 -25.3999996185303 -1.10000002384186 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -12 1 1 0 16 0 48 16 0 80 80 0 0.442735042735043 0.0109424209222198 221.367521367521 221.367521367521 -3.9000002422219 -2.2999999682654 -29.9999985510007 -8.89999961853027 -22.3999996185303 -1.29999995231628 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -13 1 1 0 16 0 49 16 0 80 80 0 0.437851037851038 0.0110645797103643 218.925518925519 218.925518925519 -3.9000002422219 -2.2999999682654 -29.9999985510007 -7.09999990463257 -19.2999992370605 -1.5 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -14 1 1 0 16 0 50 16 0 80 80 0 0.434676434676435 0.0111443726345897 217.338217338217 217.338217338217 -3.9000002422219 -2.2999999682654 -29.9999985510007 -5.40000009536743 -16.2999992370605 -1.79999995231628 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -15 1 1 0 16 0 51 16 0 80 80 0 0.419291819291819 0.0115514537319541 209.64590964591 209.64590964591 -3.9000002422219 -2.2999999682654 -29.9999985510007 -3.59999990463257 -13.3000001907349 -2 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -16 1 1 0 16 0 52 16 0 80 80 0 0.422222222222222 0.011470353230834 211.111111111111 211.111111111111 -3.9000002422219 -2.2999999682654 -29.9999985510007 -1.89999997615814 -10.3000001907349 -2.29999995231628 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -17 1 1 0 16 0 53 16 0 80 80 0 0.442979242979243 0.0109320040792227 221.489621489621 221.489621489621 -3.9000002422219 -2.2999999682654 -29.9999985510007 -0.100000001490116 -7.19999980926514 -2.5 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -18 1 1 0 16 0 54 16 0 80 80 0 0.454212454212454 0.010661456733942 227.106227106227 227.106227106227 -3.9000002422219 -2.2999999682654 -29.9999985510007 1.60000002384186 -4.19999980926514 -2.70000004768372 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -19 1 1 0 16 0 55 16 0 80 80 0 0.438583638583639 0.0110444780439138 219.291819291819 219.291819291819 -3.9000002422219 -2.2999999682654 -29.9999985510007 3.40000009536743 -1.20000004768372 -3 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -20 1 1 0 16 0 56 16 0 80 80 0 0.451770451770452 0.0107228131964803 225.885225885226 225.885225885226 -3.9000002422219 -2.2999999682654 -29.9999985510007 5.09999990463257 1.79999995231628 -3.20000004768372 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -21 1 1 0 16 0 57 16 0 80 80 0 0.43956043956044 0.0110190957784653 219.78021978022 219.78021978022 -3.9000002422219 -2.2999999682654 -29.9999985510007 6.90000009536743 4.90000009536743 -3.5 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -22 1 1 0 16 0 58 16 0 80 80 0 0.418559218559219 0.0115696135908365 209.279609279609 209.279609279609 -3.9000002422219 -2.2999999682654 -29.9999985510007 8.60000038146973 7.90000009536743 -3.70000004768372 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -23 1 1 0 16 0 59 16 0 80 80 0 0.393406593406593 0.0123128313571215 196.703296703297 196.703296703297 -3.9000002422219 -2.2999999682654 -29.9999985510007 10.3999996185303 10.8999996185303 -3.90000009536743 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -24 1 1 0 16 0 60 16 0 80 80 0 0.355799755799756 0.0136143220588565 177.899877899878 177.899877899878 -3.9000002422219 -2.2999999682654 -29.9999985510007 12.1000003814697 13.8999996185303 -4.19999980926514 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -25 1 1 0 16 0 61 16 0 80 80 0 0.317460317460317 0.0152558228000998 158.730158730159 158.730158730159 -3.9000002422219 -2.2999999682654 -29.9999985510007 13.8000001907349 17 -4.40000009536743 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -26 1 1 0 16 0 62 16 0 80 80 0 0.341636141636142 0.0141819268465042 170.818070818071 170.818070818071 -3.9000002422219 -2.2999999682654 -29.9999985510007 15.6000003814697 20 -4.69999980926514 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -27 1 1 0 16 0 63 16 0 80 80 0 0.339438339438339 0.0142712369561195 169.71916971917 169.71916971917 -3.9000002422219 -2.2999999682654 -29.9999985510007 17.2999992370605 23 -4.90000009536743 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -28 1 1 0 16 0 64 16 0 80 80 0 0.343345543345543 0.0141055593267083 171.672771672772 171.672771672772 -3.9000002422219 -2.2999999682654 -29.9999985510007 19.1000003814697 26 -5.09999990463257 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -29 1 1 0 16 0 65 16 0 80 80 0 0.381929181929182 0.0126843014732003 190.964590964591 190.964590964591 -3.9000002422219 -2.2999999682654 -29.9999985510007 20.7999992370605 29.1000003814697 -5.40000009536743 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -30 1 1 0 16 0 66 16 0 80 80 0 0.434920634920635 0.0111344903707504 217.460317460317 217.460317460317 -3.9000002422219 -2.2999999682654 -29.9999985510007 22.6000003814697 32.0999984741211 -5.59999990463257 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -31 1 1 0 16 0 67 16 0 80 80 0 0.430769230769231 0.0112440697848797 215.384615384615 215.384615384615 -3.9000002422219 -2.2999999682654 -29.9999985510007 24.2999992370605 35.0999984741211 -5.80000019073486 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -32 1 1 0 16 0 68 16 0 80 80 0 0.448595848595849 0.010798366740346 224.297924297924 224.297924297924 -3.9000002422219 -2.2999999682654 -29.9999985510007 26.1000003814697 38.0999984741211 -6.09999990463257 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -33 1 1 0 16 0 69 16 0 80 80 0 0.508424908424908 0.00952458381652832 254.212454212454 254.212454212454 -3.9000002422219 -2.2999999682654 -29.9999985510007 27.7999992370605 41.0999984741211 -6.30000019073486 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -34 1 1 0 16 0 70 16 0 80 80 0 0.47032967032967 0.0102959554642439 235.164835164835 235.164835164835 -3.9000002422219 -2.2999999682654 -29.9999985510007 29.6000003814697 44.2000007629395 -6.59999990463257 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -35 1 1 0 16 0 71 16 0 80 80 0 0.511843711843712 0.00946179777383804 255.921855921856 255.921855921856 -3.9000002422219 -2.2999999682654 -29.9999985510007 31.2999992370605 47.2000007629395 -6.80000019073486 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -36 1 1 0 16 0 72 16 0 80 80 0 0.471794871794872 0.0102670257911086 235.897435897436 235.897435897436 -3.9000002422219 -2.2999999682654 -29.9999985510007 33.0999984741211 50.2000007629395 -7 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -37 1 1 0 16 0 73 16 0 80 80 0 0.472771672771673 0.0102435695007443 236.385836385836 236.385836385836 -3.9000002422219 -2.2999999682654 -29.9999985510007 34.7999992370605 53.2000007629395 -7.30000019073486 3.5 0 0 1 0 0 2.7 2.7 0.0300002002716064 0 0 0 0 80 0 0 0 0 0 0 0 11 0 0 0 0 0 -# === END OF DATA DESCRIPTION FILE =============================================== - diff --git a/nibabel/tests/data/variant_v4_2_header.PAR b/nibabel/tests/data/variant_v4_2_header.PAR deleted file mode 100644 index 277aea358b..0000000000 --- a/nibabel/tests/data/variant_v4_2_header.PAR +++ /dev/null @@ -1,128 +0,0 @@ -# === DATA DESCRIPTION FILE ====================================================== -# -# CAUTION - Investigational device. -# Limited by Federal Law to investigational use. -# -# Dataset name: E:\\Export\phantom_EPI_asc_CLEAR_2_1 -# -# CLINICAL TRYOUT Research image export tool V4.2 -# -# === GENERAL INFORMATION ======================================================== -# -. Patient name : phantom -. Examination name : Konvertertest -. Protocol name : EPI_asc CLEAR -. Examination date/time : 2014.02.14 / 09:00:57 -. Series_data_type : Image MRSERIES -. Acquisition nr : 2 -. Reconstruction nr : 1 -. Scan Duration [sec] : 14 -. Max. number of cardiac phases : 1 -. Max. number of echoes : 1 -. Max. number of slices/locations : 9 -. Max. number of dynamics : 3 -. Max. number of mixes : 1 -. Patient Position : HFS -. Preparation direction : Anterior-Posterior -. Technique : FEEPI -. Scan resolution (x, y) : 64 39 -. Scan mode : MS -. Repetition time [msec] : 21225.76 -. FOV (ap,fh,rl) [mm] : 240.000 70.000 240.000 -. Water Fat shift [pixels] : 11.050 -. Angulation midslice(ap,fh,rl)[degr]: -13.265 0.000 0.000 -. Off Centre midslice(ap,fh,rl) [mm] : 2.508 30.339 -16.032 -. Flow compensation <0=no 1=yes> ? : 0 -. Presaturation <0=no 1=yes> ? : 0 -. Phase encoding velocity [cm/sec] : 0.000000 0.000000 0.000000 -. MTC <0=no 1=yes> ? : 0 -. SPIR <0=no 1=yes> ? : 1 -. EPI factor <0,1=no EPI> : 39 -. Dynamic scan <0=no 1=yes> ? : 1 -. Diffusion <0=no 1=yes> ? : 0 -. Diffusion echo time [msec] : 0.00 -. Max. number of diffusion values : 1 -. Max. number of gradient orients : 1 -# -# === PIXEL VALUES ============================================================= -# PV = pixel value in REC file, FP = floating point value, DV = displayed value on console -# RS = rescale slope, RI = rescale intercept, SS = scale slope -# DV = PV * RS + RI FP = DV / (RS * SS) -# -# === IMAGE INFORMATION DEFINITION ============================================= -# The rest of this file contains ONE line per image, this line contains the following information: -# -# slice number (integer) -# echo number (integer) -# dynamic scan number (integer) -# cardiac phase number (integer) -# image_type_mr (integer) -# scanning sequence (integer) -# index in REC file (in images) (integer) -# image pixel size (in bits) (integer) -# scan percentage (integer) -# recon resolution (x y) (2*integer) -# rescale intercept (float) -# rescale slope (float) -# scale slope (float) -# window center (integer) -# window width (integer) -# image angulation (ap,fh,rl in degrees ) (3*float) -# image offcentre (ap,fh,rl in mm ) (3*float) -# slice thickness (in mm ) (float) -# slice gap (in mm ) (float) -# image_display_orientation (integer) -# slice orientation ( TRA/SAG/COR ) (integer) -# fmri_status_indication (integer) -# image_type_ed_es (end diast/end syst) (integer) -# pixel spacing (x,y) (in mm) (2*float) -# echo_time (float) -# dyn_scan_begin_time (float) -# trigger_time (float) -# diffusion_b_factor (float) -# number of averages (integer) -# image_flip_angle (in degrees) (float) -# cardiac frequency (bpm) (integer) -# minimum RR-interval (in ms) (integer) -# maximum RR-interval (in ms) (integer) -# TURBO factor <0=no turbo> (integer) -# Inversion delay (in ms) (float) -# diffusion b value number (imagekey!) (integer) -# gradient orientation number (imagekey!) (integer) -# contrast type (string) -# diffusion anisotropy type (string) -# diffusion (ap, fh, rl) (3*float) -# label type (ASL) (imagekey!) (integer) -# -# === IMAGE INFORMATION ========================================================== -# sl ec dyn ph ty idx pix scan% rec size (re)scale window angulation offcentre thick gap info spacing echo dtime ttime diff avg flip freq RR-int turbo delay b grad cont anis diffusion L.ty - - 1 1 1 1 0 2 0 16 62 64 64 0.00000 1.29035 4.28404e-003 1070 1860 -13.26 -0.00 -0.00 2.51 -0.81 -8.69 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 2 1 1 1 0 2 1 16 62 64 64 0.00000 1.29035 4.28404e-003 1122 1951 -13.26 -0.00 -0.00 2.51 6.98 -10.53 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 3 1 1 1 0 2 2 16 62 64 64 0.00000 1.29035 4.28404e-003 1137 1977 -13.26 -0.00 -0.00 2.51 14.77 -12.36 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 4 1 1 1 0 2 3 16 62 64 64 0.00000 1.29035 4.28404e-003 1217 2116 -13.26 -0.00 -0.00 2.51 22.55 -14.20 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 5 1 1 1 0 2 4 16 62 64 64 0.00000 1.29035 4.28404e-003 1216 2113 -13.26 -0.00 -0.00 2.51 30.34 -16.03 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 6 1 1 1 0 2 5 16 62 64 64 0.00000 1.29035 4.28404e-003 1141 1983 -13.26 -0.00 -0.00 2.51 38.13 -17.87 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 7 1 1 1 0 2 6 16 62 64 64 0.00000 1.29035 4.28404e-003 1119 1945 -13.26 -0.00 -0.00 2.51 45.91 -19.70 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 8 1 1 1 0 2 7 16 62 64 64 0.00000 1.29035 4.28404e-003 1097 1907 -13.26 -0.00 -0.00 2.51 53.70 -21.54 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 9 1 1 1 0 2 8 16 62 64 64 0.00000 1.29035 4.28404e-003 1146 1991 -13.26 -0.00 -0.00 2.51 61.49 -23.37 6.000 2.000 0 1 0 2 3.750 3.750 30.00 0.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 1 1 2 1 0 2 9 16 62 64 64 0.00000 1.29035 4.28404e-003 1071 1863 -13.26 -0.00 -0.00 2.51 -0.81 -8.69 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 2 1 2 1 0 2 10 16 62 64 64 0.00000 1.29035 4.28404e-003 1123 1953 -13.26 -0.00 -0.00 2.51 6.98 -10.53 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 3 1 2 1 0 2 11 16 62 64 64 0.00000 1.29035 4.28404e-003 1135 1973 -13.26 -0.00 -0.00 2.51 14.77 -12.36 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 4 1 2 1 0 2 12 16 62 64 64 0.00000 1.29035 4.28404e-003 1209 2101 -13.26 -0.00 -0.00 2.51 22.55 -14.20 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 5 1 2 1 0 2 13 16 62 64 64 0.00000 1.29035 4.28404e-003 1215 2113 -13.26 -0.00 -0.00 2.51 30.34 -16.03 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 6 1 2 1 0 2 14 16 62 64 64 0.00000 1.29035 4.28404e-003 1145 1990 -13.26 -0.00 -0.00 2.51 38.13 -17.87 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 7 1 2 1 0 2 15 16 62 64 64 0.00000 1.29035 4.28404e-003 1119 1945 -13.26 -0.00 -0.00 2.51 45.91 -19.70 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 8 1 2 1 0 2 16 16 62 64 64 0.00000 1.29035 4.28404e-003 1093 1899 -13.26 -0.00 -0.00 2.51 53.70 -21.54 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 9 1 2 1 0 2 17 16 62 64 64 0.00000 1.29035 4.28404e-003 1150 1999 -13.26 -0.00 -0.00 2.51 61.49 -23.37 6.000 2.000 0 1 0 2 3.750 3.750 30.00 2.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 1 1 3 1 0 2 18 16 62 64 64 0.00000 1.29035 4.28404e-003 1070 1860 -13.26 -0.00 -0.00 2.51 -0.81 -8.69 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 2 1 3 1 0 2 19 16 62 64 64 0.00000 1.29035 4.28404e-003 1125 1955 -13.26 -0.00 -0.00 2.51 6.98 -10.53 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 3 1 3 1 0 2 20 16 62 64 64 0.00000 1.29035 4.28404e-003 1135 1973 -13.26 -0.00 -0.00 2.51 14.77 -12.36 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 4 1 3 1 0 2 21 16 62 64 64 0.00000 1.29035 4.28404e-003 1211 2105 -13.26 -0.00 -0.00 2.51 22.55 -14.20 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 5 1 3 1 0 2 22 16 62 64 64 0.00000 1.29035 4.28404e-003 1218 2118 -13.26 -0.00 -0.00 2.51 30.34 -16.03 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 6 1 3 1 0 2 23 16 62 64 64 0.00000 1.29035 4.28404e-003 1143 1987 -13.26 -0.00 -0.00 2.51 38.13 -17.87 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 7 1 3 1 0 2 24 16 62 64 64 0.00000 1.29035 4.28404e-003 1120 1947 -13.26 -0.00 -0.00 2.51 45.91 -19.70 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 8 1 3 1 0 2 25 16 62 64 64 0.00000 1.29035 4.28404e-003 1093 1901 -13.26 -0.00 -0.00 2.51 53.70 -21.54 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - 9 1 3 1 0 2 26 16 62 64 64 0.00000 1.29035 4.28404e-003 1151 2001 -13.26 -0.00 -0.00 2.51 61.49 -23.37 6.000 2.000 0 1 0 2 3.750 3.750 30.00 4.00 0.00 0.00 0 90.00 0 0 0 39 0.0 1 1 8 0 0.000 0.000 0.000 1 - -# === END OF DATA DESCRIPTION FILE =============================================== diff --git a/nibabel/tests/nibabel_data.py b/nibabel/tests/nibabel_data.py deleted file mode 100644 index 5919eba925..0000000000 --- a/nibabel/tests/nibabel_data.py +++ /dev/null @@ -1,47 +0,0 @@ -"""Functions / decorators for finding / requiring nibabel-data directory""" - -import unittest -from os import environ, listdir -from os.path import dirname, exists, isdir, realpath -from os.path import join as pjoin - - -def get_nibabel_data(): - """Return path to nibabel-data or empty string if missing - - First use ``NIBABEL_DATA_DIR`` environment variable. - - If this variable is missing then look for data in directory below package - directory. - """ - nibabel_data = environ.get('NIBABEL_DATA_DIR') - if nibabel_data is None: - mod = __import__('nibabel') - containing_path = dirname(dirname(realpath(mod.__file__))) - nibabel_data = pjoin(containing_path, 'nibabel-data') - return nibabel_data if isdir(nibabel_data) else '' - - -def needs_nibabel_data(subdir=None): - """Decorator for tests needing nibabel-data - - Parameters - ---------- - subdir : None or str - Subdirectory we need in nibabel-data directory. If None, only require - nibabel-data directory itself. - - Returns - ------- - skip_dec : decorator - Decorator skipping tests if required directory not present - """ - nibabel_data = get_nibabel_data() - if nibabel_data == '': - return unittest.skip('Need nibabel-data directory for this test') - if subdir is None: - return lambda x: x - required_path = pjoin(nibabel_data, subdir) - # Path should not be empty (as is the case for not-updated submodules) - have_files = exists(required_path) and len(listdir(required_path)) > 0 - return unittest.skipUnless(have_files, f'Need files in {required_path} for these tests') diff --git a/nibabel/tests/scriptrunner.py b/nibabel/tests/scriptrunner.py deleted file mode 100644 index 2f3de50791..0000000000 --- a/nibabel/tests/scriptrunner.py +++ /dev/null @@ -1,150 +0,0 @@ -"""Module to help tests check script output - -Provides class to be instantiated in tests that check scripts. Usually works -something like this in a test module:: - - from .scriptrunner import ScriptRunner - runner = ScriptRunner() - -Then, in the tests, something like:: - - code, stdout, stderr = runner.run_command(['my-script', my_arg]) - assert_equal(code, 0) - assert_equal(stdout, b'This script ran OK') -""" - -import os -import sys -from os.path import dirname, isdir, isfile, pathsep, realpath -from os.path import join as pjoin -from subprocess import PIPE, Popen - -MY_PACKAGE = __package__ - - -def local_script_dir(script_sdir): - """Get local script directory if running in development dir, else None""" - # Check for presence of scripts in development directory. ``realpath`` - # allows for the situation where the development directory has been linked - # into the path. - package_path = dirname(__import__(MY_PACKAGE).__file__) - above_us = realpath(pjoin(package_path, '..')) - devel_script_dir = pjoin(above_us, script_sdir) - if isfile(pjoin(above_us, 'setup.py')) and isdir(devel_script_dir): - return devel_script_dir - return None - - -def local_module_dir(module_name): - """Get local module directory if running in development dir, else None""" - mod = __import__(module_name) - containing_path = dirname(dirname(realpath(mod.__file__))) - if containing_path == realpath(os.getcwd()): - return containing_path - return None - - -class ScriptRunner: - """Class to run scripts and return output - - Finds local scripts and local modules if running in the development - directory, otherwise finds system scripts and modules. - """ - - def __init__( - self, - script_sdir='scripts', - module_sdir=MY_PACKAGE, - debug_print_var=None, - output_processor=lambda x: x, - ): - """Init ScriptRunner instance - - Parameters - ---------- - script_sdir : str, optional - Name of subdirectory in top-level directory (directory containing - setup.py), to find scripts in development tree. Typically - 'scripts', but might be 'bin'. - module_sdir : str, optional - Name of subdirectory in top-level directory (directory containing - setup.py), to find main package directory. - debug_print_vsr : str, optional - Name of environment variable that indicates whether to do debug - printing or no. - output_processor : callable - Callable to run on the stdout, stderr outputs before returning - them. Use this to convert bytes to unicode, strip whitespace, etc. - """ - self.local_script_dir = local_script_dir(script_sdir) - self.local_module_dir = local_module_dir(module_sdir) - if debug_print_var is None: - debug_print_var = f'{module_sdir.upper()}_DEBUG_PRINT' - self.debug_print = os.environ.get(debug_print_var, False) - self.output_processor = output_processor - - def run_command(self, cmd, check_code=True): - """Run command sequence `cmd` returning exit code, stdout, stderr - - Parameters - ---------- - cmd : str or sequence - string with command name or sequence of strings defining command - check_code : {True, False}, optional - If True, raise error for non-zero return code - - Returns - ------- - returncode : int - return code from execution of `cmd` - stdout : bytes - stdout from `cmd` - stderr : bytes - stderr from `cmd` - """ - if isinstance(cmd, str): - cmd = [cmd] - else: - cmd = list(cmd) - if not self.local_script_dir is None: - # Windows can't run script files without extensions natively so we need - # to run local scripts (no extensions) via the Python interpreter. On - # Unix, we might have the wrong incantation for the Python interpreter - # in the hash bang first line in the source file. So, either way, run - # the script through the Python interpreter - cmd = [sys.executable, pjoin(self.local_script_dir, cmd[0])] + cmd[1:] - if os.name == 'nt': - # Quote any arguments with spaces. The quotes delimit the arguments - # on Windows, and the arguments might be file paths with spaces. - # On Unix the list elements are each separate arguments. - cmd = [f'"{c}"' if ' ' in c else c for c in cmd] - if self.debug_print: - print(f"Running command '{cmd}'") - env = os.environ - if not self.local_module_dir is None: - # module likely comes from the current working directory. We might need - # that directory on the path if we're running the scripts from a - # temporary directory - env = env.copy() - pypath = env.get('PYTHONPATH', None) - if pypath is None: - env['PYTHONPATH'] = self.local_module_dir - else: - env['PYTHONPATH'] = self.local_module_dir + pathsep + pypath - proc = Popen(cmd, stdout=PIPE, stderr=PIPE, env=env) - stdout, stderr = proc.communicate() - if proc.poll() is None: - proc.terminate() - if check_code and proc.returncode != 0: - raise RuntimeError( - f"""Command "{cmd}" failed with - stdout - ------ - {stdout} - stderr - ------ - {stderr} - """ - ) - opp = self.output_processor - return proc.returncode, opp(stdout), opp(stderr) diff --git a/nibabel/tests/test_affines.py b/nibabel/tests/test_affines.py deleted file mode 100644 index d4ea11821b..0000000000 --- a/nibabel/tests/test_affines.py +++ /dev/null @@ -1,238 +0,0 @@ -# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: - -from itertools import product - -import numpy as np -import pytest -from numpy.testing import assert_almost_equal, assert_array_almost_equal, assert_array_equal - -from ..affines import ( - AffineError, - append_diag, - apply_affine, - dot_reduce, - from_matvec, - obliquity, - rescale_affine, - to_matvec, - voxel_sizes, -) -from ..eulerangles import euler2mat -from ..orientations import aff2axcodes - - -def validated_apply_affine(T, xyz): - # This was the original apply_affine implementation that we've stashed here - # to test against - xyz = np.asarray(xyz) - shape = xyz.shape[0:-1] - XYZ = np.dot(np.reshape(xyz, (np.prod(shape), 3)), T[0:3, 0:3].T) - XYZ[:, 0] += T[0, 3] - XYZ[:, 1] += T[1, 3] - XYZ[:, 2] += T[2, 3] - XYZ = np.reshape(XYZ, shape + (3,)) - return XYZ - - -def test_apply_affine(): - rng = np.random.RandomState(20110903) - aff = np.diag([2, 3, 4, 1]) - pts = rng.uniform(size=(4, 3)) - assert_array_equal(apply_affine(aff, pts), pts * [[2, 3, 4]]) - aff[:3, 3] = [10, 11, 12] - assert_array_equal(apply_affine(aff, pts), pts * [[2, 3, 4]] + [[10, 11, 12]]) - aff[:3, :] = rng.normal(size=(3, 4)) - exp_res = np.concatenate((pts.T, np.ones((1, 4))), axis=0) - exp_res = np.dot(aff, exp_res)[:3, :].T - assert_array_equal(apply_affine(aff, pts), exp_res) - # Check we get the same result as the previous implementation - assert_almost_equal(validated_apply_affine(aff, pts), apply_affine(aff, pts)) - # Check that lists work for inputs - assert_array_equal(apply_affine(aff.tolist(), pts.tolist()), exp_res) - # Check that it's the same as a banal implementation in the simple case - aff = np.array([[0, 2, 0, 10], [3, 0, 0, 11], [0, 0, 4, 12], [0, 0, 0, 1]]) - pts = np.array([[1, 2, 3], [2, 3, 4], [4, 5, 6], [6, 7, 8]]) - exp_res = (np.dot(aff[:3, :3], pts.T) + aff[:3, 3:4]).T - assert_array_equal(apply_affine(aff, pts), exp_res) - # That points can be reshaped and you'll get the same shape output - pts = pts.reshape((2, 2, 3)) - exp_res = exp_res.reshape((2, 2, 3)) - assert_array_equal(apply_affine(aff, pts), exp_res) - - # Check inplace modification. - res = apply_affine(aff, pts, inplace=True) - assert_array_equal(res, exp_res) - assert np.shares_memory(res, pts) - - # That ND also works - for N in range(2, 6): - aff = np.eye(N) - nd = N - 1 - aff[:nd, :nd] = rng.normal(size=(nd, nd)) - pts = rng.normal(size=(2, 3, nd)) - res = apply_affine(aff, pts) - # crude apply - new_pts = np.ones((N, 6)) - new_pts[:-1, :] = np.rollaxis(pts, -1).reshape((nd, 6)) - exp_pts = np.dot(aff, new_pts) - exp_pts = np.rollaxis(exp_pts[:-1, :], 0, 2) - exp_res = exp_pts.reshape((2, 3, nd)) - assert_array_almost_equal(res, exp_res) - - -def test_matrix_vector(): - for M, N in ((4, 4), (5, 4), (4, 5)): - xform = np.zeros((M, N)) - xform[:-1, :] = np.random.normal(size=(M - 1, N)) - xform[-1, -1] = 1 - newmat, newvec = to_matvec(xform) - mat = xform[:-1, :-1] - vec = xform[:-1, -1] - assert_array_equal(newmat, mat) - assert_array_equal(newvec, vec) - assert newvec.shape == (M - 1,) - assert_array_equal(from_matvec(mat, vec), xform) - # Check default translation works - xform_not = xform[:] - xform_not[:-1, :] = 0 - assert_array_equal(from_matvec(mat), xform) - assert_array_equal(from_matvec(mat, None), xform) - # Check array-like works - newmat, newvec = to_matvec(xform.tolist()) - assert_array_equal(newmat, mat) - assert_array_equal(newvec, vec) - assert_array_equal(from_matvec(mat.tolist(), vec.tolist()), xform) - - -def test_append_diag(): - # Routine for appending diagonal elements - assert_array_equal(append_diag(np.diag([2, 3, 1]), [1]), np.diag([2, 3, 1, 1])) - assert_array_equal(append_diag(np.diag([2, 3, 1]), [1, 1]), np.diag([2, 3, 1, 1, 1])) - aff = np.array( - [ - [2, 0, 0], - [0, 3, 0], - [0, 0, 1], - [0, 0, 1], - ] - ) - assert_array_equal( - append_diag(aff, [5], [9]), - [ - [2, 0, 0, 0], - [0, 3, 0, 0], - [0, 0, 0, 1], - [0, 0, 5, 9], - [0, 0, 0, 1], - ], - ) - assert_array_equal( - append_diag(aff, [5, 6], [9, 10]), - [ - [2, 0, 0, 0, 0], - [0, 3, 0, 0, 0], - [0, 0, 0, 0, 1], - [0, 0, 5, 0, 9], - [0, 0, 0, 6, 10], - [0, 0, 0, 0, 1], - ], - ) - aff = np.array( - [ - [2, 0, 0, 0], - [0, 3, 0, 0], - [0, 0, 0, 1], - ] - ) - assert_array_equal( - append_diag(aff, [5], [9]), - [ - [2, 0, 0, 0, 0], - [0, 3, 0, 0, 0], - [0, 0, 0, 5, 9], - [0, 0, 0, 0, 1], - ], - ) - # Length of starts has to match length of steps - with pytest.raises(AffineError): - append_diag(aff, [5, 6], [9]) - - -def test_dot_reduce(): - # Chaining numpy dot - # Error for no arguments - with pytest.raises(TypeError): - dot_reduce() - # Anything at all on its own, passes through - assert dot_reduce(1) == 1 - assert dot_reduce(None) is None - assert dot_reduce([1, 2, 3]) == [1, 2, 3] - # Two or more -> dot product - vec = [1, 2, 3] - mat = np.arange(4, 13).reshape((3, 3)) - assert_array_equal(dot_reduce(vec, mat), np.dot(vec, mat)) - assert_array_equal(dot_reduce(mat, vec), np.dot(mat, vec)) - mat2 = np.arange(13, 22).reshape((3, 3)) - assert_array_equal(dot_reduce(mat2, vec, mat), mat2 @ (vec @ mat)) - assert_array_equal(dot_reduce(mat, vec, mat2), mat @ (vec @ mat2)) - - -def test_voxel_sizes(): - affine = np.diag([2, 3, 4, 1]) - assert_almost_equal(voxel_sizes(affine), [2, 3, 4]) - # Some example rotations - rotations = [] - for x_rot, y_rot, z_rot in product((0, 0.4), (0, 0.6), (0, 0.8)): - rotations.append(euler2mat(z_rot, y_rot, x_rot)) - # Works on any size of array - for n in range(2, 10): - vox_sizes = np.arange(n) + 4.1 - aff = np.diag(list(vox_sizes) + [1]) - assert_almost_equal(voxel_sizes(aff), vox_sizes) - # Translations make no difference - aff[:-1, -1] = np.arange(n) + 10 - assert_almost_equal(voxel_sizes(aff), vox_sizes) - # Does not have to be square - new_row = np.vstack((np.zeros(n + 1), aff)) - assert_almost_equal(voxel_sizes(new_row), vox_sizes) - new_col = np.c_[np.zeros(n + 1), aff] - assert_almost_equal(voxel_sizes(new_col), [0] + list(vox_sizes)) - if n < 3: - continue - # Rotations do not change the voxel size - for rotation in rotations: - rot_affine = np.eye(n + 1) - rot_affine[:3, :3] = rotation - full_aff = rot_affine.dot(aff) - assert_almost_equal(voxel_sizes(full_aff), vox_sizes) - - -def test_obliquity(): - """Check the calculation of inclination of an affine axes.""" - from math import pi - - aligned = np.diag([2.0, 2.0, 2.3, 1.0]) - aligned[:-1, -1] = [-10, -10, -7] - R = from_matvec(euler2mat(x=0.09, y=0.001, z=0.001), [0.0, 0.0, 0.0]) - oblique = R.dot(aligned) - assert_almost_equal(obliquity(aligned), [0.0, 0.0, 0.0]) - assert_almost_equal(obliquity(oblique) * 180 / pi, [0.0810285, 5.1569949, 5.1569376]) - - -def test_rescale_affine(): - rng = np.random.RandomState(20200415) - orig_shape = rng.randint(low=20, high=512, size=(3,)) - orig_aff = np.eye(4) - orig_aff[:3, :] = rng.normal(size=(3, 4)) - orig_axcodes = aff2axcodes(orig_aff) - orig_centroid = apply_affine(orig_aff, (orig_shape - 1) // 2) - - for new_shape in (None, tuple(orig_shape), (256, 256, 256), (64, 64, 40)): - for new_zooms in ((1, 1, 1), (2, 2, 3), (0.5, 0.5, 0.5)): - new_aff = rescale_affine(orig_aff, orig_shape, new_zooms, new_shape) - assert aff2axcodes(new_aff) == orig_axcodes - if new_shape is None: - new_shape = tuple(orig_shape) - new_centroid = apply_affine(new_aff, (np.array(new_shape) - 1) // 2) - assert_almost_equal(new_centroid, orig_centroid) diff --git a/nibabel/tests/test_analyze.py b/nibabel/tests/test_analyze.py deleted file mode 100644 index 85669b3661..0000000000 --- a/nibabel/tests/test_analyze.py +++ /dev/null @@ -1,916 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Test Analyze headers - -See test_wrapstruct.py for tests of the wrapped structarr-ness of the Analyze -header -""" - -import itertools -import logging -import os -import pickle -import re -from io import BytesIO, StringIO - -import numpy as np -import pytest -from numpy.testing import assert_array_almost_equal, assert_array_equal - -from .. import imageglobals -from ..analyze import AnalyzeHeader, AnalyzeImage -from ..arraywriters import WriterError -from ..casting import sctypes_aliases -from ..nifti1 import Nifti1Header -from ..optpkg import optional_package -from ..spatialimages import HeaderDataError, HeaderTypeError, supported_np_types -from ..testing import ( - assert_dt_equal, - bytesio_filemap, - bytesio_round_trip, - data_path, - suppress_warnings, -) -from ..tmpdirs import InTemporaryDirectory -from . import test_spatialimages as tsi -from . import test_wrapstruct as tws - -HAVE_ZSTD = optional_package('pyzstd')[1] - -header_file = os.path.join(data_path, 'analyze.hdr') - -PIXDIM0_MSG = 'pixdim[1,2,3] should be non-zero; setting 0 dims to 1' - - -def add_duplicate_types(supported_np_types): - # Update supported numpy types with named scalar types that map to the same set of dtypes - dtypes = {np.dtype(t) for t in supported_np_types} - supported_np_types.update(scalar for scalar in sctypes_aliases if np.dtype(scalar) in dtypes) - - -class TestAnalyzeHeader(tws._TestLabeledWrapStruct): - header_class = AnalyzeHeader - example_file = header_file - sizeof_hdr = AnalyzeHeader.sizeof_hdr - supported_np_types = {np.uint8, np.int16, np.int32, np.float32, np.float64, np.complex64} - add_duplicate_types(supported_np_types) - - def test_supported_types(self): - hdr = self.header_class() - assert self.supported_np_types == supported_np_types(hdr) - - def get_bad_bb(self): - # A value for the binary block that should raise an error - # Completely zeros binary block (nearly) always (fairly) bad - return b'\x00' * self.header_class.template_dtype.itemsize - - def test_general_init(self): - super().test_general_init() - hdr = self.header_class() - # an empty header has shape (0,) - like an empty array - # (np.array([])) - assert hdr.get_data_shape() == (0,) - # The affine is always homogeneous 3D regardless of shape. The - # default affine will have -1 as the X zoom iff default_x_flip - # is True (which it is by default). We have to be careful of the - # translations though - these arise from SPM's use of the origin - # field, and the center of the image. - assert_array_equal(np.diag(hdr.get_base_affine()), [-1, 1, 1, 1]) - # But zooms only go with number of dimensions - assert hdr.get_zooms() == (1.0,) - - def test_header_size(self): - assert self.header_class.template_dtype.itemsize == self.sizeof_hdr - - def test_empty(self): - hdr = self.header_class() - assert len(hdr.binaryblock) == self.sizeof_hdr - assert hdr['sizeof_hdr'] == self.sizeof_hdr - assert np.all(hdr['dim'][1:] == 1) - assert hdr['dim'][0] == 0 - assert np.all(hdr['pixdim'] == 1) - assert hdr['datatype'] == 16 # float32 - assert hdr['bitpix'] == 32 - - def _set_something_into_hdr(self, hdr): - # Called from test_bytes test method. Specific to the header data type - with suppress_warnings(): - hdr.set_data_shape((1, 2, 3)) - - def test_checks(self): - # Test header checks - hdr_t = self.header_class() - # _dxer just returns the diagnostics as a string - assert self._dxer(hdr_t) == '' - hdr = hdr_t.copy() - hdr['sizeof_hdr'] = 1 - with suppress_warnings(): - assert self._dxer(hdr) == 'sizeof_hdr should be ' + str(self.sizeof_hdr) - hdr = hdr_t.copy() - hdr['datatype'] = 0 - assert self._dxer(hdr) == 'data code 0 not supported\nbitpix does not match datatype' - hdr = hdr_t.copy() - hdr['bitpix'] = 0 - assert self._dxer(hdr) == 'bitpix does not match datatype' - - def test_pixdim_checks(self): - hdr_t = self.header_class() - for i in (1, 2, 3): - hdr = hdr_t.copy() - hdr['pixdim'][i] = -1 - assert self._dxer(hdr) == 'pixdim[1,2,3] should be positive' - - def test_log_checks(self): - # Test logging, fixing, errors for header checking - HC = self.header_class - # magic - hdr = HC() - with suppress_warnings(): - hdr['sizeof_hdr'] = 350 # severity 30 - fhdr, message, raiser = self.log_chk(hdr, 30) - - assert fhdr['sizeof_hdr'] == self.sizeof_hdr - assert ( - message == f'sizeof_hdr should be {self.sizeof_hdr}; ' - f'set sizeof_hdr to {self.sizeof_hdr}' - ) - pytest.raises(*raiser) - # RGB datatype does not raise error - hdr = HC() - hdr.set_data_dtype('RGB') - fhdr, message, raiser = self.log_chk(hdr, 0) - # datatype not recognized - hdr = HC() - hdr['datatype'] = -1 # severity 40 - with suppress_warnings(): - fhdr, message, raiser = self.log_chk(hdr, 40) - assert message == 'data code -1 not recognized; not attempting fix' - - pytest.raises(*raiser) - # datatype not supported - hdr['datatype'] = 255 # severity 40 - fhdr, message, raiser = self.log_chk(hdr, 40) - assert message == 'data code 255 not supported; not attempting fix' - pytest.raises(*raiser) - # bitpix - hdr = HC() - hdr['datatype'] = 16 # float32 - hdr['bitpix'] = 16 # severity 10 - fhdr, message, raiser = self.log_chk(hdr, 10) - assert fhdr['bitpix'] == 32 - assert message == 'bitpix does not match datatype; setting bitpix to match datatype' - pytest.raises(*raiser) - - def test_pixdim_log_checks(self): - # pixdim positive - HC = self.header_class - hdr = HC() - hdr['pixdim'][1] = -2 # severity 35 - fhdr, message, raiser = self.log_chk(hdr, 35) - assert fhdr['pixdim'][1] == 2 - assert message == 'pixdim[1,2,3] should be positive; setting to abs of pixdim values' - pytest.raises(*raiser) - hdr = HC() - hdr['pixdim'][1] = 0 # severity 30 - fhdr, message, raiser = self.log_chk(hdr, 30) - assert fhdr['pixdim'][1] == 1 - assert message == PIXDIM0_MSG - pytest.raises(*raiser) - # both - hdr = HC() - hdr['pixdim'][1] = 0 # severity 30 - hdr['pixdim'][2] = -2 # severity 35 - fhdr, message, raiser = self.log_chk(hdr, 35) - assert fhdr['pixdim'][1] == 1 - assert fhdr['pixdim'][2] == 2 - assert message == ( - 'pixdim[1,2,3] should be non-zero and pixdim[1,2,3] should be ' - 'positive; setting 0 dims to 1 and setting to abs of pixdim values' - ) - pytest.raises(*raiser) - - def test_no_scaling_fixes(self): - # Check we do not fix slope or intercept - # - # We used to fix difficult-to-interpret slope and intercept values in - # headers that support them. Now we pass everything and let the - # `get_slope_inter()` routine reinterpet diffireinterpet difficult - # values. - # Analyze doesn't support slope or intercept; the tests are here for - # children of this class that do support them. - HC = self.header_class - if not HC.has_data_slope: - return - hdr = HC() - has_inter = HC.has_data_intercept - slopes = (1, 0, np.nan, np.inf, -np.inf) - inters = (0, np.nan, np.inf, -np.inf) if has_inter else (0,) - for slope, inter in itertools.product(slopes, inters): - hdr['scl_slope'] = slope - if has_inter: - hdr['scl_inter'] = inter - self.assert_no_log_err(hdr) - - def test_logger_error(self): - # Check that we can reset the logger and error level - HC = self.header_class - hdr = HC() - # Make a new logger - str_io = StringIO() - logger = logging.getLogger('test.logger') - logger.addHandler(logging.StreamHandler(str_io)) - # Prepare a defect: bitpix not matching data type - hdr['datatype'] = 16 # float32 - hdr['bitpix'] = 16 # severity 10 - logger.setLevel(10) - log_cache = imageglobals.logger, imageglobals.error_level - try: - # Check log message appears in new logger - imageglobals.logger = logger - hdr.copy().check_fix() - assert str_io.getvalue() == ( - 'bitpix does not match datatype; setting bitpix to match datatype\n' - ) - # Check that error_level in fact causes error to be raised - imageglobals.error_level = 10 - with pytest.raises(HeaderDataError): - hdr.copy().check_fix() - finally: - imageglobals.logger, imageglobals.error_level = log_cache - - def test_data_dtype(self): - # check getting and setting of data type - # codes / types supported by all binary headers - all_supported_types = ( - (2, np.uint8), - (4, np.int16), - (8, np.int32), - (16, np.float32), - (32, np.complex64), - (64, np.float64), - (128, np.dtype([('R', 'u1'), ('G', 'u1'), ('B', 'u1')])), - ) - # and unsupported - here using some labels instead - all_unsupported_types = (np.void, 'none', 'all', 0) - - def assert_set_dtype(dt_spec, np_dtype): - hdr = self.header_class() - hdr.set_data_dtype(dt_spec) - assert_dt_equal(hdr.get_data_dtype(), np_dtype) - - # Test code, type known to be supported by all types - for code, npt in all_supported_types: - # Can set with code value - assert_set_dtype(code, npt) - # or numpy type - assert_set_dtype(npt, npt) - # or numpy dtype - assert_set_dtype(np.dtype(npt), npt) - # Test numerical types supported by this header type - for npt in self.supported_np_types: - # numpy type - assert_set_dtype(npt, npt) - # or numpy dtype - assert_set_dtype(np.dtype(npt), npt) - # or swapped numpy dtype - assert_set_dtype(np.dtype(npt).newbyteorder(), npt) - # or string dtype code - assert_set_dtype(np.dtype(npt).str, npt) - # or string dtype code without byteorder - if np.dtype(npt).str[0] in '=|<>': - assert_set_dtype(np.dtype(npt).str[1:], npt) - # Test aliases to Python types - assert_set_dtype(float, np.float64) # float64 always supported - np_sys_int = np.dtype(int).type # int could be 32 or 64 bit - if issubclass(self.header_class, Nifti1Header): - # We don't allow int aliases in Nifti - with pytest.raises(ValueError): - hdr = self.header_class() - hdr.set_data_dtype(int) - elif np_sys_int in self.supported_np_types: # no int64 for Analyze - assert_set_dtype(int, np_sys_int) - hdr = self.header_class() - for inp in all_unsupported_types: - with pytest.raises(HeaderDataError): - hdr.set_data_dtype(inp) - - def test_shapes(self): - # Test that shape checks work - hdr = self.header_class() - for shape in ((2, 3, 4), (2, 3, 4, 5), (2, 3), (2,)): - hdr.set_data_shape(shape) - assert hdr.get_data_shape() == shape - # Check max works, but max+1 raises error - dim_dtype = hdr.structarr['dim'].dtype - mx = int(np.iinfo(dim_dtype).max) - shape = (mx,) - hdr.set_data_shape(shape) - assert hdr.get_data_shape() == shape - shape = (mx + 1,) - with pytest.raises(HeaderDataError): - hdr.set_data_shape(shape) - # Lists or tuples or arrays will work for setting shape - shape = (2, 3, 4) - for constructor in (list, tuple, np.array): - hdr.set_data_shape(constructor(shape)) - assert hdr.get_data_shape() == shape - - def test_read_write_data(self): - # Check reading and writing of data - hdr = self.header_class() - # Trying to read data from an empty header gives no data - bytes = hdr.data_from_fileobj(BytesIO()) - assert len(bytes) == 0 - # Setting no data into an empty header results in - no data - str_io = BytesIO() - hdr.data_to_fileobj([], str_io) - assert str_io.getvalue() == b'' - # Setting more data then there should be gives an error - with pytest.raises(HeaderDataError): - hdr.data_to_fileobj(np.zeros(3), str_io) - # Test valid write - hdr.set_data_shape((1, 2, 3)) - hdr.set_data_dtype(np.float32) - S = BytesIO() - data = np.arange(6, dtype=np.float64) - # data have to be the right shape - with pytest.raises(HeaderDataError): - hdr.data_to_fileobj(data, S) - data = data.reshape((1, 2, 3)) - # and size - with pytest.raises(HeaderDataError): - hdr.data_to_fileobj(data[:, :, :-1], S) - with pytest.raises(HeaderDataError): - hdr.data_to_fileobj(data[:, :-1, :], S) - # OK if so - hdr.data_to_fileobj(data, S) - # Read it back - data_back = hdr.data_from_fileobj(S) - # Should be about the same - assert_array_almost_equal(data, data_back) - # but with the header dtype, not the data dtype - assert hdr.get_data_dtype() == data_back.dtype - # this is with native endian, not so for swapped - S2 = BytesIO() - hdr2 = hdr.as_byteswapped() - hdr2.set_data_dtype(np.float32) - hdr2.set_data_shape((1, 2, 3)) - hdr2.data_to_fileobj(data, S2) - data_back2 = hdr2.data_from_fileobj(S2) - # Compares the same - assert_array_almost_equal(data_back, data_back2) - # Same dtype names - assert data_back.dtype.name == data_back2.dtype.name - # But not the same endianness - assert data.dtype.byteorder != data_back2.dtype.byteorder - # Try scaling down to integer - hdr.set_data_dtype(np.uint8) - S3 = BytesIO() - # Analyze header cannot do scaling, so turn off scaling with - # 'rescale=False' - with np.errstate(invalid='ignore'): - hdr.data_to_fileobj(data, S3, rescale=False) - data_back = hdr.data_from_fileobj(S3) - assert_array_almost_equal(data, data_back) - # If the header can't do scaling, rescale raises an error - if not hdr.has_data_slope: - with pytest.raises(HeaderTypeError): - hdr.data_to_fileobj(data, S3) - with pytest.raises(HeaderTypeError): - hdr.data_to_fileobj(data, S3, rescale=True) - # If not scaling we lose precision from rounding - data = np.arange(6, dtype=np.float64).reshape((1, 2, 3)) + 0.5 - with np.errstate(invalid='ignore'): - hdr.data_to_fileobj(data, S3, rescale=False) - data_back = hdr.data_from_fileobj(S3) - assert not np.allclose(data, data_back) - # Test RGB image - dtype = np.dtype([('R', 'uint8'), ('G', 'uint8'), ('B', 'uint8')]) - data = np.ones((1, 2, 3), dtype) - hdr.set_data_dtype(dtype) - S4 = BytesIO() - hdr.data_to_fileobj(data, S4) - data_back = hdr.data_from_fileobj(S4) - assert_array_equal(data, data_back) - - def test_datatype(self): - ehdr = self.header_class() - codes = self.header_class._data_type_codes - for code in codes.value_set(): - npt = codes.type[code] - if npt is np.void: - with pytest.raises(HeaderDataError): - ehdr.set_data_dtype(code) - continue - dt = codes.dtype[code] - ehdr.set_data_dtype(npt) - assert ehdr['datatype'] == code - assert ehdr['bitpix'] == dt.itemsize * 8 - ehdr.set_data_dtype(code) - assert ehdr['datatype'] == code - ehdr.set_data_dtype(dt) - assert ehdr['datatype'] == code - - def test_offset(self): - # Test get / set offset - hdr = self.header_class() - offset = hdr.get_data_offset() - hdr.set_data_offset(offset + 16) - assert hdr.get_data_offset() == offset + 16 - - def test_data_shape_zooms_affine(self): - hdr = self.header_class() - for shape in ((1, 2, 3), (0,), (1,), (1, 2), (1, 2, 3, 4)): - L = len(shape) - hdr.set_data_shape(shape) - if L: - assert hdr.get_data_shape() == shape - else: - assert hdr.get_data_shape() == (0,) - # Default zoom - for 3D - is 1(()) - assert hdr.get_zooms() == (1,) * L - # errors if zooms do not match shape - if len(shape): - with pytest.raises(HeaderDataError): - hdr.set_zooms((1,) * (L - 1)) - # Errors for negative zooms - with pytest.raises(HeaderDataError): - hdr.set_zooms((-1,) + (1,) * (L - 1)) - with pytest.raises(HeaderDataError): - hdr.set_zooms((1,) * (L + 1)) - # Errors for negative zooms - with pytest.raises(HeaderDataError): - hdr.set_zooms((-1,) * L) - # reducing the dimensionality of the array and then increasing - # it again reverts the previously set zoom values to 1.0 - hdr = self.header_class() - hdr.set_data_shape((1, 2, 3)) - hdr.set_zooms((4, 5, 6)) - assert_array_equal(hdr.get_zooms(), (4, 5, 6)) - hdr.set_data_shape((1, 2)) - assert_array_equal(hdr.get_zooms(), (4, 5)) - hdr.set_data_shape((1, 2, 3)) - assert_array_equal(hdr.get_zooms(), (4, 5, 1)) - # Setting zooms changes affine - assert_array_equal(np.diag(hdr.get_base_affine()), [-4, 5, 1, 1]) - hdr.set_zooms((1, 1, 1)) - assert_array_equal(np.diag(hdr.get_base_affine()), [-1, 1, 1, 1]) - - def test_default_x_flip(self): - hdr = self.header_class() - hdr.default_x_flip = True - hdr.set_data_shape((1, 2, 3)) - hdr.set_zooms((1, 1, 1)) - assert_array_equal(np.diag(hdr.get_base_affine()), [-1, 1, 1, 1]) - hdr.default_x_flip = False - # Check avoids translations - assert_array_equal(np.diag(hdr.get_base_affine()), [1, 1, 1, 1]) - - def test_from_eg_file(self): - fileobj = open(self.example_file, 'rb') - hdr = self.header_class.from_fileobj(fileobj, check=False) - assert hdr.endianness == '>' - assert hdr['sizeof_hdr'] == self.sizeof_hdr - - def test_orientation(self): - # Test flips - hdr = self.header_class() - assert hdr.default_x_flip - hdr.set_data_shape((3, 5, 7)) - hdr.set_zooms((4, 5, 6)) - aff = np.diag((-4, 5, 6, 1)) - aff[:3, 3] = np.array([1, 2, 3]) * np.array([-4, 5, 6]) * -1 - assert_array_equal(hdr.get_base_affine(), aff) - hdr.default_x_flip = False - assert not hdr.default_x_flip - aff[0] *= -1 - assert_array_equal(hdr.get_base_affine(), aff) - - def test_str(self): - super().test_str() - hdr = self.header_class() - s1 = str(hdr) - # check the datacode recoding - rexp = re.compile(r'^datatype +: float32', re.MULTILINE) - assert rexp.search(s1) is not None - - def test_from_header(self): - # check from header class method. - klass = self.header_class - empty = klass.from_header() - assert klass() == empty - empty = klass.from_header(None) - assert klass() == empty - hdr = klass() - hdr.set_data_dtype(np.float64) - hdr.set_data_shape((1, 2, 3)) - hdr.set_zooms((3.0, 2.0, 1.0)) - for check in (True, False): - copy = klass.from_header(hdr, check=check) - assert hdr == copy - assert hdr is not copy - - class C: - def get_data_dtype(self): - return np.dtype('i2') - - def get_data_shape(self): - return (5, 4, 3) - - def get_zooms(self): - return (10.0, 9.0, 8.0) - - converted = klass.from_header(C()) - assert isinstance(converted, klass) - assert converted.get_data_dtype() == np.dtype('i2') - assert converted.get_data_shape() == (5, 4, 3) - assert converted.get_zooms() == (10.0, 9.0, 8.0) - - def test_base_affine(self): - klass = self.header_class - hdr = klass() - hdr.set_data_shape((3, 5, 7)) - hdr.set_zooms((3, 2, 1)) - assert hdr.default_x_flip - assert_array_almost_equal( - hdr.get_base_affine(), - [ - [-3.0, 0.0, 0.0, 3.0], - [0.0, 2.0, 0.0, -4.0], - [0.0, 0.0, 1.0, -3.0], - [0.0, 0.0, 0.0, 1.0], - ], - ) - hdr.set_data_shape((3, 5)) - assert_array_almost_equal( - hdr.get_base_affine(), - [ - [-3.0, 0.0, 0.0, 3.0], - [0.0, 2.0, 0.0, -4.0], - [0.0, 0.0, 1.0, -0.0], - [0.0, 0.0, 0.0, 1.0], - ], - ) - hdr.set_data_shape((3, 5, 7)) - assert_array_almost_equal( - hdr.get_base_affine(), - [ - [-3.0, 0.0, 0.0, 3.0], - [0.0, 2.0, 0.0, -4.0], - [0.0, 0.0, 1.0, -3.0], - [0.0, 0.0, 0.0, 1.0], - ], - ) - - def test_scaling(self): - # Test integer scaling from float - # Analyze headers cannot do float-integer scaling - hdr = self.header_class() - assert hdr.default_x_flip - shape = (1, 2, 3) - hdr.set_data_shape(shape) - hdr.set_data_dtype(np.float32) - data = np.ones(shape, dtype=np.float64) - S = BytesIO() - # Writing to float datatype doesn't need scaling - hdr.data_to_fileobj(data, S) - rdata = hdr.data_from_fileobj(S) - assert_array_almost_equal(data, rdata) - # Now test writing to integers - hdr.set_data_dtype(np.int32) - # Writing to int needs scaling, and raises an error if we can't scale - if not hdr.has_data_slope: - with pytest.raises(HeaderTypeError): - hdr.data_to_fileobj(data, BytesIO()) - # But if we aren't scaling, convert the floats to integers and write - with np.errstate(invalid='ignore'): - hdr.data_to_fileobj(data, S, rescale=False) - rdata = hdr.data_from_fileobj(S) - assert np.allclose(data, rdata) - # This won't work for floats that aren't close to integers - data_p5 = data + 0.5 - with np.errstate(invalid='ignore'): - hdr.data_to_fileobj(data_p5, S, rescale=False) - rdata = hdr.data_from_fileobj(S) - assert not np.allclose(data_p5, rdata) - - def test_slope_inter(self): - hdr = self.header_class() - assert hdr.get_slope_inter() == (None, None) - for slinter in ( - (None,), - (None, None), - (np.nan, np.nan), - (np.nan, None), - (None, np.nan), - (1.0,), - (1.0, None), - (None, 0), - (1.0, 0), - ): - hdr.set_slope_inter(*slinter) - assert hdr.get_slope_inter() == (None, None) - with pytest.raises(HeaderTypeError): - hdr.set_slope_inter(1.1) - with pytest.raises(HeaderTypeError): - hdr.set_slope_inter(1.0, 0.1) - - def test_from_analyze_map(self): - # Test that any header can pass values from a mapping - klass = self.header_class - # Header needs to implement data_dtype, data_shape, zooms - - class H1: - pass - - with pytest.raises(AttributeError): - klass.from_header(H1()) - - class H2: - def get_data_dtype(self): - return np.dtype('u1') - - with pytest.raises(AttributeError): - klass.from_header(H2()) - - class H3(H2): - def get_data_shape(self): - return (2, 3, 4) - - with pytest.raises(AttributeError): - klass.from_header(H3()) - - class H4(H3): - def get_zooms(self): - return 4.0, 5.0, 6.0 - - exp_hdr = klass() - exp_hdr.set_data_dtype(np.dtype('u1')) - exp_hdr.set_data_shape((2, 3, 4)) - exp_hdr.set_zooms((4, 5, 6)) - assert klass.from_header(H4()) == exp_hdr - # cal_max, cal_min get properly set from ``as_analyze_map`` - - class H5(H4): - def as_analyze_map(self): - return dict(cal_min=-100, cal_max=100) - - exp_hdr['cal_min'] = -100 - exp_hdr['cal_max'] = 100 - assert klass.from_header(H5()) == exp_hdr - # set_* methods override fields from header - - class H6(H5): - def as_analyze_map(self): - return dict(datatype=4, bitpix=32, cal_min=-100, cal_max=100) - - assert klass.from_header(H6()) == exp_hdr - # Any mapping will do, including a Nifti header - - class H7(H5): - def as_analyze_map(self): - n_hdr = Nifti1Header() - n_hdr.set_data_dtype(np.dtype('i2')) - n_hdr['cal_min'] = -100 - n_hdr['cal_max'] = 100 - return n_hdr - - # Values from methods still override values from header (shape, dtype, - # zooms still at defaults from n_hdr header fields above) - assert klass.from_header(H7()) == exp_hdr - - -def test_best_affine(): - hdr = AnalyzeHeader() - hdr.set_data_shape((3, 5, 7)) - hdr.set_zooms((4, 5, 6)) - assert_array_equal(hdr.get_base_affine(), hdr.get_best_affine()) - - -def test_data_code_error(): - # test analyze raising error for unsupported codes - hdr = Nifti1Header() - hdr['datatype'] = 256 - with pytest.raises(HeaderDataError): - AnalyzeHeader.from_header(hdr) - - -class TestAnalyzeImage(tsi.TestSpatialImage, tsi.MmapImageMixin): - image_class = AnalyzeImage - can_save = True - supported_np_types = TestAnalyzeHeader.supported_np_types - - def test_supported_types(self): - img = self.image_class(np.zeros((2, 3, 4)), np.eye(4)) - assert self.supported_np_types == supported_np_types(img) - - def test_default_header(self): - # Check default header is as expected - arr = np.arange(24, dtype=np.int16).reshape((2, 3, 4)) - img = self.image_class(arr, None) - hdr = self.image_class.header_class() - hdr.set_data_shape(arr.shape) - hdr.set_data_dtype(arr.dtype) - hdr.set_data_offset(0) - hdr.set_slope_inter(np.nan, np.nan) - assert img.header == hdr - - def test_data_hdr_cache(self): - # test the API for loaded images, such that the data returned - # from np.asanyarray(img.dataobj) and img.get_fdata() are not - # affected by subsequent changes to the header. - IC = self.image_class - # save an image to a file map - fm = IC.make_file_map() - for value in fm.values(): - value.fileobj = BytesIO() - shape = (2, 3, 4) - data = np.arange(24, dtype=np.int8).reshape(shape) - affine = np.eye(4) - hdr = IC.header_class() - hdr.set_data_dtype(np.int16) - img = IC(data, affine, hdr) - img.to_file_map(fm) - img2 = IC.from_file_map(fm) - assert img2.shape == shape - assert img2.get_data_dtype().type == np.int16 - hdr = img2.header - hdr.set_data_shape((3, 2, 2)) - assert hdr.get_data_shape() == (3, 2, 2) - hdr.set_data_dtype(np.uint8) - assert hdr.get_data_dtype() == np.dtype(np.uint8) - assert_array_equal(img2.get_fdata(), data) - assert_array_equal(np.asanyarray(img2.dataobj), data) - - def test_affine_44(self): - IC = self.image_class - shape = (2, 3, 4) - data = np.arange(24, dtype=np.int16).reshape(shape) - affine = np.diag([2, 3, 4, 1]) - # OK - affine correct shape - img = IC(data, affine) - assert_array_equal(affine, img.affine) - # OK - affine can be array-like - img = IC(data, affine.tolist()) - assert_array_equal(affine, img.affine) - # Not OK - affine wrong shape - with pytest.raises(ValueError): - IC(data, np.diag([2, 3, 4])) - - def test_dtype_init_arg(self): - # data_dtype can be set by argument in absence of header - img_klass = self.image_class - arr = np.arange(24, dtype=np.int16).reshape((2, 3, 4)) - aff = np.eye(4) - for dtype in self.supported_np_types: - img = img_klass(arr, aff, dtype=dtype) - assert img.get_data_dtype() == dtype - # It can also override the header dtype - hdr = img.header - for dtype in self.supported_np_types: - img = img_klass(arr, aff, hdr, dtype=dtype) - assert img.get_data_dtype() == dtype - - def test_offset_to_zero(self): - # Check offset is always set to zero when creating images - img_klass = self.image_class - arr = np.arange(24, dtype=np.int16).reshape((2, 3, 4)) - aff = np.eye(4) - img = img_klass(arr, aff) - assert img.header.get_data_offset() == 0 - # Save to BytesIO object(s), make sure offset still zero - bytes_map = bytesio_filemap(img_klass) - img.to_file_map(bytes_map) - assert img.header.get_data_offset() == 0 - # Set offset in in-memory image - big_off = 1024 - img.header.set_data_offset(big_off) - assert img.header.get_data_offset() == big_off - # Offset is in proxy but not in image after saving to fileobj - img_rt = bytesio_round_trip(img) - assert img_rt.dataobj.offset == big_off - assert img_rt.header.get_data_offset() == 0 - # The original header still has the big_off value - img.header.set_data_offset(big_off) - # Making a new image with this header resets to zero - img_again = img_klass(arr, aff, img.header) - assert img_again.header.get_data_offset() == 0 - - def test_big_offset_exts(self): - # Check writing offset beyond data works for different file extensions - img_klass = self.image_class - arr = np.arange(24, dtype=np.int16).reshape((2, 3, 4)) - aff = np.eye(4) - img_ext = img_klass.files_types[0][1] - compressed_exts = ['', '.gz', '.bz2'] - if HAVE_ZSTD: - compressed_exts += ['.zst'] - with InTemporaryDirectory(): - for offset in (0, 2048): - # Set offset in in-memory image - for compressed_ext in compressed_exts: - img = img_klass(arr, aff) - img.header.set_data_offset(offset) - fname = 'test' + img_ext + compressed_ext - img.to_filename(fname) - img_back = img_klass.from_filename(fname) - assert_array_equal(arr, img_back.dataobj) - del img, img_back - - def test_header_updating(self): - # Only update on changes - img_klass = self.image_class - # With a None affine - don't overwrite zooms - img = img_klass(np.zeros((2, 3, 4)), None) - hdr = img.header - hdr.set_zooms((4, 5, 6)) - # Save / reload using bytes IO objects - for value in img.file_map.values(): - value.fileobj = BytesIO() - img.to_file_map() - hdr_back = img.from_file_map(img.file_map).header - assert_array_equal(hdr_back.get_zooms(), (4, 5, 6)) - # With a real affine, update zooms - img = img_klass(np.zeros((2, 3, 4)), np.diag([2, 3, 4, 1]), hdr) - hdr = img.header - assert_array_equal(hdr.get_zooms(), (2, 3, 4)) - # Modify affine in-place? Update on save. - img.affine[0, 0] = 9 - for value in img.file_map.values(): - value.fileobj = BytesIO() - img.to_file_map() - hdr_back = img.from_file_map(img.file_map).header - assert_array_equal(hdr.get_zooms(), (9, 3, 4)) - # Modify data in-place? Update on save - data = img.get_fdata() - data.shape = (3, 2, 4) - img.to_file_map() - img_back = img.from_file_map(img.file_map) - assert_array_equal(img_back.shape, (3, 2, 4)) - - def test_pickle(self): - # Test that images pickle - # Image that is not proxied can pickle - img_klass = self.image_class - img = img_klass(np.zeros((2, 3, 4)), None) - img_str = pickle.dumps(img) - img2 = pickle.loads(img_str) - assert_array_equal(img.get_fdata(), img2.get_fdata()) - assert img.header == img2.header - # Save / reload using bytes IO objects - for value in img.file_map.values(): - value.fileobj = BytesIO() - img.to_file_map() - img_prox = img.from_file_map(img.file_map) - img_str = pickle.dumps(img_prox) - img2_prox = pickle.loads(img_str) - assert_array_equal(img.get_fdata(), img2_prox.get_fdata()) - - def test_no_finite_values(self): - # save of data with no finite values to int type raises error if we have - # no scaling - data = np.zeros((2, 3, 4)) - data[:, 0] = np.nan - data[:, 1] = np.inf - data[:, 2] = -np.inf - img = self.image_class(data, None) - img.set_data_dtype(np.int16) - assert img.get_data_dtype() == np.dtype(np.int16) - fm = bytesio_filemap(img) - if not img.header.has_data_slope: - with pytest.raises(WriterError): - img.to_file_map(fm) - return - img.to_file_map(fm) - img_back = self.image_class.from_file_map(fm) - assert_array_equal(img_back.dataobj, 0) - - def test_dtype_to_filename_arg(self): - # data_dtype can be set by argument in absence of header - img_klass = self.image_class - arr = np.arange(24, dtype=np.int16).reshape((2, 3, 4)) - aff = np.eye(4) - img = img_klass(arr, aff) - fname = 'test' + img_klass.files_types[0][1] - with InTemporaryDirectory(): - for dtype in self.supported_np_types: - img.to_filename(fname, dtype=dtype) - new_img = img_klass.from_filename(fname) - assert new_img.get_data_dtype() == dtype - # data_type is reset after write - assert img.get_data_dtype() == np.int16 - - -def test_unsupported(): - # analyze does not support uint32 - data = np.arange(24, dtype=np.int32).reshape((2, 3, 4)) - affine = np.eye(4) - data = np.arange(24, dtype=np.uint32).reshape((2, 3, 4)) - with pytest.raises(HeaderDataError): - AnalyzeImage(data, affine) diff --git a/nibabel/tests/test_api_validators.py b/nibabel/tests/test_api_validators.py deleted file mode 100644 index 2388089f2c..0000000000 --- a/nibabel/tests/test_api_validators.py +++ /dev/null @@ -1,116 +0,0 @@ -"""Metaclass and class for validating instance APIs""" - -import os - -import pytest - - -class validator2test(type): - """Wrap ``validator_*`` methods with test method testing instances - - * Find methods with names starting with 'validate_' - * Create test method with same name - * Test method iterates, running validate method over all obj, param pairs - """ - - def __new__(mcs, name, bases, dict): - klass = type.__new__(mcs, name, bases, dict) - - def make_test(name, validator): - def meth(self): - for imaker, params in self.obj_params(): - validator(self, imaker, params) - - meth.__name__ = 'test_' + name[len('validate_') :] - meth.__doc__ = f'autogenerated test from {klass.__name__}.{name}' - if hasattr(validator, 'pytestmark'): - meth.pytestmark = validator.pytestmark - return meth - - for name in dir(klass): - if not name.startswith('validate_'): - continue - # Assume this is a validation method; make a test - test_meth = make_test(name, getattr(klass, name)) - setattr(klass, test_meth.__name__, test_meth) - return klass - - -class ValidateAPI(metaclass=validator2test): - """A class to validate APIs - - Your job is twofold: - - * define an ``obj_params`` iterable, where the iterator returns (``obj``, - ``params``) pairs. ``obj`` is something that you want to validate against - an API. ``params`` is a mapping giving parameters for this object to test - against. - * define ``validate_xxx`` methods, that accept ``obj`` and - ``params`` as arguments, and check ``obj`` against ``params`` - - The metaclass finds each ``validate_xxx`` method and makes a new - ``test_xxx`` method that calls ``validate_xxx`` for each (``obj``, - ``params``) pair returned from ``obj_params`` - - See :class:`TextValidateSomething` for an example - """ - - -class TestValidateSomething(ValidateAPI): - """Example implementing an API validator test class""" - - def obj_params(self): - """Iterator returning (obj, params) pairs - - ``obj`` is some instance for which we want to check the API. - - ``params`` is a mapping with parameters that you are going to check - against ``obj``. See the :meth:`validate_something` method for an - example. - """ - - class C: - def __init__(self, var): - self.var = var - - def get_var(self): - return self.var - - yield C(5), {'var': 5} - yield C('easypeasy'), {'var': 'easypeasy'} - - def validate_something(self, obj, params): - """Do some checks of the `obj` API against `params` - - The metaclass sets up a ``test_something`` function that runs these - checks on each ( - """ - assert obj.var == params['var'] - assert obj.get_var() == params['var'] - - -@pytest.mark.xfail( - os.getenv('PYTEST_XDIST_WORKER') is not None, - reason='Execution in the same scope cannot be guaranteed', -) -class TestRunAllTests(ValidateAPI): - """Class to test that each validator test gets run - - We check this in the module teardown function - """ - - run_tests = {} - - def obj_params(self): - yield 1, 2 - - def validate_first(self, obj, param): - self.run_tests.add('first') - - def validate_second(self, obj, param): - self.run_tests.add('second') - - @classmethod - def teardown_class(cls): - # Check that both validate_xxx tests got run - assert cls.run_tests == {'first', 'second'} diff --git a/nibabel/tests/test_arrayproxy.py b/nibabel/tests/test_arrayproxy.py deleted file mode 100644 index 65b9131905..0000000000 --- a/nibabel/tests/test_arrayproxy.py +++ /dev/null @@ -1,606 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Tests for arrayproxy module""" - -import contextlib -import gzip -import pickle -from io import BytesIO -from unittest import mock - -import numpy as np -import pytest -from numpy.testing import assert_array_almost_equal, assert_array_equal -from packaging.version import Version - -from .. import __version__ -from ..arrayproxy import ArrayProxy, get_obj_dtype, is_proxy, reshape_dataobj -from ..deprecator import ExpiredDeprecationError -from ..nifti1 import Nifti1Header, Nifti1Image -from ..openers import ImageOpener -from ..testing import memmap_after_ufunc -from ..tmpdirs import InTemporaryDirectory -from .test_fileslice import slicer_samples -from .test_openers import patch_indexed_gzip - - -class FunkyHeader: - def __init__(self, shape): - self.shape = shape - - def get_data_shape(self): - return self.shape[:] - - def get_data_dtype(self): - return np.int32 - - def get_data_offset(self): - return 16 - - def get_slope_inter(self): - return 1.0, 0.0 - - def copy(self): - # Not needed when we remove header property - return FunkyHeader(self.shape) - - -class CArrayProxy(ArrayProxy): - # C array memory layout - _default_order = 'C' - - -class DeprecatedCArrayProxy(ArrayProxy): - # Used in test_deprecated_order_classvar. Remove when that test is removed (8.0) - order = 'C' - - -def test_init(): - bio = BytesIO() - shape = [2, 3, 4] - dtype = np.int32 - arr = np.arange(24, dtype=dtype).reshape(shape) - bio.seek(16) - bio.write(arr.tobytes(order='F')) - hdr = FunkyHeader(shape) - ap = ArrayProxy(bio, hdr) - assert ap.file_like is bio - assert ap.shape == shape - # shape should be read only - with pytest.raises(AttributeError): - ap.shape = shape - # Get the data - assert_array_equal(np.asarray(ap), arr) - # Check we can modify the original header without changing the ap version - hdr.shape[0] = 6 - assert ap.shape != shape - # Data stays the same, also - assert_array_equal(np.asarray(ap), arr) - # You wouldn't do this, but order=None explicitly requests the default order - ap2 = ArrayProxy(bio, FunkyHeader(arr.shape), order=None) - assert_array_equal(np.asarray(ap2), arr) - # C order also possible - bio = BytesIO() - bio.seek(16) - bio.write(arr.tobytes(order='C')) - ap = CArrayProxy(bio, FunkyHeader((2, 3, 4))) - assert_array_equal(np.asarray(ap), arr) - # Illegal init - with pytest.raises(TypeError): - ArrayProxy(bio, object()) - with pytest.raises(ValueError): - ArrayProxy(bio, hdr, order='badval') - - -def test_tuplespec(): - bio = BytesIO() - shape = [2, 3, 4] - dtype = np.int32 - arr = np.arange(24, dtype=dtype).reshape(shape) - bio.seek(16) - bio.write(arr.tobytes(order='F')) - # Create equivalent header and tuple specs - hdr = FunkyHeader(shape) - tuple_spec = (hdr.get_data_shape(), hdr.get_data_dtype(), hdr.get_data_offset(), 1.0, 0.0) - ap_header = ArrayProxy(bio, hdr) - ap_tuple = ArrayProxy(bio, tuple_spec) - # Header and tuple specs produce identical behavior - for prop in ('shape', 'dtype', 'offset', 'slope', 'inter', 'is_proxy'): - assert getattr(ap_header, prop) == getattr(ap_tuple, prop) - for method, args in (('get_unscaled', ()), ('__array__', ()), ('__getitem__', ((0, 2, 1),))): - assert_array_equal(getattr(ap_header, method)(*args), getattr(ap_tuple, method)(*args)) - # Partial tuples of length 2-4 are also valid - for n in range(2, 5): - ArrayProxy(bio, tuple_spec[:n]) - # Bad tuple lengths - with pytest.raises(TypeError): - ArrayProxy(bio, ()) - with pytest.raises(TypeError): - ArrayProxy(bio, tuple_spec[:1]) - with pytest.raises(TypeError): - ArrayProxy(bio, tuple_spec + ('error',)) - - -def write_raw_data(arr, hdr, fileobj): - hdr.set_data_shape(arr.shape) - hdr.set_data_dtype(arr.dtype) - fileobj.write(b'\x00' * hdr.get_data_offset()) - fileobj.write(arr.tobytes(order='F')) - - -def test_nifti1_init(): - bio = BytesIO() - shape = (2, 3, 4) - hdr = Nifti1Header() - arr = np.arange(24, dtype=np.int16).reshape(shape) - write_raw_data(arr, hdr, bio) - hdr.set_slope_inter(2, 10) - ap = ArrayProxy(bio, hdr) - assert ap.file_like == bio - assert ap.shape == shape - # Get the data - assert_array_equal(np.asarray(ap), arr * 2.0 + 10) - with InTemporaryDirectory(): - f = open('test.nii', 'wb') - write_raw_data(arr, hdr, f) - f.close() - ap = ArrayProxy('test.nii', hdr) - assert ap.file_like == 'test.nii' - assert ap.shape == shape - assert_array_equal(np.asarray(ap), arr * 2.0 + 10) - - -@pytest.mark.parametrize('n_dim', (1, 2, 3)) -@pytest.mark.parametrize('offset', (0, 20)) -def test_proxy_slicing(n_dim, offset): - shape = (15, 16, 17)[:n_dim] - arr = np.arange(np.prod(shape)).reshape(shape) - hdr = Nifti1Header() - hdr.set_data_offset(offset) - hdr.set_data_dtype(arr.dtype) - hdr.set_data_shape(shape) - for order, klass in ('F', ArrayProxy), ('C', CArrayProxy): - fobj = BytesIO() - fobj.write(b'\0' * offset) - fobj.write(arr.tobytes(order=order)) - prox = klass(fobj, hdr) - assert prox.order == order - for sliceobj in slicer_samples(shape): - assert_array_equal(arr[sliceobj], prox[sliceobj]) - - -def test_proxy_slicing_with_scaling(): - shape = (15, 16, 17) - offset = 20 - arr = np.arange(np.prod(shape)).reshape(shape) - hdr = Nifti1Header() - hdr.set_data_offset(offset) - hdr.set_data_dtype(arr.dtype) - hdr.set_data_shape(shape) - hdr.set_slope_inter(2.0, 1.0) - fobj = BytesIO() - fobj.write(bytes(offset)) - fobj.write(arr.tobytes(order='F')) - prox = ArrayProxy(fobj, hdr) - sliceobj = (None, slice(None), 1, -1) - assert_array_equal(arr[sliceobj] * 2.0 + 1.0, prox[sliceobj]) - - -@pytest.mark.parametrize('order', ('C', 'F')) -def test_order_override(order): - shape = (15, 16, 17) - arr = np.arange(np.prod(shape)).reshape(shape) - fobj = BytesIO() - fobj.write(arr.tobytes(order=order)) - for klass in (ArrayProxy, CArrayProxy): - prox = klass(fobj, (shape, arr.dtype), order=order) - assert prox.order == order - sliceobj = (None, slice(None), 1, -1) - assert_array_equal(arr[sliceobj], prox[sliceobj]) - - -def test_deprecated_order_classvar(): - shape = (15, 16, 17) - arr = np.arange(np.prod(shape)).reshape(shape) - fobj = BytesIO() - fobj.write(arr.tobytes(order='C')) - sliceobj = (None, slice(None), 1, -1) - - # We don't really care about the original order, just that the behavior - # of the deprecated mode matches the new behavior - fprox = ArrayProxy(fobj, (shape, arr.dtype), order='F') - cprox = ArrayProxy(fobj, (shape, arr.dtype), order='C') - - # Start raising errors when we crank the dev version - if Version(__version__) >= Version('7.0.0.dev0'): - cm = pytest.raises(ExpiredDeprecationError) - else: - cm = pytest.deprecated_call() - - with cm: - prox = DeprecatedCArrayProxy(fobj, (shape, arr.dtype)) - assert prox.order == 'C' - assert_array_equal(prox[sliceobj], cprox[sliceobj]) - with cm: - prox = DeprecatedCArrayProxy(fobj, (shape, arr.dtype), order='C') - assert prox.order == 'C' - assert_array_equal(prox[sliceobj], cprox[sliceobj]) - with cm: - prox = DeprecatedCArrayProxy(fobj, (shape, arr.dtype), order='F') - assert prox.order == 'F' - assert_array_equal(prox[sliceobj], fprox[sliceobj]) - - -def test_is_proxy(): - # Test is_proxy function - hdr = FunkyHeader((2, 3, 4)) - bio = BytesIO() - prox = ArrayProxy(bio, hdr) - assert is_proxy(prox) - assert not is_proxy(bio) - assert not is_proxy(hdr) - assert not is_proxy(np.zeros((2, 3, 4))) - - class NP: - is_proxy = False - - assert not is_proxy(NP()) - - -def test_reshape_dataobj(): - # Test function that reshapes using method if possible - shape = (1, 2, 3, 4) - hdr = FunkyHeader(shape) - bio = BytesIO() - prox = ArrayProxy(bio, hdr) - arr = np.arange(np.prod(shape), dtype=prox.dtype).reshape(shape) - bio.write(b'\x00' * prox.offset + arr.tobytes(order='F')) - assert_array_equal(prox, arr) - assert_array_equal(reshape_dataobj(prox, (2, 3, 4)), np.reshape(arr, (2, 3, 4))) - assert prox.shape == shape - assert arr.shape == shape - assert_array_equal(reshape_dataobj(arr, (2, 3, 4)), np.reshape(arr, (2, 3, 4))) - assert arr.shape == shape - - class ArrGiver: - def __array__(self): - return arr - - assert_array_equal(reshape_dataobj(ArrGiver(), (2, 3, 4)), np.reshape(arr, (2, 3, 4))) - assert arr.shape == shape - - -def test_reshaped_is_proxy(): - shape = (1, 2, 3, 4) - hdr = FunkyHeader(shape) - bio = BytesIO() - prox = ArrayProxy(bio, hdr) - assert isinstance(prox.reshape((2, 3, 4)), ArrayProxy) - minus1 = prox.reshape((2, -1, 4)) - assert isinstance(minus1, ArrayProxy) - assert minus1.shape == (2, 3, 4) - with pytest.raises(ValueError): - prox.reshape((-1, -1, 4)) - with pytest.raises(ValueError): - prox.reshape((2, 3, 5)) - with pytest.raises(ValueError): - prox.reshape((2, -1, 5)) - - -def test_get_obj_dtype(): - # Check get_obj_dtype(obj) returns same result as array(obj).dtype - bio = BytesIO() - shape = (2, 3, 4) - hdr = Nifti1Header() - arr = np.arange(24, dtype=np.int16).reshape(shape) - write_raw_data(arr, hdr, bio) - hdr.set_slope_inter(2, 10) - prox = ArrayProxy(bio, hdr) - assert get_obj_dtype(prox) == np.dtype('float64') - assert get_obj_dtype(np.array(prox)) == np.dtype('float64') - hdr.set_slope_inter(1, 0) - prox = ArrayProxy(bio, hdr) - assert get_obj_dtype(prox) == np.dtype('int16') - assert get_obj_dtype(np.array(prox)) == np.dtype('int16') - - class ArrGiver: - def __array__(self): - return arr - - assert get_obj_dtype(ArrGiver()) == np.dtype('int16') - - -def test_get_unscaled(): - # Test fetch of raw array - class FunkyHeader2(FunkyHeader): - def get_slope_inter(self): - return 2.1, 3.14 - - shape = (2, 3, 4) - hdr = FunkyHeader2(shape) - bio = BytesIO() - # Check standard read works - arr = np.arange(24, dtype=np.int32).reshape(shape, order='F') - bio.write(b'\x00' * hdr.get_data_offset()) - bio.write(arr.tobytes(order='F')) - prox = ArrayProxy(bio, hdr) - assert_array_almost_equal(np.array(prox), arr * 2.1 + 3.14) - # Check unscaled read works - assert_array_almost_equal(prox.get_unscaled(), arr) - - -def test_mmap(): - # Unscaled should return mmap from suitable file, this can be tuned - hdr = FunkyHeader((2, 3, 4)) - check_mmap(hdr, hdr.get_data_offset(), ArrayProxy) - - -def check_mmap(hdr, offset, proxy_class, has_scaling=False, unscaled_is_view=True): - """Assert that array proxies return memory maps as expected - - Parameters - ---------- - hdr : object - Image header instance - offset : int - Offset in bytes of image data in file (that we will write) - proxy_class : class - Class of image array proxy to test - has_scaling : {False, True} - True if the `hdr` says to apply scaling to the output data, False - otherwise. - unscaled_is_view : {True, False} - True if getting the unscaled data returns a view of the array. If - False, then type of returned array will depend on whether numpy has the - old viral (< 1.12) memmap behavior (returns memmap) or the new behavior - (returns ndarray). See: https://github.com/numpy/numpy/pull/7406 - """ - shape = hdr.get_data_shape() - arr = np.arange(np.prod(shape), dtype=hdr.get_data_dtype()).reshape(shape) - fname = 'test.bin' - # Whether unscaled array memory backed by memory map (regardless of what - # numpy says). - unscaled_really_mmap = unscaled_is_view - # Whether scaled array memory backed by memory map (regardless of what - # numpy says). - scaled_really_mmap = unscaled_really_mmap and not has_scaling - # Whether ufunc on memmap return memmap - viral_memmap = memmap_after_ufunc() - with InTemporaryDirectory(): - with open(fname, 'wb') as fobj: - fobj.write(b' ' * offset) - fobj.write(arr.tobytes(order='F')) - for mmap, expected_mode in ( - # mmap value, expected memmap mode - # mmap=None -> no mmap value - # expected mode=None -> no memmap returned - (None, 'c'), - (True, 'c'), - ('c', 'c'), - ('r', 'r'), - (False, None), - ): - kwargs = {} - if mmap is not None: - kwargs['mmap'] = mmap - prox = proxy_class(fname, hdr, **kwargs) - unscaled = prox.get_unscaled() - back_data = np.asanyarray(prox) - unscaled_is_mmap = isinstance(unscaled, np.memmap) - back_is_mmap = isinstance(back_data, np.memmap) - if expected_mode is None: - assert not unscaled_is_mmap - assert not back_is_mmap - else: - assert unscaled_is_mmap == (viral_memmap or unscaled_really_mmap) - assert back_is_mmap == (viral_memmap or scaled_really_mmap) - if scaled_really_mmap: - assert back_data.mode == expected_mode - del prox, back_data - # Check that mmap is keyword-only - with pytest.raises(TypeError): - proxy_class(fname, hdr, True) - # Check invalid values raise error - with pytest.raises(ValueError): - proxy_class(fname, hdr, mmap='rw') - with pytest.raises(ValueError): - proxy_class(fname, hdr, mmap='r+') - - -# An image opener class which counts how many instances of itself have been -# created -class CountingImageOpener(ImageOpener): - num_openers = 0 - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - CountingImageOpener.num_openers += 1 - - -def _count_ImageOpeners(proxy, data, voxels): - CountingImageOpener.num_openers = 0 - # expected data is defined in the test_keep_file_open_* tests - for i in range(voxels.shape[0]): - x, y, z = (int(c) for c in voxels[i, :]) - assert proxy[x, y, z] == x * 100 + y * 10 + z - return CountingImageOpener.num_openers - - -@contextlib.contextmanager -def patch_keep_file_open_default(value): - # Patch arrayproxy.KEEP_FILE_OPEN_DEFAULT with the given value - with mock.patch('nibabel.arrayproxy.KEEP_FILE_OPEN_DEFAULT', value): - yield - - -def test_keep_file_open_true_false_invalid(): - # Test the behaviour of the keep_file_open __init__ flag, when it is set to - # True or False. Expected behaviour is as follows: - # keep_open | igzip present | persist ImageOpener | igzip.drop_handles - # | and is gzip file | | - # ----------|------------------|---------------------|------------------- - # False | False | False | n/a - # False | True | True | True - # True | False | True | n/a - # True | True | True | False - # - # Each test tuple contains: - # - file type - gzipped ('gz') or not ('bin'), or an open file handle - # ('open') - # - keep_file_open value passed to ArrayProxy - # - whether or not indexed_gzip is present - # - expected value for internal ArrayProxy._persist_opener flag - # - expected value for internal ArrayProxy._keep_file_open flag - tests = [ - # open file handle - kfo and have_igzip are both irrelevant - ('open', False, False, False, False), - ('open', False, True, False, False), - ('open', True, False, False, False), - ('open', True, True, False, False), - # non-gzip file - have_igzip is irrelevant, decision should be made - # solely from kfo flag - ('bin', False, False, False, False), - ('bin', False, True, False, False), - ('bin', True, False, True, True), - ('bin', True, True, True, True), - # gzip file. If igzip is present, we persist the ImageOpener. - ('gz', False, False, False, False), - ('gz', False, True, True, False), - ('gz', True, False, True, True), - ('gz', True, True, True, True), - ] - - dtype = np.float32 - data = np.arange(1000, dtype=dtype).reshape((10, 10, 10)) - voxels = np.random.randint(0, 10, (10, 3)) - - for test in tests: - filetype, kfo, have_igzip, exp_persist, exp_kfo = test - with ( - InTemporaryDirectory(), - mock.patch('nibabel.openers.ImageOpener', CountingImageOpener), - patch_indexed_gzip(have_igzip), - ): - fname = f'testdata.{filetype}' - # create the test data file - if filetype == 'gz': - with gzip.open(fname, 'wb') as fobj: - fobj.write(data.tobytes(order='F')) - else: - with open(fname, 'wb') as fobj: - fobj.write(data.tobytes(order='F')) - # pass in a file name or open file handle. If the latter, we open - # two file handles, because we're going to create two proxies - # below. - if filetype == 'open': - fobj1 = open(fname, 'rb') - fobj2 = open(fname, 'rb') - else: - fobj1 = fname - fobj2 = fname - try: - proxy = ArrayProxy(fobj1, ((10, 10, 10), dtype), keep_file_open=kfo) - # We also test that we get the same behaviour when the - # KEEP_FILE_OPEN_DEFAULT flag is changed - with patch_keep_file_open_default(kfo): - proxy_def = ArrayProxy(fobj2, ((10, 10, 10), dtype)) - # check internal flags - assert proxy._persist_opener == exp_persist - assert proxy._keep_file_open == exp_kfo - assert proxy_def._persist_opener == exp_persist - assert proxy_def._keep_file_open == exp_kfo - # check persist_opener behaviour - whether one imageopener is - # created for the lifetime of the ArrayProxy, or one is - # created on each access - if exp_persist: - assert _count_ImageOpeners(proxy, data, voxels) == 1 - assert _count_ImageOpeners(proxy_def, data, voxels) == 1 - else: - assert _count_ImageOpeners(proxy, data, voxels) == 10 - assert _count_ImageOpeners(proxy_def, data, voxels) == 10 - # if indexed_gzip is active, check that the file object was - # created correctly - the _opener.fobj will be a - # MockIndexedGzipFile, defined in test_openers.py - if filetype == 'gz' and have_igzip: - assert proxy._opener.fobj._drop_handles == (not exp_kfo) - # if we were using an open file handle, check that the proxy - # didn't close it - if filetype == 'open': - assert not fobj1.closed - assert not fobj2.closed - finally: - del proxy - del proxy_def - if filetype == 'open': - fobj1.close() - fobj2.close() - # Test invalid values of keep_file_open - with InTemporaryDirectory(): - fname = 'testdata' - with open(fname, 'wb') as fobj: - fobj.write(data.tobytes(order='F')) - - for invalid_kfo in (55, 'auto', 'cauto'): - with pytest.raises(ValueError): - ArrayProxy(fname, ((10, 10, 10), dtype), keep_file_open=invalid_kfo) - with patch_keep_file_open_default(invalid_kfo): - with pytest.raises(ValueError): - ArrayProxy(fname, ((10, 10, 10), dtype)) - - -def islock(l): - # isinstance doesn't work on threading.Lock? - return hasattr(l, 'acquire') and hasattr(l, 'release') - - -def test_pickle_lock(): - # Test that ArrayProxy can be pickled, and that thread lock is created - - proxy = ArrayProxy('dummyfile', ((10, 10, 10), np.float32)) - assert islock(proxy._lock) - pickled = pickle.dumps(proxy) - unpickled = pickle.loads(pickled) - assert islock(unpickled._lock) - assert proxy._lock is not unpickled._lock - - -def test_copy(): - # Test copying array proxies - - # If the file-like is a file name, get a new lock - proxy = ArrayProxy('dummyfile', ((10, 10, 10), np.float32)) - assert islock(proxy._lock) - copied = proxy.copy() - assert islock(copied._lock) - assert proxy._lock is not copied._lock - - # If an open filehandle, the lock should be shared to - # avoid changing filehandle state in critical sections - proxy = ArrayProxy(BytesIO(), ((10, 10, 10), np.float32)) - assert islock(proxy._lock) - copied = proxy.copy() - assert islock(copied._lock) - assert proxy._lock is copied._lock - - -def test_copy_with_indexed_gzip_handle(tmp_path): - indexed_gzip = pytest.importorskip('indexed_gzip') - - spec = ((50, 50, 50, 50), np.float32, 352, 1, 0) - data = np.arange(np.prod(spec[0]), dtype=spec[1]).reshape(spec[0]) - fname = str(tmp_path / 'test.nii.gz') - Nifti1Image(data, np.eye(4)).to_filename(fname) - - with indexed_gzip.IndexedGzipFile(fname) as fobj: - proxy = ArrayProxy(fobj, spec) - copied = proxy.copy() - - assert proxy.file_like is copied.file_like - assert np.array_equal(proxy[0, 0, 0], copied[0, 0, 0]) - assert np.array_equal(proxy[-1, -1, -1], copied[-1, -1, -1]) diff --git a/nibabel/tests/test_arraywriters.py b/nibabel/tests/test_arraywriters.py deleted file mode 100644 index 4a853ecf5e..0000000000 --- a/nibabel/tests/test_arraywriters.py +++ /dev/null @@ -1,847 +0,0 @@ -"""Testing array writer objects - -See docstring of :mod:`nibabel.arraywriters` for API. -""" - -import itertools -from io import BytesIO -from platform import machine, python_compiler - -import numpy as np -import pytest -from numpy.testing import assert_array_almost_equal, assert_array_equal - -from ..arraywriters import ( - ArrayWriter, - ScalingError, - SlopeArrayWriter, - SlopeInterArrayWriter, - WriterError, - get_slope_inter, - make_array_writer, -) -from ..casting import int_abs, sctypes, shared_range, type_info -from ..testing import assert_allclose_safely, suppress_warnings -from ..volumeutils import _dt_min_max, apply_read_scaling, array_from_file - -FLOAT_TYPES = sctypes['float'] -COMPLEX_TYPES = sctypes['complex'] -INT_TYPES = sctypes['int'] -UINT_TYPES = sctypes['uint'] -CFLOAT_TYPES = FLOAT_TYPES + COMPLEX_TYPES -IUINT_TYPES = INT_TYPES + UINT_TYPES -NUMERIC_TYPES = CFLOAT_TYPES + IUINT_TYPES - - -def round_trip(writer, order='F', apply_scale=True): - sio = BytesIO() - arr = writer.array - with np.errstate(invalid='ignore'): - writer.to_fileobj(sio, order) - data_back = array_from_file(arr.shape, writer.out_dtype, sio, order=order) - slope, inter = get_slope_inter(writer) - if apply_scale: - data_back = apply_read_scaling(data_back, slope, inter) - return data_back - - -def test_arraywriters(): - # Test initialize - # Simple cases - if machine() == 'sparc64' and python_compiler().startswith('GCC'): - # bus errors on at least np 1.4.1 through 1.6.1 for complex - test_types = FLOAT_TYPES + IUINT_TYPES - else: - test_types = NUMERIC_TYPES - for klass in (SlopeInterArrayWriter, SlopeArrayWriter, ArrayWriter): - for type in test_types: - arr = np.arange(10, dtype=type) - aw = klass(arr) - assert aw.array is arr - assert aw.out_dtype == arr.dtype - assert_array_equal(arr, round_trip(aw)) - # Byteswapped should be OK - bs_arr = arr.byteswap() - bs_arr = bs_arr.view(bs_arr.dtype.newbyteorder('S')) - bs_aw = klass(bs_arr) - bs_aw_rt = round_trip(bs_aw) - # assert against original array because POWER7 was running into - # trouble using the byteswapped array (bs_arr) - assert_array_equal(arr, bs_aw_rt) - bs_aw2 = klass(bs_arr, arr.dtype) - bs_aw2_rt = round_trip(bs_aw2) - assert_array_equal(arr, bs_aw2_rt) - # 2D array - arr2 = np.reshape(arr, (2, 5)) - a2w = klass(arr2) - # Default out - in order is Fortran - arr_back = round_trip(a2w) - assert_array_equal(arr2, arr_back) - arr_back = round_trip(a2w, 'F') - assert_array_equal(arr2, arr_back) - # C order works as well - arr_back = round_trip(a2w, 'C') - assert_array_equal(arr2, arr_back) - assert arr_back.flags.c_contiguous - - -def test_arraywriter_check_scaling(): - # Check keyword-only argument to ArrayWriter - # Within range - OK - arr = np.array([0, 1, 128, 255], np.uint8) - aw = ArrayWriter(arr) - # Out of range, scaling needed, default is error - with pytest.raises(WriterError): - ArrayWriter(arr, np.int8) - # Make default explicit - with pytest.raises(WriterError): - ArrayWriter(arr, np.int8, check_scaling=True) - # Turn off scaling check - aw = ArrayWriter(arr, np.int8, check_scaling=False) - assert_array_equal(round_trip(aw), np.clip(arr, 0, 127)) - # Has to be keyword - with pytest.raises(TypeError): - ArrayWriter(arr, np.int8, False) - - -def test_no_scaling(): - # Test arraywriter when writing different types without scaling - for in_dtype, out_dtype, awt in itertools.product( - NUMERIC_TYPES, NUMERIC_TYPES, (ArrayWriter, SlopeArrayWriter, SlopeInterArrayWriter) - ): - mn_in, mx_in = _dt_min_max(in_dtype) - arr = np.array([mn_in, 0, 1, mx_in], dtype=in_dtype) - kwargs = dict(check_scaling=False) if awt == ArrayWriter else dict(calc_scale=False) - aw = awt(arr, out_dtype, **kwargs) - with suppress_warnings(): - back_arr = round_trip(aw) - exp_back = arr.copy() - # If converting to floating point type, casting is direct. - # Otherwise we will need to do float-(u)int casting at some point. - if out_dtype in IUINT_TYPES: - if in_dtype in CFLOAT_TYPES: - # Working precision is (at least) float - with suppress_warnings(): - exp_back = exp_back.astype(float) - # Float to iu conversion will always round, clip - with np.errstate(invalid='ignore'): - exp_back = np.round(exp_back) - if hasattr(aw, 'slope') and in_dtype in FLOAT_TYPES: - # Finite scaling sets infs to min / max - exp_back = np.clip(exp_back, 0, 1) - else: - # Clip to shared range of working precision - exp_back = np.clip(exp_back, *shared_range(float, out_dtype)) - else: # iu input and output type - # No scaling, never gets converted to float. - # Does get clipped to range of output type - mn_out, mx_out = _dt_min_max(out_dtype) - if (mn_in, mx_in) != (mn_out, mx_out): - # Use smaller of input, output range to avoid np.clip - # upcasting the array because of large clip limits. - exp_back = np.clip(exp_back, max(mn_in, mn_out), min(mx_in, mx_out)) - elif in_dtype in COMPLEX_TYPES: - # always cast to real from complex - with suppress_warnings(): - exp_back = exp_back.astype(float) - exp_back = exp_back.astype(out_dtype) - # Sometimes working precision is float32 - allow for small differences - assert_allclose_safely(back_arr, exp_back) - - -def test_scaling_needed(): - # Structured types return True if dtypes same, raise error otherwise - dt_def = [('f', 'i4')] - arr = np.ones(10, dt_def) - for t in NUMERIC_TYPES: - with pytest.raises(WriterError): - ArrayWriter(arr, t) - narr = np.ones(10, t) - with pytest.raises(WriterError): - ArrayWriter(narr, dt_def) - assert not ArrayWriter(arr).scaling_needed() - assert not ArrayWriter(arr, dt_def).scaling_needed() - # Any numeric type that can cast, needs no scaling - for in_t in NUMERIC_TYPES: - for out_t in NUMERIC_TYPES: - if np.can_cast(in_t, out_t): - aw = ArrayWriter(np.ones(10, in_t), out_t) - assert not aw.scaling_needed() - for in_t in NUMERIC_TYPES: - # Numeric types to complex never need scaling - arr = np.ones(10, in_t) - for out_t in COMPLEX_TYPES: - assert not ArrayWriter(arr, out_t).scaling_needed() - # Attempts to scale from complex to anything else fails - for in_t in COMPLEX_TYPES: - for out_t in FLOAT_TYPES + IUINT_TYPES: - arr = np.ones(10, in_t) - with pytest.raises(WriterError): - ArrayWriter(arr, out_t) - # Scaling from anything but complex to floats is OK - for in_t in FLOAT_TYPES + IUINT_TYPES: - arr = np.ones(10, in_t) - for out_t in FLOAT_TYPES: - assert not ArrayWriter(arr, out_t).scaling_needed() - # For any other output type, arrays with no data don't need scaling - for in_t in FLOAT_TYPES + IUINT_TYPES: - arr_0 = np.zeros(10, in_t) - arr_e = [] - for out_t in IUINT_TYPES: - assert not ArrayWriter(arr_0, out_t).scaling_needed() - assert not ArrayWriter(arr_e, out_t).scaling_needed() - # Going to (u)ints, non-finite arrays don't need scaling for writers that - # can do scaling because these use finite_range to threshold the input data, - # but ArrayWriter does not do this. so scaling_needed is True - for in_t in FLOAT_TYPES: - arr_nan = np.zeros(10, in_t) + np.nan - arr_inf = np.zeros(10, in_t) + np.inf - arr_minf = np.zeros(10, in_t) - np.inf - arr_mix = np.array([np.nan, np.inf, -np.inf], dtype=in_t) - for out_t in IUINT_TYPES: - for arr in (arr_nan, arr_inf, arr_minf, arr_mix): - assert ArrayWriter(arr, out_t, check_scaling=False).scaling_needed() - assert not SlopeArrayWriter(arr, out_t).scaling_needed() - assert not SlopeInterArrayWriter(arr, out_t).scaling_needed() - # Floats as input always need scaling - for in_t in FLOAT_TYPES: - arr = np.ones(10, in_t) - for out_t in IUINT_TYPES: - # We need an arraywriter that will tolerate construction when - # scaling is needed - assert SlopeArrayWriter(arr, out_t).scaling_needed() - # in-range (u)ints don't need scaling - for in_t in IUINT_TYPES: - in_info = np.iinfo(in_t) - in_min, in_max = in_info.min, in_info.max - for out_t in IUINT_TYPES: - out_info = np.iinfo(out_t) - out_min, out_max = out_info.min, out_info.max - if in_min >= out_min and in_max <= out_max: - arr = np.array([in_min, in_max], in_t) - assert np.can_cast(arr.dtype, out_t) - # We've already tested this with can_cast above, but... - assert not ArrayWriter(arr, out_t).scaling_needed() - continue - # The output data type does not include the input data range - max_min = max(in_min, out_min) # 0 for input or output uint - min_max = min(in_max, out_max) - arr = np.array([max_min, min_max], in_t) - assert not ArrayWriter(arr, out_t).scaling_needed() - assert SlopeInterArrayWriter(arr + 1, out_t).scaling_needed() - if in_t in INT_TYPES: - assert SlopeInterArrayWriter(arr - 1, out_t).scaling_needed() - - -def test_special_rt(): - # Test that zeros; none finite - round trip to zeros for scalable types - # For ArrayWriter, these error for default creation, when forced to create - # the writer, they round trip to out_dtype max - arr = np.array([np.inf, np.nan, -np.inf]) - for in_dtt in FLOAT_TYPES: - for out_dtt in IUINT_TYPES: - in_arr = arr.astype(in_dtt) - with pytest.raises(WriterError): - ArrayWriter(in_arr, out_dtt) - aw = ArrayWriter(in_arr, out_dtt, check_scaling=False) - mn, mx = shared_range(float, out_dtt) - assert np.allclose(round_trip(aw).astype(float), [mx, 0, mn]) - for klass in (SlopeArrayWriter, SlopeInterArrayWriter): - aw = klass(in_arr, out_dtt) - assert get_slope_inter(aw) == (1, 0) - assert_array_equal(round_trip(aw), 0) - for in_dtt, out_dtt, awt in itertools.product( - FLOAT_TYPES, IUINT_TYPES, (ArrayWriter, SlopeArrayWriter, SlopeInterArrayWriter) - ): - arr = np.zeros((3,), dtype=in_dtt) - aw = awt(arr, out_dtt) - assert get_slope_inter(aw) == (1, 0) - assert_array_equal(round_trip(aw), 0) - - -def test_high_int2uint(): - # Need to take care of high values when testing whether values are already - # in range. There was a bug here were the comparison was in floating point, - # and therefore not exact, and 2**63 appeared to be in range for np.int64 - arr = np.array([2**63], dtype=np.uint64) - out_type = np.int64 - aw = SlopeInterArrayWriter(arr, out_type) - assert aw.inter == 2**63 - - -def test_slope_inter_castable(): - # Test scaling for arraywriter instances - # Test special case of all zeros - for in_dtt in FLOAT_TYPES + IUINT_TYPES: - for out_dtt in NUMERIC_TYPES: - for klass in (ArrayWriter, SlopeArrayWriter, SlopeInterArrayWriter): - arr = np.zeros((5,), dtype=in_dtt) - klass(arr, out_dtt) # no error - # Test special case of none finite - # This raises error for ArrayWriter, but not for the others - arr = np.array([np.inf, np.nan, -np.inf]) - for in_dtt in FLOAT_TYPES: - for out_dtt in IUINT_TYPES: - in_arr = arr.astype(in_dtt) - with pytest.raises(WriterError): - ArrayWriter(in_arr, out_dtt) - SlopeArrayWriter(arr.astype(in_dtt), out_dtt) # no error - SlopeInterArrayWriter(arr.astype(in_dtt), out_dtt) # no error - for in_dtt, out_dtt, arr, slope_only, slope_inter, neither in ( - (np.float32, np.float32, 1, True, True, True), - (np.float64, np.float32, 1, True, True, True), - (np.float32, np.complex128, 1, True, True, True), - (np.uint32, np.complex128, 1, True, True, True), - (np.int64, np.float32, 1, True, True, True), - (np.float32, np.int16, 1, True, True, False), - (np.complex128, np.float32, 1, False, False, False), - (np.complex128, np.int16, 1, False, False, False), - (np.uint8, np.int16, 1, True, True, True), - # The following tests depend on the input data - (np.uint16, np.int16, 1, True, True, True), # 1 is in range - (np.uint16, np.int16, 2**16 - 1, True, True, False), # This not in range - (np.uint16, np.int16, (0, 2**16 - 1), True, True, False), - (np.uint16, np.uint8, 1, True, True, True), - (np.int16, np.uint16, 1, True, True, True), # in range - (np.int16, np.uint16, -1, True, True, False), # flip works for scaling - (np.int16, np.uint16, (-1, 1), False, True, False), # not with +- - (np.int8, np.uint16, 1, True, True, True), # in range - (np.int8, np.uint16, -1, True, True, False), # flip works for scaling - (np.int8, np.uint16, (-1, 1), False, True, False), # not with +- - ): - # data for casting - data = np.array(arr, dtype=in_dtt) - # With scaling but no intercept - if slope_only: - SlopeArrayWriter(data, out_dtt) - else: - with pytest.raises(WriterError): - SlopeArrayWriter(data, out_dtt) - # With scaling and intercept - if slope_inter: - SlopeInterArrayWriter(data, out_dtt) - else: - with pytest.raises(WriterError): - SlopeInterArrayWriter(data, out_dtt) - # With neither - if neither: - ArrayWriter(data, out_dtt) - else: - with pytest.raises(WriterError): - ArrayWriter(data, out_dtt) - - -def test_calculate_scale(): - # Test for special cases in scale calculation - npa = np.array - SIAW = SlopeInterArrayWriter - SAW = SlopeArrayWriter - # Offset handles scaling when it can - aw = SIAW(npa([-2, -1], dtype=np.int8), np.uint8) - assert get_slope_inter(aw) == (1.0, -2.0) - # Sign flip handles these cases - aw = SAW(npa([-2, -1], dtype=np.int8), np.uint8) - assert get_slope_inter(aw) == (-1.0, 0.0) - aw = SAW(npa([-2, 0], dtype=np.int8), np.uint8) - assert get_slope_inter(aw) == (-1.0, 0.0) - # But not when min magnitude is too large (scaling mechanism kicks in) - aw = SAW(npa([-510, 0], dtype=np.int16), np.uint8) - assert get_slope_inter(aw) == (-2.0, 0.0) - # Or for floats (attempts to expand across range) - aw = SAW(npa([-2, 0], dtype=np.float32), np.uint8) - assert get_slope_inter(aw) != (-1.0, 0.0) - # Case where offset handles scaling - aw = SIAW(npa([-1, 1], dtype=np.int8), np.uint8) - assert get_slope_inter(aw) == (1.0, -1.0) - # Can't work for no offset case - with pytest.raises(WriterError): - SAW(npa([-1, 1], dtype=np.int8), np.uint8) - # Offset trick can't work when max is out of range - aw = SIAW(npa([-1, 255], dtype=np.int16), np.uint8) - slope_inter = get_slope_inter(aw) - assert slope_inter != (1.0, -1.0) - - -def test_resets(): - # Test reset of values, caching of scales - for klass, inp, outp in ( - (SlopeInterArrayWriter, (1, 511), (2.0, 1.0)), - (SlopeArrayWriter, (0, 510), (2.0, 0.0)), - ): - arr = np.array(inp) - outp = np.array(outp) - aw = klass(arr, np.uint8) - assert_array_equal(get_slope_inter(aw), outp) - aw.calc_scale() # cached no change - assert_array_equal(get_slope_inter(aw), outp) - aw.calc_scale(force=True) # same data, no change - assert_array_equal(get_slope_inter(aw), outp) - # Change underlying array - aw.array[:] = aw.array * 2 - aw.calc_scale() # cached still - assert_array_equal(get_slope_inter(aw), outp) - aw.calc_scale(force=True) # new data, change - assert_array_equal(get_slope_inter(aw), outp * 2) - # Test reset - aw.reset() - assert_array_equal(get_slope_inter(aw), (1.0, 0.0)) - - -def test_no_offset_scale(): - # Specific tests of no-offset scaling - SAW = SlopeArrayWriter - # Floating point - for data in ( - (-128, 127), - (-128, 126), - (-128, -127), - (-128, 0), - (-128, -1), - (126, 127), - (-127, 127), - ): - aw = SAW(np.array(data, dtype=np.float32), np.int8) - assert aw.slope == 1.0 - aw = SAW(np.array([-126, 127 * 2.0], dtype=np.float32), np.int8) - assert aw.slope == 2 - aw = SAW(np.array([-128 * 2.0, 127], dtype=np.float32), np.int8) - assert aw.slope == 2 - # Test that nasty abs behavior does not upset us - n = -(2**15) - aw = SAW(np.array([n, n], dtype=np.int16), np.uint8) - assert_array_almost_equal(aw.slope, n / 255.0, 5) - - -def test_with_offset_scale(): - # Tests of specific cases in slope, inter - SIAW = SlopeInterArrayWriter - aw = SIAW(np.array([0, 127], dtype=np.int8), np.uint8) - assert (aw.slope, aw.inter) == (1, 0) # in range - aw = SIAW(np.array([-1, 126], dtype=np.int8), np.uint8) - assert (aw.slope, aw.inter) == (1, -1) # offset only - aw = SIAW(np.array([-1, 254], dtype=np.int16), np.uint8) - assert (aw.slope, aw.inter) == (1, -1) # offset only - aw = SIAW(np.array([-1, 255], dtype=np.int16), np.uint8) - assert (aw.slope, aw.inter) != (1, -1) # Too big for offset only - aw = SIAW(np.array([-256, -2], dtype=np.int16), np.uint8) - assert (aw.slope, aw.inter) == (1, -256) # offset only - aw = SIAW(np.array([-256, -2], dtype=np.int16), np.int8) - assert (aw.slope, aw.inter) == (1, -129) # offset only - - -def test_io_scaling(): - # Test scaling works for max, min when going from larger to smaller type, - # and from float to integer. - bio = BytesIO() - for in_type, out_type in itertools.product( - (np.int16, np.uint16, np.float32), (np.int8, np.uint8, np.int16, np.uint16) - ): - out_dtype = np.dtype(out_type) - info = type_info(in_type) - imin, imax = info['min'], info['max'] - if imin == 0: # unsigned int - val_tuples = ((0, imax), (100, imax)) - else: - val_tuples = ((imin, 0, imax), (imin, 0), (0, imax), (imin, 100, imax)) - if imin != 0: - val_tuples += ((imin, 0), (0, imax)) - for vals in val_tuples: - arr = np.array(vals, dtype=in_type) - aw = SlopeInterArrayWriter(arr, out_dtype) - aw.to_fileobj(bio) - arr2 = array_from_file(arr.shape, out_dtype, bio) - arr3 = apply_read_scaling(arr2, aw.slope, aw.inter) - # Max rounding error for integer type - # Slope might be negative - max_miss = np.abs(aw.slope) / 2.0 - abs_err = np.abs(arr - arr3) - assert np.all(abs_err <= max_miss) - if out_type in UINT_TYPES and 0 in (min(arr), max(arr)): - # Check that error is minimized for 0 as min or max - assert min(abs_err) == abs_err[arr == 0] - bio.truncate(0) - bio.seek(0) - - -def test_input_ranges(): - # Test we get good precision for a range of input data - arr = np.arange(-500, 501, 10, dtype=np.float64) - bio = BytesIO() - working_type = np.float32 - work_eps = np.finfo(working_type).eps - for out_type, offset in itertools.product(IUINT_TYPES, range(-1000, 1000, 100)): - aw = SlopeInterArrayWriter(arr, out_type) - aw.to_fileobj(bio) - arr2 = array_from_file(arr.shape, out_type, bio) - arr3 = apply_read_scaling(arr2, aw.slope, aw.inter) - # Max rounding error for integer type - # Slope might be negative - max_miss = np.abs(aw.slope) / working_type(2.0) + work_eps * 10 - abs_err = np.abs(arr - arr3) - max_err = np.abs(arr) * work_eps + max_miss - assert np.all(abs_err <= max_err) - if out_type in UINT_TYPES and 0 in (min(arr), max(arr)): - # Check that error is minimized for 0 as min or max - assert min(abs_err) == abs_err[arr == 0] - bio.truncate(0) - bio.seek(0) - - -def test_nan2zero(): - # Test conditions under which nans written to zero, and error conditions - # nan2zero as argument to `to_fileobj` deprecated, raises error if not the - # same as input nan2zero - meaning that by default, nan2zero of False will - # raise an error. - arr = np.array([np.nan, 99.0], dtype=np.float32) - for awt, kwargs in ( - (ArrayWriter, dict(check_scaling=False)), - (SlopeArrayWriter, dict(calc_scale=False)), - (SlopeInterArrayWriter, dict(calc_scale=False)), - ): - # nan2zero default is True - # nan2zero ignored for floats - aw = awt(arr, np.float32, **kwargs) - data_back = round_trip(aw) - assert_array_equal(np.isnan(data_back), [True, False]) - # set explicitly - aw = awt(arr, np.float32, nan2zero=True, **kwargs) - data_back = round_trip(aw) - assert_array_equal(np.isnan(data_back), [True, False]) - # Integer output with nan2zero gives zero - aw = awt(arr, np.int32, **kwargs) - data_back = round_trip(aw) - assert_array_equal(data_back, [0, 99]) - # Integer output with nan2zero=False gives whatever astype gives - aw = awt(arr, np.int32, nan2zero=False, **kwargs) - data_back = round_trip(aw) - astype_res = np.array(np.nan).astype(np.int32) - assert_array_equal(data_back, [astype_res, 99]) - - -def test_byte_orders(): - arr = np.arange(10, dtype=np.int32) - # Test endian read/write of types not requiring scaling - for tp in (np.uint64, np.float64, np.complex128): - dt = np.dtype(tp) - for code in '<>': - ndt = dt.newbyteorder(code) - for klass in (SlopeInterArrayWriter, SlopeArrayWriter, ArrayWriter): - aw = klass(arr, ndt) - data_back = round_trip(aw) - assert_array_almost_equal(arr, data_back) - - -def test_writers_roundtrip(): - ndt = np.dtype(np.float64) - arr = np.arange(3, dtype=ndt) - # intercept - aw = SlopeInterArrayWriter(arr, ndt, calc_scale=False) - aw.inter = 1.0 - data_back = round_trip(aw) - assert_array_equal(data_back, arr) - # scaling - aw.slope = 2.0 - data_back = round_trip(aw) - assert_array_equal(data_back, arr) - # if there is no valid data, we get zeros - aw = SlopeInterArrayWriter(arr + np.nan, np.int32) - data_back = round_trip(aw) - assert_array_equal(data_back, np.zeros(arr.shape)) - # infs generate ints at same value as max - arr[0] = np.inf - aw = SlopeInterArrayWriter(arr, np.int32) - data_back = round_trip(aw) - assert_array_almost_equal(data_back, [2, 1, 2]) - - -def test_to_float(): - start, stop = 0, 100 - for in_type in NUMERIC_TYPES: - step = 1 if in_type in IUINT_TYPES else 0.5 - info = type_info(in_type) - mn, mx = info['min'], info['max'] - arr = np.arange(start, stop, step, dtype=in_type) - arr[0] = mn - arr[-1] = mx - for out_type in CFLOAT_TYPES: - out_info = type_info(out_type) - for klass in (SlopeInterArrayWriter, SlopeArrayWriter, ArrayWriter): - if in_type in COMPLEX_TYPES and out_type in FLOAT_TYPES: - with pytest.raises(WriterError): - klass(arr, out_type) - continue - aw = klass(arr, out_type) - assert aw.array is arr - assert aw.out_dtype == out_type - arr_back = round_trip(aw) - assert_array_equal(arr.astype(out_type), arr_back) - # Check too-big values overflowed correctly - out_min, out_max = out_info['min'], out_info['max'] - assert np.all(arr_back[arr > out_max] == np.inf) - assert np.all(arr_back[arr < out_min] == -np.inf) - - -def test_dumber_writers(): - arr = np.arange(10, dtype=np.float64) - aw = SlopeArrayWriter(arr) - aw.slope = 2.0 - assert aw.slope == 2.0 - with pytest.raises(AttributeError): - aw.inter - aw = ArrayWriter(arr) - with pytest.raises(AttributeError): - aw.slope - with pytest.raises(AttributeError): - aw.inter - # Attempt at scaling should raise error for dumb type - with pytest.raises(WriterError): - ArrayWriter(arr, np.int16) - - -def test_writer_maker(): - arr = np.arange(10, dtype=np.float64) - aw = make_array_writer(arr, np.float64) - assert isinstance(aw, SlopeInterArrayWriter) - aw = make_array_writer(arr, np.float64, True, True) - assert isinstance(aw, SlopeInterArrayWriter) - aw = make_array_writer(arr, np.float64, True, False) - assert isinstance(aw, SlopeArrayWriter) - aw = make_array_writer(arr, np.float64, False, False) - assert isinstance(aw, ArrayWriter) - with pytest.raises(ValueError): - make_array_writer(arr, np.float64, False) - with pytest.raises(ValueError): - make_array_writer(arr, np.float64, False, True) - # Does calc_scale get run by default? - aw = make_array_writer(arr, np.int16, calc_scale=False) - assert (aw.slope, aw.inter) == (1, 0) - aw.calc_scale() - slope, inter = aw.slope, aw.inter - assert not (slope, inter) == (1, 0) - # Should run by default - aw = make_array_writer(arr, np.int16) - assert (aw.slope, aw.inter) == (slope, inter) - aw = make_array_writer(arr, np.int16, calc_scale=True) - assert (aw.slope, aw.inter) == (slope, inter) - - -def test_float_int_min_max(): - # Conversion between float and int - for in_dt in FLOAT_TYPES: - finf = type_info(in_dt) - arr = np.array([finf['min'], finf['max']], dtype=in_dt) - # Bug in numpy 1.6.2 on PPC leading to infs - abort - if not np.all(np.isfinite(arr)): - print(f'Hit PPC max -> inf bug; skip in_type {in_dt}') - continue - for out_dt in IUINT_TYPES: - try: - with suppress_warnings(): # overflow - aw = SlopeInterArrayWriter(arr, out_dt) - except ScalingError: - continue - arr_back_sc = round_trip(aw) - assert np.allclose(arr, arr_back_sc) - - -def test_int_int_min_max(): - # Conversion between (u)int and (u)int - eps = np.finfo(np.float64).eps - rtol = 1e-6 - for in_dt in IUINT_TYPES: - iinf = np.iinfo(in_dt) - arr = np.array([iinf.min, iinf.max], dtype=in_dt) - for out_dt in IUINT_TYPES: - try: - aw = SlopeInterArrayWriter(arr, out_dt) - except ScalingError: - continue - arr_back_sc = round_trip(aw) - # integer allclose - adiff = int_abs(arr - arr_back_sc) - rdiff = adiff / (arr + eps) - assert np.all(rdiff < rtol) - - -def test_int_int_slope(): - # Conversion between (u)int and (u)int for slopes only - eps = np.finfo(np.float64).eps - rtol = 1e-7 - for in_dt in IUINT_TYPES: - iinf = np.iinfo(in_dt) - for out_dt in IUINT_TYPES: - kinds = np.dtype(in_dt).kind + np.dtype(out_dt).kind - if kinds in ('ii', 'uu', 'ui'): - arrs = (np.array([iinf.min, iinf.max], dtype=in_dt),) - elif kinds == 'iu': - arrs = (np.array([iinf.min, 0], dtype=in_dt), np.array([0, iinf.max], dtype=in_dt)) - for arr in arrs: - try: - aw = SlopeArrayWriter(arr, out_dt) - except ScalingError: - continue - assert not aw.slope == 0 - arr_back_sc = round_trip(aw) - # integer allclose - adiff = int_abs(arr - arr_back_sc) - rdiff = adiff / (arr + eps) - assert np.all(rdiff < rtol) - - -def test_float_int_spread(): - # Test rounding error for spread of values - powers = np.arange(-10, 10, 0.5) - arr = np.concatenate((-(10**powers), 10**powers)) - for in_dt in (np.float32, np.float64): - arr_t = arr.astype(in_dt) - for out_dt in IUINT_TYPES: - aw = SlopeInterArrayWriter(arr_t, out_dt) - arr_back_sc = round_trip(aw) - # Get estimate for error - max_miss = rt_err_estimate(arr_t, arr_back_sc.dtype, aw.slope, aw.inter) - # Simulate allclose test with large atol - diff = np.abs(arr_t - arr_back_sc) - rdiff = diff / np.abs(arr_t) - assert np.all((diff <= max_miss) | (rdiff <= 1e-5)) - - -def rt_err_estimate(arr_t, out_dtype, slope, inter): - # Error attributable to rounding - slope = 1 if slope is None else slope - inter = 1 if inter is None else inter - max_int_miss = slope / 2.0 - # Estimate error attributable to floating point slope / inter; - # Remove inter / slope, put in a float type to simulate the type - # promotion for the multiplication, apply slope / inter - flt_there = (arr_t - inter) / slope - flt_back = flt_there.astype(out_dtype) * slope + inter - max_flt_miss = np.abs(arr_t - flt_back).max() - # Max error is sum of rounding and fp error - return max_int_miss + max_flt_miss - - -def test_rt_bias(): - # Check for bias in round trip - rng = np.random.RandomState(20111214) - mu, std, count = 100, 10, 100 - arr = rng.normal(mu, std, size=(count,)) - eps = np.finfo(np.float32).eps - for in_dt in (np.float32, np.float64): - arr_t = arr.astype(in_dt) - for out_dt in IUINT_TYPES: - aw = SlopeInterArrayWriter(arr_t, out_dt) - arr_back_sc = round_trip(aw) - bias = np.mean(arr_t - arr_back_sc) - # Get estimate for error - max_miss = rt_err_estimate(arr_t, arr_back_sc.dtype, aw.slope, aw.inter) - # Hokey use of max_miss as a std estimate - bias_thresh = np.max([max_miss / np.sqrt(count), eps]) - assert np.abs(bias) < bias_thresh - - -def test_nan2zero_scaling(): - # Scaling needs to take into account whether nan can be represented as zero - # in the input data (before scaling). - # nan can be represented as zero of we can store (0 - intercept) / divslope - # in the output data - because reading back the data as `stored_array * divslope + - # intercept` will reconstruct zeros for the nans in the original input. - # - # Make array requiring scaling for which range does not cover zero -> arr - # Append nan to arr -> nan_arr - # Append 0 to arr -> zero_arr - # Write / read nan_arr, zero_arr - # Confirm nan, 0 generated same output value - for awt, in_dt, out_dt, sign in itertools.product( - (SlopeArrayWriter, SlopeInterArrayWriter), - FLOAT_TYPES, - IUINT_TYPES, - (-1, 1), - ): - # Use fixed-up type information to avoid bugs, especially on PPC - in_info = type_info(in_dt) - out_info = type_info(out_dt) - # Skip impossible combinations - if in_info['min'] == 0 and sign == -1: - continue - mx = min(in_info['max'], out_info['max'] * 2.0, 2**32) - vals = [np.nan] + [100, mx] - nan_arr = np.array(vals, dtype=in_dt) * sign - # Check that nan scales to same value as zero within same array - nan_arr_0 = np.array([0] + vals, dtype=in_dt) * sign - # Check that nan scales to almost the same value as zero in another array - zero_arr = np.nan_to_num(nan_arr) - nan_aw = awt(nan_arr, out_dt, nan2zero=True) - back_nan = round_trip(nan_aw) * float(sign) - nan_0_aw = awt(nan_arr_0, out_dt, nan2zero=True) - back_nan_0 = round_trip(nan_0_aw) * float(sign) - zero_aw = awt(zero_arr, out_dt, nan2zero=True) - back_zero = round_trip(zero_aw) * float(sign) - assert np.allclose(back_nan[1:], back_zero[1:]) - assert_array_equal(back_nan[1:], back_nan_0[2:]) - assert np.abs(back_nan[0] - back_zero[0]) < 1e-2 - assert back_nan_0[0] == back_nan_0[1] - - -def test_finite_range_nan(): - # Test finite range method and has_nan property - for in_arr, res in ( - ([[-1, 0, 1], [np.inf, np.nan, -np.inf]], (-1, 1)), - (np.array([[-1, 0, 1], [np.inf, np.nan, -np.inf]]), (-1, 1)), - ([[np.nan], [np.nan]], (np.inf, -np.inf)), # all nans slices - (np.zeros((3, 4, 5)) + np.nan, (np.inf, -np.inf)), - ([[-np.inf], [np.inf]], (np.inf, -np.inf)), # all infs slices - (np.zeros((3, 4, 5)) + np.inf, (np.inf, -np.inf)), - ([[np.nan, -1, 2], [-2, np.nan, 1]], (-2, 2)), - ([[np.nan, -np.inf, 2], [-2, np.nan, np.inf]], (-2, 2)), - ([[-np.inf, 2], [np.nan, 1]], (1, 2)), # good max case - ([[np.nan, -np.inf, 2], [-2, np.nan, np.inf]], (-2, 2)), - ([np.nan], (np.inf, -np.inf)), - ([np.inf], (np.inf, -np.inf)), - ([-np.inf], (np.inf, -np.inf)), - ([np.inf, 1], (1, 1)), # only look at finite values - ([-np.inf, 1], (1, 1)), - ([[], []], (np.inf, -np.inf)), # empty array - (np.array([[-3, 0, 1], [2, -1, 4]], dtype=int), (-3, 4)), - (np.array([[1, 0, 1], [2, 3, 4]], dtype=np.uint), (0, 4)), - ([0.0, 1, 2, 3], (0, 3)), - # Complex comparison works as if they are floats - ([[np.nan, -1 - 100j, 2], [-2, np.nan, 1 + 100j]], (-2, 2)), - ([[np.nan, -1, 2 - 100j], [-2 + 100j, np.nan, 1]], (-2 + 100j, 2 - 100j)), - ): - for awt, kwargs in ( - (ArrayWriter, dict(check_scaling=False)), - (SlopeArrayWriter, {}), - (SlopeArrayWriter, dict(calc_scale=False)), - (SlopeInterArrayWriter, {}), - (SlopeInterArrayWriter, dict(calc_scale=False)), - ): - for out_type in NUMERIC_TYPES: - has_nan = np.any(np.isnan(in_arr)) - try: - aw = awt(in_arr, out_type, **kwargs) - except WriterError: - continue - # Should not matter about the order of finite range method call - # and has_nan property - test this is true - assert aw.has_nan == has_nan - assert aw.finite_range() == res - aw = awt(in_arr, out_type, **kwargs) - assert aw.finite_range() == res - assert aw.has_nan == has_nan - # Check float types work as complex - in_arr = np.array(in_arr) - if in_arr.dtype.kind == 'f': - c_arr = in_arr.astype(np.complex128) - try: - aw = awt(c_arr, out_type, **kwargs) - except WriterError: - continue - aw = awt(c_arr, out_type, **kwargs) - assert aw.has_nan == has_nan - assert aw.finite_range() == res - # Structured type cannot be nan and we can test this - a = np.array([[1.0, 0, 1], [2, 3, 4]]).view([('f1', 'f')]) - aw = awt(a, a.dtype, **kwargs) - with pytest.raises(TypeError): - aw.finite_range() - assert not aw.has_nan diff --git a/nibabel/tests/test_batteryrunners.py b/nibabel/tests/test_batteryrunners.py deleted file mode 100644 index 5cae764c8b..0000000000 --- a/nibabel/tests/test_batteryrunners.py +++ /dev/null @@ -1,176 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Tests for BatteryRunner and Report objects""" - -import logging -from io import StringIO - -import pytest - -from ..batteryrunners import BatteryRunner, Report - - -# define some trivial functions as checks -def chk1(obj, fix=False): - rep = Report(KeyError) - if 'testkey' in obj: - return obj, rep - rep.problem_level = 20 - rep.problem_msg = 'no "testkey"' - if fix: - obj['testkey'] = 1 - rep.fix_msg = 'added "testkey"' - return obj, rep - - -def chk2(obj, fix=False): - # Can return different codes for different errors in same check - rep = Report() - try: - ok = obj['testkey'] == 0 - except KeyError: - rep.problem_level = 20 - rep.problem_msg = 'no "testkey"' - rep.error = KeyError - if fix: - obj['testkey'] = 1 - rep.fix_msg = 'added "testkey"' - return obj, rep - if ok: - return obj, rep - rep.problem_level = 10 - rep.problem_msg = '"testkey" != 0' - rep.error = ValueError - if fix: - rep.fix_msg = 'set "testkey" to 0' - obj['testkey'] = 0 - return obj, rep - - -def chk_warn(obj, fix=False): - rep = Report(KeyError) - if not 'anotherkey' in obj: - rep.problem_level = 30 - rep.problem_msg = 'no "anotherkey"' - if fix: - obj['anotherkey'] = 'a string' - rep.fix_msg = 'added "anotherkey"' - return obj, rep - - -def chk_error(obj, fix=False): - rep = Report(KeyError) - if not 'thirdkey' in obj: - rep.problem_level = 40 - rep.problem_msg = 'no "thirdkey"' - if fix: - obj['anotherkey'] = 'a string' - rep.fix_msg = 'added "anotherkey"' - return obj, rep - - -def test_init_basic(): - # With no args, raise - with pytest.raises(TypeError): - BatteryRunner() - # Len returns number of checks - battrun = BatteryRunner((chk1,)) - assert len(battrun) == 1 - battrun = BatteryRunner((chk1, chk2)) - assert len(battrun) == 2 - - -def test_init_report(): - rep = Report() - assert rep == Report(Exception, 0, '', '') - - -def test_report_strings(): - rep = Report() - assert rep.__str__() != '' - assert rep.message == '' - str_io = StringIO() - rep.write_raise(str_io) - assert str_io.getvalue() == '' - rep = Report(ValueError, 20, 'msg', 'fix') - rep.write_raise(str_io) - assert str_io.getvalue() == '' - rep.problem_level = 30 - rep.write_raise(str_io) - assert str_io.getvalue() == 'Level 30: msg; fix\n' - str_io.truncate(0) - str_io.seek(0) - # No fix string, no fix message - rep.fix_msg = '' - rep.write_raise(str_io) - assert str_io.getvalue() == 'Level 30: msg\n' - rep.fix_msg = 'fix' - str_io.truncate(0) - str_io.seek(0) - # If we drop the level, nothing goes to the log - rep.problem_level = 20 - rep.write_raise(str_io) - assert str_io.getvalue() == '' - # Unless we set the default log level in the call - rep.write_raise(str_io, log_level=20) - assert str_io.getvalue() == 'Level 20: msg; fix\n' - str_io.truncate(0) - str_io.seek(0) - # If we set the error level down this low, we raise an error - with pytest.raises(ValueError): - rep.write_raise(str_io, 20) - # But the log level wasn't low enough to do a log entry - assert str_io.getvalue() == '' - # Error still raised with lower log threshold, but now we do get a - # log entry - with pytest.raises(ValueError): - rep.write_raise(str_io, 20, 20) - assert str_io.getvalue() == 'Level 20: msg; fix\n' - # If there's no error, we can't raise - str_io.truncate(0) - str_io.seek(0) - rep.error = None - rep.write_raise(str_io, 20) - assert str_io.getvalue() == '' - - -def test_logging(): - rep = Report(ValueError, 20, 'msg', 'fix') - str_io = StringIO() - logger = logging.getLogger('test.logger') - logger.setLevel(30) # defaultish level - logger.addHandler(logging.StreamHandler(str_io)) - rep.log_raise(logger) - assert str_io.getvalue() == '' - rep.problem_level = 30 - rep.log_raise(logger) - assert str_io.getvalue() == 'msg; fix\n' - str_io.truncate(0) - str_io.seek(0) - - -def test_checks(): - battrun = BatteryRunner((chk1,)) - reports = battrun.check_only({}) - assert reports[0] == Report(KeyError, 20, 'no "testkey"', '') - obj, reports = battrun.check_fix({}) - assert reports[0] == Report(KeyError, 20, 'no "testkey"', 'added "testkey"') - assert obj == {'testkey': 1} - battrun = BatteryRunner((chk1, chk2)) - reports = battrun.check_only({}) - assert reports[0] == Report(KeyError, 20, 'no "testkey"', '') - assert reports[1] == Report(KeyError, 20, 'no "testkey"', '') - obj, reports = battrun.check_fix({}) - # In the case of fix, the previous fix exposes a different error - # Note, because obj is mutable, first and second point to modified - # (and final) dictionary - output_obj = {'testkey': 0} - assert reports[0] == Report(KeyError, 20, 'no "testkey"', 'added "testkey"') - assert reports[1] == Report(ValueError, 10, '"testkey" != 0', 'set "testkey" to 0') - assert obj == output_obj diff --git a/nibabel/tests/test_brikhead.py b/nibabel/tests/test_brikhead.py deleted file mode 100644 index 31e0d0d47c..0000000000 --- a/nibabel/tests/test_brikhead.py +++ /dev/null @@ -1,147 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## - -from os.path import join as pjoin - -import numpy as np -import pytest -from numpy.testing import assert_array_equal - -from .. import Nifti1Image, brikhead -from ..testing import assert_data_similar, data_path -from .test_fileslice import slicer_samples - -EXAMPLE_IMAGES = [ - dict( - head=pjoin(data_path, 'example4d+orig.HEAD'), - fname=pjoin(data_path, 'example4d+orig.BRIK.gz'), - shape=(33, 41, 25, 3), - dtype=np.int16, - affine=np.array( - [ - [-3.0, 0, 0, 49.5], - [0, -3.0, 0, 82.312], - [0, 0, 3.0, -52.3511], - [0, 0, 0, 1.0], - ] - ), - zooms=(3.0, 3.0, 3.0, 3.0), - data_summary=dict(min=0, max=13722, mean=4266.76024636), - is_proxy=True, - space='ORIG', - labels=['#0', '#1', '#2'], - scaling=None, - ), - dict( - head=pjoin(data_path, 'scaled+tlrc.HEAD'), - fname=pjoin(data_path, 'scaled+tlrc.BRIK'), - shape=(47, 54, 43, 1.0), - dtype=np.int16, - affine=np.array( - [ - [3.0, 0, 0, -66.0], - [0, 3.0, 0, -87.0], - [0, 0, 3.0, -54.0], - [0, 0, 0, 1.0], - ] - ), - zooms=(3.0, 3.0, 3.0, 0.0), - data_summary=dict( - min=1.9416814999999998e-07, max=0.0012724615542099998, mean=0.00023919645351876782 - ), - is_proxy=True, - space='TLRC', - labels=['#0'], - scaling=np.array([3.88336300e-08]), - ), -] - -EXAMPLE_BAD_IMAGES = [ - dict(head=pjoin(data_path, 'bad_datatype+orig.HEAD'), err=brikhead.AFNIImageError), - dict(head=pjoin(data_path, 'bad_attribute+orig.HEAD'), err=brikhead.AFNIHeaderError), -] - - -class TestAFNIHeader: - module = brikhead - test_files = EXAMPLE_IMAGES - - def test_makehead(self): - for tp in self.test_files: - head1 = self.module.AFNIHeader.from_fileobj(tp['head']) - head2 = self.module.AFNIHeader.from_header(head1) - assert head1 == head2 - with pytest.raises(self.module.AFNIHeaderError): - self.module.AFNIHeader.from_header(header=None) - with pytest.raises(self.module.AFNIHeaderError): - self.module.AFNIHeader.from_header(tp['fname']) - - -class TestAFNIImage: - module = brikhead - test_files = EXAMPLE_IMAGES - - def test_brikheadfile(self): - for tp in self.test_files: - brik = self.module.load(tp['fname']) - assert brik.get_data_dtype().type == tp['dtype'] - assert brik.shape == tp['shape'] - assert brik.header.get_zooms() == tp['zooms'] - assert_array_equal(brik.affine, tp['affine']) - assert brik.header.get_space() == tp['space'] - data = brik.get_fdata() - assert data.shape == tp['shape'] - assert_array_equal(brik.dataobj.scaling, tp['scaling']) - assert brik.header.get_volume_labels() == tp['labels'] - - def test_load(self): - # Check highest level load of brikhead works - for tp in self.test_files: - img = self.module.load(tp['head']) - data = img.get_fdata() - assert data.shape == tp['shape'] - # min, max, mean values - assert_data_similar(data, tp) - # check if file can be converted to nifti - ni_img = Nifti1Image.from_image(img) - assert_array_equal(ni_img.affine, tp['affine']) - assert_array_equal(ni_img.get_fdata(), data) - - def test_array_proxy_slicing(self): - # Test slicing of array proxy - for tp in self.test_files: - img = self.module.load(tp['fname']) - arr = img.get_fdata() - prox = img.dataobj - assert prox.is_proxy - for sliceobj in slicer_samples(img.shape): - assert_array_equal(arr[sliceobj], prox[sliceobj]) - - -class TestBadFiles: - module = brikhead - test_files = EXAMPLE_BAD_IMAGES - - def test_brikheadfile(self): - for tp in self.test_files: - with pytest.raises(tp['err']): - self.module.load(tp['head']) - - -class TestBadVars: - module = brikhead - vars = [ - 'type = badtype-attribute\nname = BRICK_TYPES\ncount = 1\n1\n', - 'type = integer-attribute\ncount = 1\n1\n', - ] - - def test_unpack_var(self): - for var in self.vars: - with pytest.raises(self.module.AFNIHeaderError): - self.module._unpack_var(var) diff --git a/nibabel/tests/test_casting.py b/nibabel/tests/test_casting.py deleted file mode 100644 index c6c1ddb661..0000000000 --- a/nibabel/tests/test_casting.py +++ /dev/null @@ -1,271 +0,0 @@ -"""Test casting utilities""" - -import os -from platform import machine - -import numpy as np -import pytest -from numpy.testing import assert_array_equal - -from ..casting import ( - CastingError, - able_int_type, - best_float, - float_to_int, - floor_log2, - int_abs, - longdouble_precision_improved, - sctypes, - shared_range, - ulp, -) -from ..testing import suppress_warnings - - -def test_shared_range(): - for ft in sctypes['float']: - for it in sctypes['int'] + sctypes['uint']: - # Test that going a bit above or below the calculated min and max - # either generates the same number when cast, or the max int value - # (if this system generates that) or something smaller (because of - # overflow) - mn, mx = shared_range(ft, it) - with suppress_warnings(): - ovs = ft(mx) + np.arange(2048, dtype=ft) - # Float16 can overflow to inf - bit_bigger = ovs[np.isfinite(ovs)].astype(it) - casted_mx = ft(mx).astype(it) - imax = int(np.iinfo(it).max) - thresh_overflow = False - if casted_mx != imax: - # The shared_range have told us that they believe the imax does - # not have an exact representation. - fimax = ft(imax) - if np.isfinite(fimax): - assert int(fimax) != imax - # Therefore the imax, cast back to float, and to integer, will - # overflow. If it overflows to the imax, we need to allow for - # that possibility in the testing of our overflowed values - imax_roundtrip = fimax.astype(it) - if imax_roundtrip == imax: - thresh_overflow = True - if thresh_overflow: - assert np.all((bit_bigger == casted_mx) | (bit_bigger == imax)) - else: - assert np.all(bit_bigger <= casted_mx) - if it in sctypes['uint']: - assert mn == 0 - continue - # And something larger for the minimum - with suppress_warnings(): # overflow - ovs = ft(mn) - np.arange(2048, dtype=ft) - # Float16 can overflow to inf - bit_smaller = ovs[np.isfinite(ovs)].astype(it) - casted_mn = ft(mn).astype(it) - imin = int(np.iinfo(it).min) - if casted_mn != imin: - # The shared_range have told us that they believe the imin does - # not have an exact representation. - fimin = ft(imin) - if np.isfinite(fimin): - assert int(fimin) != imin - # Therefore the imin, cast back to float, and to integer, will - # overflow. If it overflows to the imin, we need to allow for - # that possibility in the testing of our overflowed values - imin_roundtrip = fimin.astype(it) - if imin_roundtrip == imin: - thresh_overflow = True - if thresh_overflow: - assert np.all((bit_smaller == casted_mn) | (bit_smaller == imin)) - else: - assert np.all(bit_smaller >= casted_mn) - - -def test_shared_range_inputs(): - # Check any dtype specifier will work as input - rng0 = shared_range(np.float32, np.int32) - assert_array_equal(rng0, shared_range('f4', 'i4')) - assert_array_equal(rng0, shared_range(np.dtype('f4'), np.dtype('i4'))) - - -def test_casting(): - for ft in sctypes['float']: - for it in sctypes['int'] + sctypes['uint']: - ii = np.iinfo(it) - arr = [ii.min - 1, ii.max + 1, -np.inf, np.inf, np.nan, 0.2, 10.6] - farr_orig = np.array(arr, dtype=ft) - # We're later going to test if we modify this array - farr = farr_orig.copy() - mn, mx = shared_range(ft, it) - with np.errstate(invalid='ignore'): - iarr = float_to_int(farr, it) - exp_arr = np.array([mn, mx, mn, mx, 0, 0, 11], dtype=it) - assert_array_equal(iarr, exp_arr) - # Now test infmax version - with np.errstate(invalid='ignore'): - iarr = float_to_int(farr, it, infmax=True) - im_exp = np.array([mn, mx, ii.min, ii.max, 0, 0, 11], dtype=it) - # Float16 can overflow to infs - if farr[0] == -np.inf: - im_exp[0] = ii.min - if farr[1] == np.inf: - im_exp[1] = ii.max - assert_array_equal(iarr, im_exp) - # NaNs, with nan2zero False, gives error - with pytest.raises(CastingError): - float_to_int(farr, it, False) - # We can pass through NaNs if we really want - exp_arr[arr.index(np.nan)] = ft(np.nan).astype(it) - with np.errstate(invalid='ignore'): - iarr = float_to_int(farr, it, nan2zero=None) - assert_array_equal(iarr, exp_arr) - # Confirm input array is not modified - nans = np.isnan(farr) - assert_array_equal(nans, np.isnan(farr_orig)) - assert_array_equal(farr[nans == False], farr_orig[nans == False]) - # Test scalars work and return scalars - assert_array_equal(float_to_int(np.float32(0), np.int16), [0]) - # Test scalar nan OK - with np.errstate(invalid='ignore'): - assert_array_equal(float_to_int(np.nan, np.int16), [0]) - # Test nans give error if not nan2zero - with pytest.raises(CastingError): - float_to_int(np.nan, np.int16, False) - - -def test_int_abs(): - for itype in sctypes['int']: - info = np.iinfo(itype) - in_arr = np.array([info.min, info.max], dtype=itype) - idtype = np.dtype(itype) - udtype = np.dtype(idtype.str.replace('i', 'u')) - assert udtype.kind == 'u' - assert idtype.itemsize == udtype.itemsize - mn, mx = in_arr - e_mn = int(mx) + 1 - assert int_abs(mx) == mx - assert int_abs(mn) == e_mn - assert_array_equal(int_abs(in_arr), [e_mn, mx]) - - -def test_floor_log2(): - assert floor_log2(2**9 + 1) == 9 - assert floor_log2(-(2**9) + 1) == 8 - assert floor_log2(2) == 1 - assert floor_log2(1) == 0 - assert floor_log2(0.5) == -1 - assert floor_log2(0.75) == -1 - assert floor_log2(0.25) == -2 - assert floor_log2(0.24) == -3 - assert floor_log2(0) is None - - -def test_able_int_type(): - # The integer type capable of containing values - for vals, exp_out in ( - ([0, 1], np.uint8), - ([0, 255], np.uint8), - ([-1, 1], np.int8), - ([0, 256], np.uint16), - ([-1, 128], np.int16), - ([0.1, 1], None), - ([0, 2**16], np.uint32), - ([-1, 2**15], np.int32), - ([0, 2**32], np.uint64), - ([-1, 2**31], np.int64), - ([-1, 2**64 - 1], None), - ([0, 2**64 - 1], np.uint64), - ([0, 2**64], None), - ): - assert able_int_type(vals) == exp_out - - -def test_able_casting(): - # Check the able_int_type function guesses numpy out type - types = sctypes['int'] + sctypes['uint'] - for in_type in types: - in_info = np.iinfo(in_type) - in_mn, in_mx = in_info.min, in_info.max - A = np.zeros((1,), dtype=in_type) - for out_type in types: - out_info = np.iinfo(out_type) - out_mn, out_mx = out_info.min, out_info.max - B = np.zeros((1,), dtype=out_type) - ApBt = (A + B).dtype.type - able_type = able_int_type([in_mn, in_mx, out_mn, out_mx]) - if able_type is None: - assert ApBt == np.float64 - continue - # Use str for comparison to avoid int32/64 vs intp comparison - # failures - assert np.dtype(ApBt).str == np.dtype(able_type).str - - -def test_best_float(): - # Finds the most capable floating point type - """most capable type will be np.longdouble except when - - * np.longdouble has float64 precision (MSVC compiled numpy) - * machine is sparc64 (float128 very slow) - * np.longdouble had float64 precision when ``casting`` moduled was imported - (precisions on windows can change, apparently) - """ - best = best_float() - end_of_ints = np.float64(2**53) - # float64 has continuous integers up to 2**53 - assert end_of_ints == end_of_ints + 1 - # longdouble may have more, but not on 32 bit windows, at least - end_of_ints = np.longdouble(2**53) - if ( - end_of_ints == (end_of_ints + 1) - or machine() == 'sparc64' # off continuous integers - or longdouble_precision_improved() # crippling slow longdouble on sparc - ): # Windows precisions can change - assert best == np.float64 - else: - assert best == np.longdouble - - -def test_longdouble_precision_improved(): - # Just check that this can only be True on Windows - - # This previously used distutils.ccompiler.get_default_compiler to check for msvc - # In https://github.com/python/cpython/blob/3467991/Lib/distutils/ccompiler.py#L919-L956 - # we see that this was implied by os.name == 'nt', so we can remove this deprecated - # call. - # However, there may be detectable conditions in Windows where we would expect this - # to be False as well. - if os.name != 'nt': - assert not longdouble_precision_improved() - - -def test_ulp(): - assert ulp() == np.finfo(np.float64).eps - assert ulp(1.0) == np.finfo(np.float64).eps - assert ulp(np.float32(1.0)) == np.finfo(np.float32).eps - assert ulp(np.float32(1.999)) == np.finfo(np.float32).eps - # Integers always return 1 - assert ulp(1) == 1 - assert ulp(2**63 - 1) == 1 - # negative / positive same - assert ulp(-1) == 1 - assert ulp(7.999) == ulp(4.0) - assert ulp(-7.999) == ulp(4.0) - assert ulp(np.float64(2**54 - 2)) == 2 - assert ulp(np.float64(2**54)) == 4 - assert ulp(np.float64(2**54)) == 4 - # Infs, NaNs return nan - assert np.isnan(ulp(np.inf)) - assert np.isnan(ulp(-np.inf)) - assert np.isnan(ulp(np.nan)) - # 0 gives subnormal smallest - subn64 = np.float64(2 ** (-1022 - 52)) - subn32 = np.float32(2 ** (-126 - 23)) - assert ulp(0.0) == subn64 - assert ulp(np.float64(0)) == subn64 - assert ulp(np.float32(0)) == subn32 - # as do multiples of subnormal smallest - assert ulp(subn64 * np.float64(2**52)) == subn64 - assert ulp(subn64 * np.float64(2**53)) == subn64 * 2 - assert ulp(subn32 * np.float32(2**23)) == subn32 - assert ulp(subn32 * np.float32(2**24)) == subn32 * 2 diff --git a/nibabel/tests/test_data.py b/nibabel/tests/test_data.py deleted file mode 100644 index 511fa7f857..0000000000 --- a/nibabel/tests/test_data.py +++ /dev/null @@ -1,239 +0,0 @@ -# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -"""Tests for data module""" - -import os -import sys -import tempfile -from os import environ as env -from os.path import join as pjoin -from tempfile import TemporaryDirectory - -import pytest - -from .. import data as nibd -from ..data import ( - Bomber, - DataError, - Datasource, - VersionedDatasource, - _cfg_value, - datasource_or_bomber, - find_data_dir, - get_data_path, - make_datasource, -) -from .test_environment import DATA_KEY, USER_KEY, with_environment # noqa: F401 - - -@pytest.fixture -def with_nimd_env(request, with_environment): # noqa: F811 - DATA_FUNCS = {} - DATA_FUNCS['home_dir_func'] = nibd.get_nipy_user_dir - DATA_FUNCS['sys_dir_func'] = nibd.get_nipy_system_dir - DATA_FUNCS['path_func'] = nibd.get_data_path - yield - nibd.get_nipy_user_dir = DATA_FUNCS['home_dir_func'] - nibd.get_nipy_system_dir = DATA_FUNCS['sys_dir_func'] - nibd.get_data_path = DATA_FUNCS['path_func'] - - -def test_datasource(): - # Tests for DataSource - pth = pjoin('some', 'path') - ds = Datasource(pth) - assert ds.get_filename('unlikeley') == pjoin(pth, 'unlikeley') - assert ds.get_filename('un', 'like', 'ley') == pjoin(pth, 'un', 'like', 'ley') - - -def test_versioned(): - with TemporaryDirectory() as tmpdir: - with pytest.raises(DataError): - VersionedDatasource(tmpdir) - tmpfile = pjoin(tmpdir, 'config.ini') - # ini file, but wrong section - with open(tmpfile, 'w') as fobj: - fobj.write('[SOMESECTION]\n') - fobj.write('version = 0.1\n') - with pytest.raises(DataError): - VersionedDatasource(tmpdir) - # ini file, but right section, wrong key - with open(tmpfile, 'w') as fobj: - fobj.write('[DEFAULT]\n') - fobj.write('somekey = 0.1\n') - with pytest.raises(DataError): - VersionedDatasource(tmpdir) - # ini file, right section and key - with open(tmpfile, 'w') as fobj: - fobj.write('[DEFAULT]\n') - fobj.write('version = 0.1\n') - vds = VersionedDatasource(tmpdir) - assert vds.version == '0.1' - assert vds.version_no == 0.1 - assert vds.major_version == 0 - assert vds.minor_version == 1 - assert vds.get_filename('config.ini') == tmpfile - # ini file, right section and key, funny value - with open(tmpfile, 'w') as fobj: - fobj.write('[DEFAULT]\n') - fobj.write('version = 0.1.2.dev\n') - vds = VersionedDatasource(tmpdir) - assert vds.version == '0.1.2.dev' - assert vds.version_no == 0.1 - assert vds.major_version == 0 - assert vds.minor_version == 1 - - -def test__cfg_value(): - # no file, return '' - assert _cfg_value('/implausible_file') == '' - # try files - try: - fd, tmpfile = tempfile.mkstemp() - fobj = os.fdopen(fd, 'wt') - # wrong section, right key - fobj.write('[strange section]\n') - fobj.write('path = /some/path\n') - fobj.flush() - assert _cfg_value(tmpfile) == '' - # right section, wrong key - fobj.write('[DATA]\n') - fobj.write('funnykey = /some/path\n') - fobj.flush() - assert _cfg_value(tmpfile) == '' - # right section, right key - fobj.write('path = /some/path\n') - fobj.flush() - assert _cfg_value(tmpfile) == '/some/path' - fobj.close() - finally: - try: - os.unlink(tmpfile) - except: - pass - - -def test_data_path(with_nimd_env): - # wipe out any sources of data paths - if DATA_KEY in env: - del env[DATA_KEY] - if USER_KEY in env: - del os.environ[USER_KEY] - fake_user_dir = '/user/path' - nibd.get_nipy_system_dir = lambda: '/unlikely/path' - nibd.get_nipy_user_dir = lambda: fake_user_dir - # now we should only have anything pointed to in the user's dir - old_pth = get_data_path() - # We should have only sys.prefix and, iff sys.prefix == /usr, - # '/usr/local'. This last to is deal with Debian patching to - # distutils. - def_dirs = [pjoin(sys.prefix, 'share', 'nipy')] - if sys.prefix == '/usr': - def_dirs.append(pjoin('/usr/local', 'share', 'nipy')) - assert old_pth == def_dirs + ['/user/path'] - # then we'll try adding some of our own - tst_pth = '/a/path' + os.path.pathsep + '/b/ path' - tst_list = ['/a/path', '/b/ path'] - # First, an environment variable - os.environ[DATA_KEY] = tst_list[0] - assert get_data_path() == tst_list[:1] + old_pth - os.environ[DATA_KEY] = tst_pth - assert get_data_path() == tst_list + old_pth - del os.environ[DATA_KEY] - # Next, make a fake user directory, and put a file in there - with TemporaryDirectory() as tmpdir: - tmpfile = pjoin(tmpdir, 'config.ini') - with open(tmpfile, 'w') as fobj: - fobj.write('[DATA]\n') - fobj.write(f'path = {tst_pth}') - nibd.get_nipy_user_dir = lambda: tmpdir - assert get_data_path() == tst_list + def_dirs + [tmpdir] - nibd.get_nipy_user_dir = lambda: fake_user_dir - assert get_data_path() == old_pth - # with some trepidation, the system config files - with TemporaryDirectory() as tmpdir: - nibd.get_nipy_system_dir = lambda: tmpdir - tmpfile = pjoin(tmpdir, 'an_example.ini') - with open(tmpfile, 'w') as fobj: - fobj.write('[DATA]\n') - fobj.write(f'path = {tst_pth}\n') - tmpfile = pjoin(tmpdir, 'another_example.ini') - with open(tmpfile, 'w') as fobj: - fobj.write('[DATA]\n') - fobj.write('path = {}\n'.format('/path/two')) - assert get_data_path() == tst_list + ['/path/two'] + old_pth - - -def test_find_data_dir(): - here, fname = os.path.split(__file__) - # here == '/nipy/utils/tests' - under_here, subhere = os.path.split(here) - # under_here == '/nipy/utils' - # subhere = 'tests' - # fails with non-existent path - with pytest.raises(DataError): - find_data_dir([here], 'implausible', 'directory') - # fails with file, when directory expected - with pytest.raises(DataError): - find_data_dir([here], fname) - # passes with directory that exists - dd = find_data_dir([under_here], subhere) - assert dd == here - # and when one path in path list does not work - dud_dir = pjoin(under_here, 'implausible') - dd = find_data_dir([dud_dir, under_here], subhere) - assert dd == here - - -def test_make_datasource(with_nimd_env): - pkg_def = dict(relpath='pkg') - with TemporaryDirectory() as tmpdir: - nibd.get_data_path = lambda: [tmpdir] - with pytest.raises(DataError): - make_datasource(pkg_def) - pkg_dir = pjoin(tmpdir, 'pkg') - os.mkdir(pkg_dir) - with pytest.raises(DataError): - make_datasource(pkg_def) - tmpfile = pjoin(pkg_dir, 'config.ini') - with open(tmpfile, 'w') as fobj: - fobj.write('[DEFAULT]\n') - fobj.write('version = 0.1\n') - ds = make_datasource(pkg_def, data_path=[tmpdir]) - assert ds.version == '0.1' - - -@pytest.mark.xfail(raises=DataError) -def test_bomber(): - b = Bomber('bomber example', 'a message') - b.any_attribute # no error - - -def test_bomber_inspect(): - b = Bomber('bomber example', 'a message') - assert not hasattr(b, 'any_attribute') - - -def test_datasource_or_bomber(with_nimd_env): - pkg_def = dict(relpath='pkg') - with TemporaryDirectory() as tmpdir: - nibd.get_data_path = lambda: [tmpdir] - ds = datasource_or_bomber(pkg_def) - with pytest.raises(DataError): - ds.get_filename('some_file.txt') - pkg_dir = pjoin(tmpdir, 'pkg') - os.mkdir(pkg_dir) - tmpfile = pjoin(pkg_dir, 'config.ini') - with open(tmpfile, 'w') as fobj: - fobj.write('[DEFAULT]\n') - fobj.write('version = 0.2\n') - ds = datasource_or_bomber(pkg_def) - ds.get_filename('some_file.txt') - # check that versioning works - pkg_def['min version'] = '0.2' - ds = datasource_or_bomber(pkg_def) # OK - ds.get_filename('some_file.txt') - pkg_def['min version'] = '0.3' - ds = datasource_or_bomber(pkg_def) # not OK - with pytest.raises(DataError): - ds.get_filename('some_file.txt') diff --git a/nibabel/tests/test_dataobj_images.py b/nibabel/tests/test_dataobj_images.py deleted file mode 100644 index 877e407812..0000000000 --- a/nibabel/tests/test_dataobj_images.py +++ /dev/null @@ -1,47 +0,0 @@ -"""Testing dataobj_images module""" - -import numpy as np - -from nibabel.dataobj_images import DataobjImage -from nibabel.filebasedimages import FileBasedHeader -from nibabel.tests.test_filebasedimages import TestFBImageAPI as _TFI -from nibabel.tests.test_image_api import DataInterfaceMixin - - -class DoNumpyImage(DataobjImage): - header_class = FileBasedHeader - valid_exts = ('.npy',) - files_types = (('image', '.npy'),) - - @classmethod - def from_file_map(klass, file_map, mmap=True, keep_file_open=None): - if mmap not in (True, False, 'c', 'r'): - raise ValueError("mmap should be one of {True, False, 'c', 'r'}") - if mmap is True: - mmap = 'c' - elif mmap is False: - mmap = None - with file_map['image'].get_prepare_fileobj('rb') as fobj: - try: - arr = np.load(fobj, mmap=mmap) - except: - arr = np.load(fobj) - return klass(arr) - - def to_file_map(self, file_map=None): - file_map = self.file_map if file_map is None else file_map - with file_map['image'].get_prepare_fileobj('wb') as fobj: - np.save(fobj, self.dataobj) - - def get_data_dtype(self): - return self.dataobj.dtype - - def set_data_dtype(self, dtype): - self._dataobj = self._dataobj.astype(dtype) - - -class TestDataobjAPI(_TFI, DataInterfaceMixin): - """Validation for DataobjImage instances""" - - # A callable returning an image from ``image_maker(data, header)`` - image_maker = DoNumpyImage diff --git a/nibabel/tests/test_deprecated.py b/nibabel/tests/test_deprecated.py deleted file mode 100644 index 01636632e4..0000000000 --- a/nibabel/tests/test_deprecated.py +++ /dev/null @@ -1,118 +0,0 @@ -"""Testing `deprecated` module""" - -import warnings - -import pytest - -from nibabel import pkg_info -from nibabel.deprecated import ( - FutureWarningMixin, - ModuleProxy, - alert_future_error, - deprecate_with_version, -) -from nibabel.tests.test_deprecator import TestDeprecatorFunc as _TestDF - - -def setup_module(): - # Hack nibabel version string - pkg_info.cmp_pkg_version.__defaults__ = ('2.0',) - - -def teardown_module(): - # Hack nibabel version string back again - pkg_info.cmp_pkg_version.__defaults__ = (pkg_info.__version__,) - - -def test_module_proxy(): - # Test proxy for module - mp = ModuleProxy('nibabel.deprecated') - assert hasattr(mp, 'ModuleProxy') - assert mp.ModuleProxy is ModuleProxy - assert repr(mp) == '' - - -def test_futurewarning_mixin(): - # Test mixin for FutureWarning - class C: - def __init__(self, val): - self.val = val - - def meth(self): - return self.val - - class D(FutureWarningMixin, C): - pass - - class E(FutureWarningMixin, C): - warn_message = 'Oh no, not this one' - - with warnings.catch_warnings(record=True) as warns: - c = C(42) - assert c.meth() == 42 - assert warns == [] - d = D(42) - assert d.meth() == 42 - warn = warns.pop(0) - assert warn.category == FutureWarning - assert str(warn.message) == 'This class will be removed in future versions' - e = E(42) - assert e.meth() == 42 - warn = warns.pop(0) - assert warn.category == FutureWarning - assert str(warn.message) == 'Oh no, not this one' - - -class TestNibabelDeprecator(_TestDF): - """Test deprecations against nibabel version""" - - dep_func = deprecate_with_version - - -def test_dev_version(): - # Test that a dev version doesn't trigger deprecation error - - @deprecate_with_version('foo', until='2.0') - def func(): - return 99 - - try: - pkg_info.cmp_pkg_version.__defaults__ = ('2.0dev',) - # No error, even though version is dev version of current - with pytest.deprecated_call(): - assert func() == 99 - finally: - pkg_info.cmp_pkg_version.__defaults__ = ('2.0',) - - -def test_alert_future_error(): - with pytest.warns(FutureWarning): - alert_future_error( - 'Message', - '9999.9.9', - warning_rec='Silence this warning by doing XYZ.', - error_rec='Fix this issue by doing XYZ.', - ) - with pytest.raises(RuntimeError): - alert_future_error( - 'Message', - '1.0.0', - warning_rec='Silence this warning by doing XYZ.', - error_rec='Fix this issue by doing XYZ.', - ) - with pytest.raises(ValueError): - alert_future_error( - 'Message', - '1.0.0', - warning_rec='Silence this warning by doing XYZ.', - error_rec='Fix this issue by doing XYZ.', - error_class=ValueError, - ) - with pytest.raises(ValueError): - alert_future_error( - 'Message', - '2.0.0', # Error if we equal the (patched) version - warning_rec='Silence this warning by doing XYZ.', - error_rec='Fix this issue by doing XYZ.', - error_class=ValueError, - ) diff --git a/nibabel/tests/test_deprecator.py b/nibabel/tests/test_deprecator.py deleted file mode 100644 index 0fdaf2014a..0000000000 --- a/nibabel/tests/test_deprecator.py +++ /dev/null @@ -1,181 +0,0 @@ -"""Testing deprecator module / Deprecator class""" - -import sys -import warnings -from functools import partial -from textwrap import indent - -import pytest - -from nibabel.deprecator import ( - TESTCLEANUP, - TESTSETUP, - Deprecator, - ExpiredDeprecationError, - _add_dep_doc, - _dedent_docstring, - _ensure_cr, -) - -from ..testing import clear_and_catch_warnings - -_OWN_MODULE = sys.modules[__name__] - -func_docstring = ( - f'A docstring\n \n foo\n \n{indent(TESTSETUP, " ", lambda x: True)}' - f' Some text\n{indent(TESTCLEANUP, " ", lambda x: True)}' -) - -if sys.version_info >= (3, 13): - func_docstring = _dedent_docstring(func_docstring) - - -def test__ensure_cr(): - # Make sure text ends with carriage return - assert _ensure_cr(' foo') == ' foo\n' - assert _ensure_cr(' foo\n') == ' foo\n' - assert _ensure_cr(' foo ') == ' foo\n' - assert _ensure_cr('foo ') == 'foo\n' - assert _ensure_cr('foo \n bar') == 'foo \n bar\n' - assert _ensure_cr('foo \n\n') == 'foo\n' - - -def test__add_dep_doc(): - # Test utility function to add deprecation message to docstring - assert _add_dep_doc('', 'foo') == 'foo\n' - assert _add_dep_doc('bar', 'foo') == 'bar\n\nfoo\n' - assert _add_dep_doc(' bar', 'foo') == ' bar\n\nfoo\n' - assert _add_dep_doc(' bar', 'foo\n') == ' bar\n\nfoo\n' - assert _add_dep_doc('bar\n\n', 'foo') == 'bar\n\nfoo\n' - assert _add_dep_doc('bar\n \n', 'foo') == 'bar\n\nfoo\n' - assert ( - _add_dep_doc(' bar\n\nSome explanation', 'foo\nbaz') - == ' bar\n\nfoo\nbaz\n\nSome explanation\n' - ) - assert ( - _add_dep_doc(' bar\n\n Some explanation', 'foo\nbaz') - == ' bar\n \n foo\n baz\n \n Some explanation\n' - ) - - -class CustomError(Exception): - """Custom error class for testing expired deprecation errors""" - - -def cmp_func(v): - """Comparison func tests against version 2.0""" - return (float(v) > 2) - (float(v) < 2) - - -def func_no_doc(): - pass - - -def func_doc(i): - """A docstring""" - - -def func_doc_long(i, j): - """A docstring\n\n Some text""" - - -class TestDeprecatorFunc: - """Test deprecator function specified in ``dep_func``""" - - dep_func = Deprecator(cmp_func) - - def test_dep_func(self): - # Test function deprecation - dec = self.dep_func - func = dec('foo')(func_no_doc) - with pytest.deprecated_call(): - assert func() is None - assert func.__doc__ == 'foo\n' - func = dec('foo')(func_doc) - with pytest.deprecated_call() as w: - assert func(1) is None - assert len(w) == 1 - assert func.__doc__ == 'A docstring\n\nfoo\n' - func = dec('foo')(func_doc_long) - with pytest.deprecated_call() as w: - assert func(1, 2) is None - assert len(w) == 1 - assert func.__doc__ == func_docstring - - # Try some since and until versions - func = dec('foo', '1.1')(func_no_doc) - assert func.__doc__ == 'foo\n\n* deprecated from version: 1.1\n' - with pytest.deprecated_call() as w: - assert func() is None - assert len(w) == 1 - func = dec('foo', until='99.4')(func_no_doc) - with pytest.deprecated_call() as w: - assert func() is None - assert len(w) == 1 - assert ( - func.__doc__ == f'foo\n\n* Will raise {ExpiredDeprecationError} as of version: 99.4\n' - ) - func = dec('foo', until='1.8')(func_no_doc) - with pytest.raises(ExpiredDeprecationError): - func() - assert func.__doc__ == f'foo\n\n* Raises {ExpiredDeprecationError} as of version: 1.8\n' - func = dec('foo', '1.2', '1.8')(func_no_doc) - with pytest.raises(ExpiredDeprecationError): - func() - assert ( - func.__doc__ == 'foo\n\n* deprecated from version: 1.2\n* Raises ' - f'{ExpiredDeprecationError} as of version: 1.8\n' - ) - func = dec('foo', '1.2', '1.8')(func_doc_long) - assert ( - func.__doc__ - == f"""\ -A docstring - -foo - -* deprecated from version: 1.2 -* Raises {ExpiredDeprecationError} as of version: 1.8 -""" - ) - with pytest.raises(ExpiredDeprecationError): - func() - - # Check different warnings and errors - func = dec('foo', warn_class=UserWarning)(func_no_doc) - with clear_and_catch_warnings(modules=[_OWN_MODULE]) as w: - warnings.simplefilter('always') - assert func() is None - assert len(w) == 1 - assert w[0].category is UserWarning - - func = dec('foo', error_class=CustomError)(func_no_doc) - with pytest.deprecated_call(): - assert func() is None - - func = dec('foo', until='1.8', error_class=CustomError)(func_no_doc) - with pytest.raises(CustomError): - func() - - -class TestDeprecatorMaker: - """Test deprecator class creation with custom warnings and errors""" - - dep_maker = staticmethod(partial(Deprecator, cmp_func)) - - def test_deprecator_maker(self): - dec = self.dep_maker(warn_class=UserWarning) - func = dec('foo')(func_no_doc) - with pytest.warns(UserWarning) as w: - # warnings.simplefilter('always') - assert func() is None - assert len(w) == 1 - - dec = self.dep_maker(error_class=CustomError) - func = dec('foo')(func_no_doc) - with pytest.deprecated_call(): - assert func() is None - - func = dec('foo', until='1.8')(func_no_doc) - with pytest.raises(CustomError): - func() diff --git a/nibabel/tests/test_dft.py b/nibabel/tests/test_dft.py deleted file mode 100644 index 6155dda83c..0000000000 --- a/nibabel/tests/test_dft.py +++ /dev/null @@ -1,122 +0,0 @@ -"""Testing dft""" - -import os -import sqlite3 -from io import BytesIO -from os.path import dirname -from os.path import join as pjoin - -from ..testing import suppress_warnings - -with suppress_warnings(): - from .. import dft - -import unittest - -import pytest - -from .. import nifti1 - -# Shield optional package imports -from ..optpkg import optional_package - -have_dicom = optional_package('pydicom')[1] -PImage, have_pil, _ = optional_package('PIL.Image') - -data_dir = pjoin(dirname(__file__), 'data') - - -def setup_module(): - if os.name == 'nt': - raise unittest.SkipTest('FUSE not available for windows, skipping dft tests') - if not have_dicom: - raise unittest.SkipTest('Need pydicom for dft tests, skipping') - - -class Test_DBclass: - """Some tests on the database manager class that don't get exercised through the API""" - - def setup_method(self): - self._db = dft._DB(fname=':memory:', verbose=False) - - def test_repr(self): - assert repr(self._db) == "" - - def test_cursor_conflict(self): - rwc = self._db.readwrite_cursor - statement = ('INSERT INTO directory (path, mtime) VALUES (?, ?)', ('/tmp', 0)) - with pytest.raises(sqlite3.IntegrityError): - # Whichever exits first will commit and make the second violate uniqueness - with rwc() as c1, rwc() as c2: - c1.execute(*statement) - c2.execute(*statement) - - -@pytest.fixture -def db(monkeypatch): - """Build a dft database in memory to avoid cross-process races - and not modify the host filesystem.""" - database = dft._DB(fname=':memory:') - monkeypatch.setattr(dft, 'DB', database) - return database - - -def test_init(db): - dft.clear_cache() - dft.update_cache(data_dir) - # Verify a second update doesn't crash - dft.update_cache(data_dir) - - -def test_study(db): - # First pass updates the cache, second pass reads it out - for base_dir in (data_dir, None): - studies = dft.get_studies(base_dir) - assert len(studies) == 1 - assert studies[0].uid == '1.3.12.2.1107.5.2.32.35119.30000010011408520750000000022' - assert studies[0].date == '20100114' - assert studies[0].time == '121314.000000' - assert studies[0].comments == 'dft study comments' - assert studies[0].patient_name == 'dft patient name' - assert studies[0].patient_id == '1234' - assert studies[0].patient_birth_date == '19800102' - assert studies[0].patient_sex == 'F' - - -def test_series(db): - studies = dft.get_studies(data_dir) - assert len(studies[0].series) == 1 - ser = studies[0].series[0] - assert ser.uid == '1.3.12.2.1107.5.2.32.35119.2010011420292594820699190.0.0.0' - assert ser.number == '12' - assert ser.description == 'CBU_DTI_64D_1A' - assert ser.rows == 256 - assert ser.columns == 256 - assert ser.bits_allocated == 16 - assert ser.bits_stored == 12 - - -def test_storage_instances(db): - studies = dft.get_studies(data_dir) - sis = studies[0].series[0].storage_instances - assert len(sis) == 2 - assert sis[0].instance_number == 1 - assert sis[1].instance_number == 2 - assert sis[0].uid == '1.3.12.2.1107.5.2.32.35119.2010011420300180088599504.0' - assert sis[1].uid == '1.3.12.2.1107.5.2.32.35119.2010011420300180088599504.1' - - -@unittest.skipUnless(have_pil, 'could not import PIL.Image') -def test_png(db): - studies = dft.get_studies(data_dir) - data = studies[0].series[0].as_png() - im = PImage.open(BytesIO(data)) - assert im.size == (256, 256) - - -def test_nifti(db): - studies = dft.get_studies(data_dir) - data = studies[0].series[0].as_nifti() - assert len(data) == 352 + 2 * 256 * 256 * 2 - h = nifti1.Nifti1Header(data[:348]) - assert h.get_data_shape() == (256, 256, 2) diff --git a/nibabel/tests/test_diff.py b/nibabel/tests/test_diff.py deleted file mode 100644 index 798a7f7b30..0000000000 --- a/nibabel/tests/test_diff.py +++ /dev/null @@ -1,80 +0,0 @@ -# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -"""Test diff""" - -from os.path import abspath, dirname -from os.path import join as pjoin - -import numpy as np - -DATA_PATH = abspath(pjoin(dirname(__file__), 'data')) - -from nibabel.cmdline.diff import are_values_different - - -def test_diff_values_int(): - large = 10**30 - assert not are_values_different(0, 0) - assert not are_values_different(1, 1) - assert not are_values_different(large, large) - assert are_values_different(0, 1) - assert are_values_different(1, 2) - assert are_values_different(1, large) - - -def test_diff_values_float(): - assert not are_values_different(0.0, 0.0) - assert not are_values_different(0.0, 0.0, 0.0) # can take more - assert not are_values_different(1.1, 1.1) - assert are_values_different(0.0, 1.1) - assert are_values_different(0.0, 0, 1.1) - assert are_values_different(1.0, 2.0) - - -def test_diff_values_mixed(): - assert are_values_different(1.0, 1) - assert are_values_different(1.0, '1') - assert are_values_different(1, '1') - assert are_values_different(1, None) - assert are_values_different(np.ndarray([0]), 'hey') - assert not are_values_different(None, None) - - -def test_diff_values_array(): - from numpy import array, inf, nan - - a_int = array([1, 2]) - a_float = a_int.astype(float) - - assert are_values_different(a_int, a_float) - assert are_values_different(a_int, a_int, a_float) - assert are_values_different(np.arange(3), np.arange(1, 4)) - assert are_values_different(np.arange(3), np.arange(4)) - assert are_values_different(np.arange(4), np.arange(4).reshape((2, 2))) - # no broadcasting should kick in - shape difference - assert are_values_different(array([1]), array([1, 1])) - assert not are_values_different(a_int, a_int) - assert not are_values_different(a_float, a_float) - - # nans - we consider them "the same" for the purpose of these comparisons - assert not are_values_different(nan, nan) - assert not are_values_different(nan, nan, nan) - assert are_values_different(nan, nan, 1) - assert are_values_different(1, nan, nan) - assert not are_values_different(array([nan, nan]), array([nan, nan])) - assert not are_values_different(array([nan, nan]), array([nan, nan]), array([nan, nan])) - assert not are_values_different(array([nan, 1]), array([nan, 1])) - assert are_values_different(array([nan, nan]), array([nan, 1])) - assert are_values_different(array([0, nan]), array([nan, 0])) - assert are_values_different(array([1, 2, 3, nan]), array([nan, 3, 5, 4])) - assert are_values_different(nan, 1.0) - assert are_values_different(array([1, 2, 3, nan]), array([3, 4, 5, nan])) - # and some inf should not be a problem - assert not are_values_different(array([0, inf]), array([0, inf])) - assert are_values_different(array([0, inf]), array([inf, 0])) - - # we will allow for types to be of different endianness but the - # same in "instantiation" type and value - assert not are_values_different(np.array(1, dtype='' - - -class TestEcatMlist(TestCase): - header_class = EcatHeader - example_file = ecat_file - - def test_mlist(self): - fid = open(self.example_file, 'rb') - hdr = self.header_class.from_fileobj(fid) - mlist = read_mlist(fid, hdr.endianness) - fid.seek(0) - fid.seek(512) - dat = fid.read(128 * 32) - dt = np.dtype([('matlist', np.int32)]) - dt = dt.newbyteorder('>') - mats = np.recarray(shape=(32, 4), dtype=dt, buf=dat) - fid.close() - # tests - assert mats['matlist'][0, 0] + mats['matlist'][0, 3] == 31 - assert get_frame_order(mlist)[0][0] == 0 - assert get_frame_order(mlist)[0][1] == 16842758.0 - # test badly ordered mlist - badordermlist = np.array( - [ - [1.68427540e07, 3.00000000e00, 1.20350000e04, 1.00000000e00], - [1.68427530e07, 1.20360000e04, 2.40680000e04, 1.00000000e00], - [1.68427550e07, 2.40690000e04, 3.61010000e04, 1.00000000e00], - [1.68427560e07, 3.61020000e04, 4.81340000e04, 1.00000000e00], - [1.68427570e07, 4.81350000e04, 6.01670000e04, 1.00000000e00], - [1.68427580e07, 6.01680000e04, 7.22000000e04, 1.00000000e00], - ] - ) - with suppress_warnings(): # STORED order - assert get_frame_order(badordermlist)[0][0] == 1 - - def test_mlist_errors(self): - fid = open(self.example_file, 'rb') - hdr = self.header_class.from_fileobj(fid) - hdr['num_frames'] = 6 - mlist = read_mlist(fid, hdr.endianness) - fid.close() - mlist = np.array( - [ - [1.68427540e07, 3.00000000e00, 1.20350000e04, 1.00000000e00], - [1.68427530e07, 1.20360000e04, 2.40680000e04, 1.00000000e00], - [1.68427550e07, 2.40690000e04, 3.61010000e04, 1.00000000e00], - [1.68427560e07, 3.61020000e04, 4.81340000e04, 1.00000000e00], - [1.68427570e07, 4.81350000e04, 6.01670000e04, 1.00000000e00], - [1.68427580e07, 6.01680000e04, 7.22000000e04, 1.00000000e00], - ] - ) - with suppress_warnings(): # STORED order - series_framenumbers = get_series_framenumbers(mlist) - # first frame stored was actually 2nd frame acquired - assert series_framenumbers[0] == 2 - order = [series_framenumbers[x] for x in sorted(series_framenumbers)] - # true series order is [2,1,3,4,5,6], note counting starts at 1 - assert order == [2, 1, 3, 4, 5, 6] - mlist[0, 0] = 0 - with suppress_warnings(): - frames_order = get_frame_order(mlist) - neworder = [frames_order[x][0] for x in sorted(frames_order)] - assert neworder == [1, 2, 3, 4, 5] - with suppress_warnings(): - with pytest.raises(OSError): - get_series_framenumbers(mlist) - - -class TestEcatSubHeader(TestCase): - header_class = EcatHeader - subhdr_class = EcatSubHeader - example_file = ecat_file - fid = open(example_file, 'rb') - hdr = header_class.from_fileobj(fid) - mlist = read_mlist(fid, hdr.endianness) - subhdr = subhdr_class(hdr, mlist, fid) - - def test_subheader_size(self): - assert self.subhdr_class._subhdrdtype.itemsize == 510 - - def test_subheader(self): - assert self.subhdr.get_shape() == (10, 10, 3) - assert self.subhdr.get_nframes() == 1 - assert self.subhdr.get_nframes() == len(self.subhdr.subheaders) - assert self.subhdr._check_affines() is True - assert_array_almost_equal( - np.diag(self.subhdr.get_frame_affine()), np.array([2.20241979, 2.20241979, 3.125, 1.0]) - ) - assert self.subhdr.get_zooms()[0] == 2.20241978764534 - assert self.subhdr.get_zooms()[2] == 3.125 - assert self.subhdr._get_data_dtype(0) == np.int16 - # assert_equal(self.subhdr._get_frame_offset(), 1024) - assert self.subhdr._get_frame_offset() == 1536 - dat = self.subhdr.raw_data_from_fileobj() - assert dat.shape == self.subhdr.get_shape() - assert self.subhdr.subheaders[0]['scale_factor'].item() == 1.0 - ecat_calib_factor = self.hdr['ecat_calibration_factor'] - assert ecat_calib_factor == 25007614.0 - - -class TestEcatImage(TestCase): - image_class = EcatImage - example_file = ecat_file - img = image_class.load(example_file) - - def test_file(self): - assert Path(self.img.file_map['header'].filename) == Path(self.example_file) - assert Path(self.img.file_map['image'].filename) == Path(self.example_file) - - def test_save(self): - tmp_file = 'tinypet_tmp.v' - with InTemporaryDirectory(): - self.img.to_filename(tmp_file) - other = self.image_class.load(tmp_file) - assert_array_equal(self.img.get_fdata(), other.get_fdata()) - # Delete object holding reference to temporary file to make Windows - # happier. - del other - - def test_data(self): - dat = self.img.get_fdata() - assert dat.shape == self.img.shape - frame = self.img.get_frame(0) - assert_array_equal(frame, dat[:, :, :, 0]) - - def test_array_proxy(self): - # Get the cached data copy - dat = self.img.get_fdata() - # Make a new one to test arrayproxy - img = self.image_class.load(self.example_file) - data_prox = img.dataobj - data2 = np.array(data_prox) - assert_array_equal(data2, dat) - # Check it rereads - data3 = np.array(data_prox) - assert_array_equal(data3, dat) - - def test_array_proxy_slicing(self): - # Test slicing of array proxy - arr = self.img.get_fdata() - prox = self.img.dataobj - assert prox.is_proxy - for sliceobj in slicer_samples(self.img.shape): - assert_array_equal(arr[sliceobj], prox[sliceobj]) - - def test_isolation(self): - # Test image isolated from external changes to affine - img_klass = self.image_class - arr, aff, hdr, sub_hdr, mlist = ( - self.img.get_fdata(), - self.img.affine, - self.img.header, - self.img.get_subheaders(), - self.img.get_mlist(), - ) - img = img_klass(arr, aff, hdr, sub_hdr, mlist) - assert_array_equal(img.affine, aff) - aff[0, 0] = 99 - assert not np.all(img.affine == aff) - - def test_float_affine(self): - # Check affines get converted to float - img_klass = self.image_class - arr, aff, hdr, sub_hdr, mlist = ( - self.img.get_fdata(), - self.img.affine, - self.img.header, - self.img.get_subheaders(), - self.img.get_mlist(), - ) - img = img_klass(arr, aff.astype(np.float32), hdr, sub_hdr, mlist) - assert img.affine.dtype == np.dtype(np.float64) - img = img_klass(arr, aff.astype(np.int16), hdr, sub_hdr, mlist) - assert img.affine.dtype == np.dtype(np.float64) - - def test_data_regression(self): - # Test whether data read has changed since 1.3.0 - # These values came from reading the example image using nibabel 1.3.0 - vals = dict(max=248750736458.0, min=1125342630.0, mean=117907565661.46666) - data = self.img.get_fdata() - assert data.max() == vals['max'] - assert data.min() == vals['min'] - assert_array_almost_equal(data.mean(), vals['mean']) - - def test_mlist_regression(self): - # Test mlist is as same as for nibabel 1.3.0 - assert_array_equal(self.img.get_mlist(), [[16842758, 3, 3011, 1]]) diff --git a/nibabel/tests/test_ecat_data.py b/nibabel/tests/test_ecat_data.py deleted file mode 100644 index 427645b92a..0000000000 --- a/nibabel/tests/test_ecat_data.py +++ /dev/null @@ -1,60 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Test we can correctly import example ECAT files""" - -import os -from os.path import join as pjoin - -import numpy as np -from numpy.testing import assert_almost_equal - -from ..ecat import load -from .nibabel_data import get_nibabel_data, needs_nibabel_data - -ECAT_TEST_PATH = pjoin(get_nibabel_data(), 'nipy-ecattest') - - -class TestNegatives: - opener = staticmethod(load) - example_params = dict( - fname=os.path.join(ECAT_TEST_PATH, 'ECAT7_testcaste_neg_values.v'), - shape=(256, 256, 63, 1), - type=np.int16, - # These values from freec64 - min=-0.00061576, - max=0.19215, - mean=0.04933, - # unit: 1/cm - ) - - @needs_nibabel_data('nipy-ecattest') - def test_load(self): - # Check highest level load of minc works - img = self.opener(self.example_params['fname']) - assert img.shape == self.example_params['shape'] - assert img.get_data_dtype(0).type == self.example_params['type'] - # Check correspondence of data and recorded shape - data = img.get_fdata() - assert data.shape == self.example_params['shape'] - # min, max, mean values from given parameters - assert_almost_equal(data.min(), self.example_params['min'], 4) - assert_almost_equal(data.max(), self.example_params['max'], 4) - assert_almost_equal(data.mean(), self.example_params['mean'], 4) - - -class TestMultiframe(TestNegatives): - example_params = dict( - fname=os.path.join(ECAT_TEST_PATH, 'ECAT7_testcase_multiframe.v'), - shape=(256, 256, 207, 3), - type=np.int16, - # Zeroed out image - min=0.0, - max=29170.67905, - mean=121.454, - ) diff --git a/nibabel/tests/test_endiancodes.py b/nibabel/tests/test_endiancodes.py deleted file mode 100644 index ce460efbb3..0000000000 --- a/nibabel/tests/test_endiancodes.py +++ /dev/null @@ -1,36 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Tests for endiancodes module""" - -import sys - -from ..volumeutils import endian_codes, native_code, swapped_code - - -def test_native_swapped(): - native_is_le = sys.byteorder == 'little' - if native_is_le: - assert (native_code, swapped_code) == ('<', '>') - else: - assert (native_code, swapped_code) == ('>', '<') - - -def test_to_numpy(): - if sys.byteorder == 'little': - assert endian_codes['native'] == '<' - assert endian_codes['swapped'] == '>' - else: - assert endian_codes['native'] == '>' - assert endian_codes['swapped'] == '<' - assert endian_codes['native'] == endian_codes['='] - assert endian_codes['big'] == '>' - for code in ('little', '<', 'l', 'L', 'le'): - assert endian_codes[code] == '<' - for code in ('big', '>', 'b', 'B', 'be'): - assert endian_codes[code] == '>' diff --git a/nibabel/tests/test_environment.py b/nibabel/tests/test_environment.py deleted file mode 100644 index aa58d9b8e0..0000000000 --- a/nibabel/tests/test_environment.py +++ /dev/null @@ -1,62 +0,0 @@ -"""Testing environment settings""" - -import os -from os import environ as env -from os.path import abspath -from os.path import join as pjoin - -import pytest - -from .. import environment as nibe - -DATA_KEY = 'NIPY_DATA_PATH' -USER_KEY = 'NIPY_USER_DIR' - - -@pytest.fixture -def with_environment(request): - """Setup test environment for some functions that are tested - in this module. In particular this functions stores attributes - and other things that we need to stub in some test functions. - This needs to be done on a function level and not module level because - each testfunction needs a pristine environment. - """ - GIVEN_ENV = {} - GIVEN_ENV['env'] = env.copy() - yield - """Restore things that were remembered by the setup_environment function """ - orig_env = GIVEN_ENV['env'] - # Pull keys out into list to avoid altering dictionary during iteration, - # causing python 3 error - for key in list(env.keys()): - if key not in orig_env: - del env[key] - env.update(orig_env) - - -def test_nipy_home(): - # Test logic for nipy home directory - assert nibe.get_home_dir() == os.path.expanduser('~') - - -def test_user_dir(with_environment): - if USER_KEY in env: - del env[USER_KEY] - home_dir = nibe.get_home_dir() - if os.name == 'posix': - exp = pjoin(home_dir, '.nipy') - else: - exp = pjoin(home_dir, '_nipy') - assert exp == nibe.get_nipy_user_dir() - env[USER_KEY] = '/a/path' - assert abspath('/a/path') == nibe.get_nipy_user_dir() - - -def test_sys_dir(): - sys_dir = nibe.get_nipy_system_dir() - if os.name == 'nt': - assert sys_dir == r'C:\etc\nipy' - elif os.name == 'posix': - assert sys_dir == r'/etc/nipy' - else: - assert sys_dir is None diff --git a/nibabel/tests/test_euler.py b/nibabel/tests/test_euler.py deleted file mode 100644 index 4d251a16e3..0000000000 --- a/nibabel/tests/test_euler.py +++ /dev/null @@ -1,187 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Tests for Euler angles""" - -import math - -import numpy as np -import pytest -from numpy import pi -from numpy.testing import assert_array_almost_equal, assert_array_equal - -from .. import eulerangles as nea -from .. import quaternions as nq - -FLOAT_EPS = np.finfo(np.float64).eps - -# Example rotations """ -params = np.arange(-pi * 2, pi * 2.5, pi / 2) -eg_rots = [(x, y, z) for x in params for y in params for z in params] - - -def x_only(x): - cosx = np.cos(x) - sinx = np.sin(x) - return np.array( - [ - [1, 0, 0], - [0, cosx, -sinx], - [0, sinx, cosx], - ] - ) - - -def y_only(y): - cosy = np.cos(y) - siny = np.sin(y) - return np.array( - [ - [cosy, 0, siny], - [0, 1, 0], - [-siny, 0, cosy], - ] - ) - - -def z_only(z): - cosz = np.cos(z) - sinz = np.sin(z) - return np.array( - [ - [cosz, -sinz, 0], - [sinz, cosz, 0], - [0, 0, 1], - ] - ) - - -def sympy_euler(z, y, x): - # The whole matrix formula for z,y,x rotations from Sympy - cos = math.cos - sin = math.sin - # the following copy / pasted from Sympy - see derivations subdirectory - return [ - [cos(y) * cos(z), -cos(y) * sin(z), sin(y)], - [ - cos(x) * sin(z) + cos(z) * sin(x) * sin(y), - cos(x) * cos(z) - sin(x) * sin(y) * sin(z), - -cos(y) * sin(x), - ], - [ - sin(x) * sin(z) - cos(x) * cos(z) * sin(y), - cos(z) * sin(x) + cos(x) * sin(y) * sin(z), - cos(x) * cos(y), - ], - ] - - -def is_valid_rotation(M): - if not np.allclose(np.linalg.det(M), 1): - return False - return np.allclose(np.eye(3), np.dot(M, M.T)) - - -def test_basic_euler(): - # some example rotations, in radians - zr = 0.05 - yr = -0.4 - xr = 0.2 - # Rotation matrix composing the three rotations - M = nea.euler2mat(zr, yr, xr) - # Corresponding individual rotation matrices - M1 = nea.euler2mat(zr) - M2 = nea.euler2mat(0, yr) - M3 = nea.euler2mat(0, 0, xr) - # which are all valid rotation matrices - assert is_valid_rotation(M) - assert is_valid_rotation(M1) - assert is_valid_rotation(M2) - assert is_valid_rotation(M3) - # Full matrix is composition of three individual matrices - assert np.allclose(M, np.dot(M3, np.dot(M2, M1))) - # Rotations can be specified with named args, default 0 - assert np.all(nea.euler2mat(zr) == nea.euler2mat(z=zr)) - assert np.all(nea.euler2mat(0, yr) == nea.euler2mat(y=yr)) - assert np.all(nea.euler2mat(0, 0, xr) == nea.euler2mat(x=xr)) - # Applying an opposite rotation same as inverse (the inverse is - # the same as the transpose, but just for clarity) - assert np.allclose(nea.euler2mat(x=-xr), np.linalg.inv(nea.euler2mat(x=xr))) - - -def test_euler_mat_1(): - M = nea.euler2mat() - assert_array_equal(M, np.eye(3)) - - -@pytest.mark.parametrize(('x', 'y', 'z'), eg_rots) -def test_euler_mat_2(x, y, z): - M1 = nea.euler2mat(z, y, x) - M2 = sympy_euler(z, y, x) - assert_array_almost_equal(M1, M2) - M3 = np.dot(x_only(x), np.dot(y_only(y), z_only(z))) - assert_array_almost_equal(M1, M3) - zp, yp, xp = nea.mat2euler(M1) - # The parameters may not be the same as input, but they give the - # same rotation matrix - M4 = nea.euler2mat(zp, yp, xp) - assert_array_almost_equal(M1, M4) - - -def sympy_euler2quat(z=0, y=0, x=0): - # direct formula for z,y,x quaternion rotations using sympy - # see derivations subfolder - cos = math.cos - sin = math.sin - # the following copy / pasted from Sympy output - return ( - cos(0.5 * x) * cos(0.5 * y) * cos(0.5 * z) - sin(0.5 * x) * sin(0.5 * y) * sin(0.5 * z), - cos(0.5 * x) * sin(0.5 * y) * sin(0.5 * z) + cos(0.5 * y) * cos(0.5 * z) * sin(0.5 * x), - cos(0.5 * x) * cos(0.5 * z) * sin(0.5 * y) - cos(0.5 * y) * sin(0.5 * x) * sin(0.5 * z), - cos(0.5 * x) * cos(0.5 * y) * sin(0.5 * z) + cos(0.5 * z) * sin(0.5 * x) * sin(0.5 * y), - ) - - -def crude_mat2euler(M): - """The simplest possible - ignoring atan2 instability""" - r11, r12, r13, r21, r22, r23, r31, r32, r33 = M.flat - return math.atan2(-r12, r11), math.asin(r13), math.atan2(-r23, r33) - - -def test_euler_instability(): - # Test for numerical errors in mat2euler - # problems arise for cos(y) near 0 - po2 = pi / 2 - zyx = po2, po2, po2 - M = nea.euler2mat(*zyx) - # Round trip - M_back = nea.euler2mat(*nea.mat2euler(M)) - assert np.allclose(M, M_back) - # disturb matrix slightly - M_e = M - FLOAT_EPS - # round trip to test - OK - M_e_back = nea.euler2mat(*nea.mat2euler(M_e)) - assert np.allclose(M_e, M_e_back) - # not so with crude routine - M_e_back = nea.euler2mat(*crude_mat2euler(M_e)) - assert not np.allclose(M_e, M_e_back) - - -@pytest.mark.parametrize(('x', 'y', 'z'), eg_rots) -def test_quats(x, y, z): - M1 = nea.euler2mat(z, y, x) - quatM = nq.mat2quat(M1) - quat = nea.euler2quat(z, y, x) - assert nq.nearly_equivalent(quatM, quat) - quatS = sympy_euler2quat(z, y, x) - assert nq.nearly_equivalent(quat, quatS) - zp, yp, xp = nea.quat2euler(quat) - # The parameters may not be the same as input, but they give the - # same rotation matrix - M2 = nea.euler2mat(zp, yp, xp) - assert_array_almost_equal(M1, M2) diff --git a/nibabel/tests/test_filebasedimages.py b/nibabel/tests/test_filebasedimages.py deleted file mode 100644 index 7d162c0917..0000000000 --- a/nibabel/tests/test_filebasedimages.py +++ /dev/null @@ -1,144 +0,0 @@ -"""Testing filebasedimages module""" - -import warnings -from itertools import product - -import numpy as np -import pytest - -from ..filebasedimages import FileBasedHeader, FileBasedImage, SerializableImage -from .test_image_api import GenericImageAPI, SerializeMixin - - -class FBNumpyImage(FileBasedImage): - header_class = FileBasedHeader - valid_exts = ('.npy',) - files_types = (('image', '.npy'),) - - def __init__(self, arr, header=None, extra=None, file_map=None): - super().__init__(header, extra, file_map) - self.arr = arr - - @property - def shape(self): - return self.arr.shape - - def get_data(self): - warnings.warn('Deprecated', DeprecationWarning) - return self.arr - - @property - def dataobj(self): - return self.arr - - def get_fdata(self): - return self.arr.astype(np.float64) - - @classmethod - def from_file_map(klass, file_map): - with file_map['image'].get_prepare_fileobj('rb') as fobj: - arr = np.load(fobj) - return klass(arr) - - def to_file_map(self, file_map=None): - file_map = self.file_map if file_map is None else file_map - with file_map['image'].get_prepare_fileobj('wb') as fobj: - np.save(fobj, self.arr) - - def get_data_dtype(self): - return self.arr.dtype - - def set_data_dtype(self, dtype): - self.arr = self.arr.astype(dtype) - - -class SerializableNumpyImage(FBNumpyImage, SerializableImage): - pass - - -class TestFBImageAPI(GenericImageAPI): - """Validation for FileBasedImage instances""" - - # A callable returning an image from ``image_maker(data, header)`` - image_maker = FBNumpyImage - # A callable returning a header from ``header_maker()`` - header_maker = FileBasedHeader - # Example shapes for created images - example_shapes = ((2,), (2, 3), (2, 3, 4), (2, 3, 4, 5)) - example_dtypes = (np.int8, np.uint16, np.int32, np.float32) - can_save = True - standard_extension = '.npy' - - def make_imaker(self, arr, header=None): - return lambda: self.image_maker(arr, header) - - def obj_params(self): - # Create new images - for shape, dtype in product(self.example_shapes, self.example_dtypes): - arr = np.arange(np.prod(shape), dtype=dtype).reshape(shape) - hdr = self.header_maker() - func = self.make_imaker(arr.copy(), hdr) - params = dict(dtype=dtype, data=arr, shape=shape, is_proxy=False) - yield func, params - - -class TestSerializableImageAPI(TestFBImageAPI, SerializeMixin): - image_maker = SerializableNumpyImage - - @staticmethod - def _header_eq(header_a, header_b): - """FileBasedHeader is an abstract class, so __eq__ is undefined. - Checking for the same header type is sufficient, here.""" - return type(header_a) == type(header_b) == FileBasedHeader - - -def test_filebased_header(): - # Test stuff about the default FileBasedHeader - - class H(FileBasedHeader): - def __init__(self, seq=None): - if seq is None: - seq = [] - self.a_list = list(seq) - - in_list = [1, 3, 2] - hdr = H(in_list) - hdr_c = hdr.copy() - assert hdr_c.a_list == hdr.a_list - # Copy is independent of original - hdr_c.a_list[0] = 99 - assert hdr_c.a_list != hdr.a_list - # From header does a copy - hdr2 = H.from_header(hdr) - assert isinstance(hdr2, H) - assert hdr2.a_list == hdr.a_list - hdr2.a_list[0] = 42 - assert hdr2.a_list != hdr.a_list - # Default header input to from_heder gives new empty header - hdr3 = H.from_header() - assert isinstance(hdr3, H) - assert hdr3.a_list == [] - hdr4 = H.from_header(None) - assert isinstance(hdr4, H) - assert hdr4.a_list == [] - - -class MultipartNumpyImage(FBNumpyImage): - # We won't actually try to write these out, just need to test an edge case - files_types = (('header', '.hdr'), ('image', '.npy')) - - -class SerializableMPNumpyImage(MultipartNumpyImage, SerializableImage): - pass - - -def test_multifile_stream_failure(): - shape = (2, 3, 4) - arr = np.arange(np.prod(shape), dtype=np.float32).reshape(shape) - img = SerializableMPNumpyImage(arr) - with pytest.raises(NotImplementedError): - img.to_bytes() - img = SerializableNumpyImage(arr) - bstr = img.to_bytes() - with pytest.raises(NotImplementedError): - SerializableMPNumpyImage.from_bytes(bstr) diff --git a/nibabel/tests/test_filehandles.py b/nibabel/tests/test_filehandles.py deleted file mode 100644 index c985d35440..0000000000 --- a/nibabel/tests/test_filehandles.py +++ /dev/null @@ -1,39 +0,0 @@ -""" -Check that loading an image does not use up filehandles. -""" - -import shutil -import unittest -from os.path import join as pjoin -from tempfile import mkdtemp - -import numpy as np - -try: - import resource as res -except ImportError: - # Not on Unix, guess limit - SOFT_LIMIT = 512 -else: - SOFT_LIMIT, HARD_LIMIT = res.getrlimit(res.RLIMIT_NOFILE) - -from ..loadsave import load, save -from ..nifti1 import Nifti1Image - - -@unittest.skipIf(SOFT_LIMIT > 4900, 'It would take too long to test filehandles') -def test_multiload(): - # Make a tiny image, save, load many times. If we are leaking filehandles, - # this will cause us to run out and generate an error - N = SOFT_LIMIT + 100 - arr = np.arange(24, dtype='int32').reshape((2, 3, 4)) - img = Nifti1Image(arr, np.eye(4)) - imgs = [] - try: - tmpdir = mkdtemp() - fname = pjoin(tmpdir, 'test.img') - save(img, fname) - imgs.extend(load(fname) for _ in range(N)) - finally: - del img, imgs - shutil.rmtree(tmpdir) diff --git a/nibabel/tests/test_fileholders.py b/nibabel/tests/test_fileholders.py deleted file mode 100644 index 83fe75aecc..0000000000 --- a/nibabel/tests/test_fileholders.py +++ /dev/null @@ -1,52 +0,0 @@ -"""Testing fileholders""" - -from io import BytesIO - -from ..fileholders import FileHolder - - -def test_init(): - fh = FileHolder('a_fname') - assert fh.filename == 'a_fname' - assert fh.fileobj is None - assert fh.pos == 0 - sio0 = BytesIO() - fh = FileHolder('a_test', sio0) - assert fh.filename == 'a_test' - assert fh.fileobj is sio0 - assert fh.pos == 0 - fh = FileHolder('a_test_2', sio0, 3) - assert fh.filename == 'a_test_2' - assert fh.fileobj is sio0 - assert fh.pos == 3 - - -def test_same_file_as(): - fh = FileHolder('a_fname') - assert fh.same_file_as(fh) - fh2 = FileHolder('a_test') - assert not fh.same_file_as(fh2) - sio0 = BytesIO() - fh3 = FileHolder('a_fname', sio0) - fh4 = FileHolder('a_fname', sio0) - assert fh3.same_file_as(fh4) - assert not fh3.same_file_as(fh) - fh5 = FileHolder(fileobj=sio0) - fh6 = FileHolder(fileobj=sio0) - assert fh5.same_file_as(fh6) - # Not if the filename is the same - assert not fh5.same_file_as(fh3) - # pos doesn't matter - fh4_again = FileHolder('a_fname', sio0, pos=4) - assert fh3.same_file_as(fh4_again) - - -def test_file_like(): - # Test returning file object or filename - fh = FileHolder('a_fname') - assert fh.file_like == 'a_fname' - bio = BytesIO() - fh = FileHolder(fileobj=bio) - assert fh.file_like is bio - fh = FileHolder('a_fname', fileobj=bio) - assert fh.file_like is bio diff --git a/nibabel/tests/test_filename_parser.py b/nibabel/tests/test_filename_parser.py deleted file mode 100644 index 4e53cb2e5d..0000000000 --- a/nibabel/tests/test_filename_parser.py +++ /dev/null @@ -1,149 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Tests for filename container""" - -import pathlib - -import pytest - -from ..filename_parser import ( - TypesFilenamesError, - _stringify_path, - parse_filename, - splitext_addext, - types_filenames, -) - - -def test_filenames(): - types_exts = (('image', '.img'), ('header', '.hdr')) - for t_fname in ('test.img', 'test.hdr', 'test', 'test.'): - tfns = types_filenames(t_fname, types_exts) - assert tfns == {'image': 'test.img', 'header': 'test.hdr'} - # enforcing extensions raises an error for bad extension - with pytest.raises(TypesFilenamesError): - types_filenames('test.funny', types_exts) - # If not enforcing extensions, it does the best job it can, - # assuming the passed filename is for the first type (in this case - # 'image') - tfns = types_filenames('test.funny', types_exts, enforce_extensions=False) - assert tfns == {'header': 'test.hdr', 'image': 'test.funny'} - # .gz and .bz2 suffixes to extensions, by default, are removed - # before extension checking etc, and then put back onto every - # returned filename. - tfns = types_filenames('test.img.gz', types_exts) - assert tfns == {'header': 'test.hdr.gz', 'image': 'test.img.gz'} - tfns = types_filenames('test.img.bz2', types_exts) - assert tfns == {'header': 'test.hdr.bz2', 'image': 'test.img.bz2'} - # of course, if we don't know about e.g. gz, and enforce_extensions - # is on, we get an error - with pytest.raises(TypesFilenamesError): - types_filenames('test.img.gz', types_exts, ()) - # if we don't know about .gz extension, and not enforcing, then we - # get something a bit odd - tfns = types_filenames( - 'test.img.gz', types_exts, trailing_suffixes=(), enforce_extensions=False - ) - assert tfns == {'header': 'test.img.hdr', 'image': 'test.img.gz'} - # the suffixes we remove and replaces can be any suffixes. - tfns = types_filenames('test.img.bzr', types_exts, ('.bzr',)) - assert tfns == {'header': 'test.hdr.bzr', 'image': 'test.img.bzr'} - # If we specifically pass the remove / replace suffixes, then we - # don't remove / replace the .gz and .bz2, unless they are passed - # specifically. - tfns = types_filenames( - 'test.img.bzr', types_exts, trailing_suffixes=('.bzr',), enforce_extensions=False - ) - assert tfns == {'header': 'test.hdr.bzr', 'image': 'test.img.bzr'} - # but, just .gz or .bz2 as extension gives an error, if enforcing is on - with pytest.raises(TypesFilenamesError): - types_filenames('test.gz', types_exts) - with pytest.raises(TypesFilenamesError): - types_filenames('test.bz2', types_exts) - # if enforcing is off, it tries to work out what the other files - # should be assuming the passed filename is of the first input type - tfns = types_filenames('test.gz', types_exts, enforce_extensions=False) - assert tfns == {'image': 'test.gz', 'header': 'test.hdr.gz'} - # case (in)sensitivity, and effect of uppercase, lowercase - tfns = types_filenames('test.IMG', types_exts) - assert tfns == {'image': 'test.IMG', 'header': 'test.HDR'} - tfns = types_filenames('test.img', (('image', '.IMG'), ('header', '.HDR'))) - assert tfns == {'header': 'test.hdr', 'image': 'test.img'} - tfns = types_filenames('test.IMG.Gz', types_exts) - assert tfns == {'image': 'test.IMG.Gz', 'header': 'test.HDR.Gz'} - - -def test_parse_filename(): - types_exts = (('t1', 'ext1'), ('t2', 'ext2')) - exp_in_outs = ( - (('/path/fname.funny', ()), ('/path/fname', '.funny', None, None)), - (('/path/fnameext2', ()), ('/path/fname', 'ext2', None, 't2')), - (('/path/fnameext2', ('.gz',)), ('/path/fname', 'ext2', None, 't2')), - (('/path/fnameext2.gz', ('.gz',)), ('/path/fname', 'ext2', '.gz', 't2')), - ) - for inps, exps in exp_in_outs: - pth, sufs = inps - res = parse_filename(pth, types_exts, sufs) - assert res == exps - upth = pth.upper() - uexps = (exps[0].upper(), exps[1].upper(), exps[2].upper() if exps[2] else None, exps[3]) - res = parse_filename(upth, types_exts, sufs) - assert res == uexps - # test case sensitivity - res = parse_filename( - '/path/fnameext2.GZ', types_exts, ('.gz',), False - ) # case insensitive again - assert res == ('/path/fname', 'ext2', '.GZ', 't2') - res = parse_filename('/path/fnameext2.GZ', types_exts, ('.gz',), True) # case sensitive - assert res == ('/path/fnameext2', '.GZ', None, None) - res = parse_filename('/path/fnameEXT2.gz', types_exts, ('.gz',), False) # case insensitive - assert res == ('/path/fname', 'EXT2', '.gz', 't2') - res = parse_filename('/path/fnameEXT2.gz', types_exts, ('.gz',), True) # case sensitive - assert res == ('/path/fnameEXT2', '', '.gz', None) - - -def test_splitext_addext(): - res = splitext_addext('fname.ext.gz') - assert res == ('fname', '.ext', '.gz') - res = splitext_addext('fname.ext') - assert res == ('fname', '.ext', '') - res = splitext_addext('fname.ext.foo', ('.foo', '.bar')) - assert res == ('fname', '.ext', '.foo') - res = splitext_addext('fname.ext.FOO', ('.foo', '.bar')) - assert res == ('fname', '.ext', '.FOO') - # case sensitive - res = splitext_addext('fname.ext.FOO', ('.foo', '.bar'), True) - assert res == ('fname.ext', '.FOO', '') - # edge cases - res = splitext_addext('.nii') - assert res == ('', '.nii', '') - res = splitext_addext('...nii') - assert res == ('..', '.nii', '') - res = splitext_addext('.') - assert res == ('.', '', '') - res = splitext_addext('..') - assert res == ('..', '', '') - res = splitext_addext('...') - assert res == ('...', '', '') - - -def test__stringify_path(): - res = _stringify_path('fname.ext.gz') - assert res == 'fname.ext.gz' - res = _stringify_path(pathlib.Path('fname.ext.gz')) - assert res == 'fname.ext.gz' - - home = pathlib.Path.home().as_posix() - res = _stringify_path(pathlib.Path('~/fname.ext.gz')) - assert res == f'{home}/fname.ext.gz' - - res = _stringify_path(pathlib.Path('./fname.ext.gz')) - assert res == 'fname.ext.gz' - res = _stringify_path(pathlib.Path('../fname.ext.gz')) - assert res == '../fname.ext.gz' diff --git a/nibabel/tests/test_files_interface.py b/nibabel/tests/test_files_interface.py deleted file mode 100644 index b3562b6083..0000000000 --- a/nibabel/tests/test_files_interface.py +++ /dev/null @@ -1,109 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Testing filesets - a draft""" - -from io import BytesIO - -import numpy as np -import pytest -from numpy.testing import assert_array_equal - -from .. import MGHImage, Nifti1Image, Nifti1Pair, all_image_classes -from ..fileholders import FileHolderError -from ..spatialimages import SpatialImage - - -def test_files_spatialimages(): - # test files creation in image classes - arr = np.zeros((2, 3, 4)) - aff = np.eye(4) - klasses = [ - klass for klass in all_image_classes if klass.rw and issubclass(klass, SpatialImage) - ] - for klass in klasses: - file_map = klass.make_file_map() - for value in file_map.values(): - assert value.filename is None - assert value.fileobj is None - assert value.pos == 0 - # If we can't create new images in memory without loading, bail here - if not klass.makeable: - continue - # MGHImage accepts only a few datatypes - # so we force a type change to float32 - if klass == MGHImage: - img = klass(arr.astype(np.float32), aff) - else: - img = klass(arr, aff) - for value in img.file_map.values(): - assert value.filename is None - assert value.fileobj is None - assert value.pos == 0 - - -def test_files_interface(): - # test high-level interface to files mapping - arr = np.zeros((2, 3, 4)) - aff = np.eye(4) - img = Nifti1Image(arr, aff) - # single image - img.set_filename('test') - assert img.get_filename() == 'test.nii' - assert img.file_map['image'].filename == 'test.nii' - with pytest.raises(KeyError): - img.file_map['header'] - # pair - note new class - img = Nifti1Pair(arr, aff) - img.set_filename('test') - assert img.get_filename() == 'test.img' - assert img.file_map['image'].filename == 'test.img' - assert img.file_map['header'].filename == 'test.hdr' - # fileobjs - single image - img = Nifti1Image(arr, aff) - img.file_map['image'].fileobj = BytesIO() - img.to_file_map() # saves to files - img2 = Nifti1Image.from_file_map(img.file_map) - # img still has correct data - assert_array_equal(img2.get_fdata(), img.get_fdata()) - # fileobjs - pair - img = Nifti1Pair(arr, aff) - img.file_map['image'].fileobj = BytesIO() - # no header yet - with pytest.raises(FileHolderError): - img.to_file_map() - img.file_map['header'].fileobj = BytesIO() - img.to_file_map() # saves to files - img2 = Nifti1Pair.from_file_map(img.file_map) - # img still has correct data - assert_array_equal(img2.get_fdata(), img.get_fdata()) - - -def test_round_trip_spatialimages(): - # write an image to files - data = np.arange(24, dtype='i4').reshape((2, 3, 4)) - aff = np.eye(4) - klasses = [ - klass - for klass in all_image_classes - if klass.rw and klass.makeable and issubclass(klass, SpatialImage) - ] - for klass in klasses: - file_map = klass.make_file_map() - for key in file_map: - file_map[key].fileobj = BytesIO() - img = klass(data, aff) - img.file_map = file_map - img.to_file_map() - # read it back again from the written files - img2 = klass.from_file_map(file_map) - assert_array_equal(img2.get_fdata(), data) - # write, read it again - img2.to_file_map() - img3 = klass.from_file_map(file_map) - assert_array_equal(img3.get_fdata(), data) diff --git a/nibabel/tests/test_fileslice.py b/nibabel/tests/test_fileslice.py deleted file mode 100644 index ae842217ff..0000000000 --- a/nibabel/tests/test_fileslice.py +++ /dev/null @@ -1,807 +0,0 @@ -"""Test slicing of file-like objects""" - -import time -from functools import partial -from io import BytesIO -from itertools import product -from threading import Lock, Thread - -import numpy as np -import pytest -from numpy.testing import assert_array_equal - -from ..fileslice import ( - _positive_slice, - _simple_fileslice, - calc_slicedefs, - canonical_slicers, - fileslice, - fill_slicer, - is_fancy, - optimize_read_slicers, - optimize_slicer, - predict_shape, - read_segments, - slice2len, - slice2outax, - slicers2segments, - strided_scalar, - threshold_heuristic, -) - - -def _check_slice(sliceobj): - # Fancy indexing always returns a copy, basic indexing returns a view - a = np.arange(100).reshape((10, 10)) - b = a[sliceobj] - if np.isscalar(b): - return # Can't check - # Check if this is a view - a[:] = 99 - b_is_view = np.all(b == 99) - assert (not is_fancy(sliceobj)) == b_is_view - - -def test_is_fancy(): - slices = (2, [2], [2, 3], Ellipsis, np.array((2, 3))) - for slice0 in slices: - _check_slice(slice0) - _check_slice((slice0,)) # tuple is same - # Double ellipsis illegal in np 1.12dev - set up check for that case - maybe_bad = slice0 is Ellipsis - for slice1 in slices: - if maybe_bad and slice1 is Ellipsis: - continue - _check_slice((slice0, slice1)) - assert not is_fancy((None,)) - assert not is_fancy((None, 1)) - assert not is_fancy((1, None)) - # Check that actual False returned (rather than falsey) - assert is_fancy(1) is False - - -def test_canonical_slicers(): - # Check transformation of sliceobj into canonical form - slicers = (slice(None), slice(9), slice(0, 9), slice(1, 10), slice(1, 10, 2), 2, np.array(2)) - - shape = (10, 10) - for slice0 in slicers: - assert canonical_slicers((slice0,), shape) == (slice0, slice(None)) - for slice1 in slicers: - sliceobj = (slice0, slice1) - assert canonical_slicers(sliceobj, shape) == sliceobj - assert canonical_slicers(sliceobj, shape + (2, 3, 4)) == sliceobj + (slice(None),) * 3 - assert canonical_slicers(sliceobj * 3, shape * 3) == sliceobj * 3 - # Check None passes through - assert canonical_slicers(sliceobj + (None,), shape) == sliceobj + (None,) - assert canonical_slicers((None,) + sliceobj, shape) == (None,) + sliceobj - assert canonical_slicers((None,) + sliceobj + (None,), shape) == (None,) + sliceobj + ( - None, - ) - # Check Ellipsis - assert canonical_slicers((Ellipsis,), shape) == (slice(None), slice(None)) - assert canonical_slicers((Ellipsis, None), shape) == (slice(None), slice(None), None) - assert canonical_slicers((Ellipsis, 1), shape) == (slice(None), 1) - assert canonical_slicers((1, Ellipsis), shape) == (1, slice(None)) - # Ellipsis at end does nothing - assert canonical_slicers((1, 1, Ellipsis), shape) == (1, 1) - assert canonical_slicers((1, Ellipsis, 2), (10, 1, 2, 3, 11)) == ( - 1, - slice(None), - slice(None), - slice(None), - 2, - ) - with pytest.raises(ValueError): - canonical_slicers((Ellipsis, 1, Ellipsis), (2, 3, 4, 5)) - # Check full slices get expanded - for slice0 in (slice(10), slice(0, 10), slice(0, 10, 1)): - assert canonical_slicers((slice0, 1), shape) == (slice(None), 1) - for slice0 in (slice(10), slice(0, 10), slice(0, 10, 1)): - assert canonical_slicers((slice0, 1), shape) == (slice(None), 1) - assert canonical_slicers((1, slice0), shape) == (1, slice(None)) - # Check ints etc get parsed through to tuples - assert canonical_slicers(1, shape) == (1, slice(None)) - assert canonical_slicers(slice(None), shape) == (slice(None), slice(None)) - # Check fancy indexing raises error - with pytest.raises(ValueError): - canonical_slicers((np.array([1]), 1), shape) - with pytest.raises(ValueError): - canonical_slicers((1, np.array([1])), shape) - # Check out of range integer raises error - with pytest.raises(ValueError): - canonical_slicers((10,), shape) - with pytest.raises(ValueError): - canonical_slicers((1, 10), shape) - with pytest.raises(ValueError): - canonical_slicers((10,), shape, True) - with pytest.raises(ValueError): - canonical_slicers((1, 10), shape, True) - # Unless check_inds is False - assert canonical_slicers((10,), shape, False) == (10, slice(None)) - assert canonical_slicers((1, 10), shape, False) == (1, 10) - # Check negative -> positive - assert canonical_slicers(-1, shape) == (9, slice(None)) - assert canonical_slicers((slice(None), -1), shape) == (slice(None), 9) - # check numpy integer scalars behave the same as numpy integers - assert canonical_slicers(np.array(2), shape) == canonical_slicers(2, shape) - assert canonical_slicers((np.array(2), np.array(1)), shape) == canonical_slicers((2, 1), shape) - assert canonical_slicers((2, np.array(1)), shape) == canonical_slicers((2, 1), shape) - assert canonical_slicers((np.array(2), 1), shape) == canonical_slicers((2, 1), shape) - - -def test_slice2outax(): - # Test function giving output axes from input ndims and slice - sn = slice(None) - assert slice2outax(1, (sn,)) == (0,) - assert slice2outax(1, (1,)) == (None,) - assert slice2outax(1, (None,)) == (1,) - assert slice2outax(1, (None, 1)) == (None,) - assert slice2outax(1, (None, 1, None)) == (None,) - assert slice2outax(1, (None, sn)) == (1,) - assert slice2outax(2, (sn,)) == (0, 1) - assert slice2outax(2, (sn, sn)) == (0, 1) - assert slice2outax(2, (1,)) == (None, 0) - assert slice2outax(2, (sn, 1)) == (0, None) - assert slice2outax(2, (None,)) == (1, 2) - assert slice2outax(2, (None, 1)) == (None, 1) - assert slice2outax(2, (None, 1, None)) == (None, 2) - assert slice2outax(2, (None, 1, None, 2)) == (None, None) - assert slice2outax(2, (None, sn, None, 1)) == (1, None) - assert slice2outax(3, (sn,)) == (0, 1, 2) - assert slice2outax(3, (sn, sn)) == (0, 1, 2) - assert slice2outax(3, (sn, None, sn)) == (0, 2, 3) - assert slice2outax(3, (sn, None, sn, None, sn)) == (0, 2, 4) - assert slice2outax(3, (1,)) == (None, 0, 1) - assert slice2outax(3, (None, sn, None, 1)) == (1, None, 3) - - -def _slices_for_len(L): - # Example slices for a dimension of length L - if L == 0: - raise ValueError('Need length > 0') - sdefs = [0, L // 2, L - 1, -1, slice(None), slice(L - 1)] - if L > 1: - sdefs += [ - -2, - slice(1, L - 1), - slice(1, L - 1, 2), - slice(L - 1, 1, -1), - slice(L - 1, 1, -2), - ] - return tuple(sdefs) - - -def test_slice2len(): - # Test slice length calculation - assert slice2len(slice(None), 10) == 10 - assert slice2len(slice(11), 10) == 10 - assert slice2len(slice(1, 11), 10) == 9 - assert slice2len(slice(1, 1), 10) == 0 - assert slice2len(slice(1, 11, 2), 10) == 5 - assert slice2len(slice(0, 11, 3), 10) == 4 - assert slice2len(slice(1, 11, 3), 10) == 3 - assert slice2len(slice(None, None, -1), 10) == 10 - assert slice2len(slice(11, None, -1), 10) == 10 - assert slice2len(slice(None, 1, -1), 10) == 8 - assert slice2len(slice(None, None, -2), 10) == 5 - assert slice2len(slice(None, None, -3), 10) == 4 - assert slice2len(slice(None, 0, -3), 10) == 3 - # Start, end are always taken to be relative if negative - assert slice2len(slice(None, -4, -1), 10) == 3 - assert slice2len(slice(-4, -2, 1), 10) == 2 - # start after stop - assert slice2len(slice(3, 2, 1), 10) == 0 - assert slice2len(slice(2, 3, -1), 10) == 0 - - -def test_fill_slicer(): - # Test slice length calculation - assert fill_slicer(slice(None), 10) == slice(0, 10, 1) - assert fill_slicer(slice(11), 10) == slice(0, 10, 1) - assert fill_slicer(slice(1, 11), 10) == slice(1, 10, 1) - assert fill_slicer(slice(1, 1), 10) == slice(1, 1, 1) - assert fill_slicer(slice(1, 11, 2), 10) == slice(1, 10, 2) - assert fill_slicer(slice(0, 11, 3), 10) == slice(0, 10, 3) - assert fill_slicer(slice(1, 11, 3), 10) == slice(1, 10, 3) - assert fill_slicer(slice(None, None, -1), 10) == slice(9, None, -1) - assert fill_slicer(slice(11, None, -1), 10) == slice(9, None, -1) - assert fill_slicer(slice(None, 1, -1), 10) == slice(9, 1, -1) - assert fill_slicer(slice(None, None, -2), 10) == slice(9, None, -2) - assert fill_slicer(slice(None, None, -3), 10) == slice(9, None, -3) - assert fill_slicer(slice(None, 0, -3), 10) == slice(9, 0, -3) - # Start, end are always taken to be relative if negative - assert fill_slicer(slice(None, -4, -1), 10) == slice(9, 6, -1) - assert fill_slicer(slice(-4, -2, 1), 10) == slice(6, 8, 1) - # start after stop - assert fill_slicer(slice(3, 2, 1), 10) == slice(3, 2, 1) - assert fill_slicer(slice(2, 3, -1), 10) == slice(2, 3, -1) - - -def test__positive_slice(): - # Reverse slice direction to be positive - assert _positive_slice(slice(0, 5, 1)) == slice(0, 5, 1) - assert _positive_slice(slice(1, 5, 3)) == slice(1, 5, 3) - assert _positive_slice(slice(4, None, -2)) == slice(0, 5, 2) - assert _positive_slice(slice(4, None, -1)) == slice(0, 5, 1) - assert _positive_slice(slice(4, 1, -1)) == slice(2, 5, 1) - assert _positive_slice(slice(4, 1, -2)) == slice(2, 5, 2) - - -def test_threshold_heuristic(): - # Test for default skip / read heuristic - # int - assert threshold_heuristic(1, 9, 1, skip_thresh=8) == 'full' - assert threshold_heuristic(1, 9, 1, skip_thresh=7) is None - assert threshold_heuristic(1, 9, 2, skip_thresh=16) == 'full' - assert threshold_heuristic(1, 9, 2, skip_thresh=15) is None - # full slice, smallest step size - assert threshold_heuristic(slice(0, 9, 1), 9, 2, skip_thresh=2) == 'full' - # Dropping skip thresh below step size gives None - assert threshold_heuristic(slice(0, 9, 1), 9, 2, skip_thresh=1) == None - # As does increasing step size - assert threshold_heuristic(slice(0, 9, 2), 9, 2, skip_thresh=3) == None - # Negative step size same as positive - assert threshold_heuristic(slice(9, None, -1), 9, 2, skip_thresh=2) == 'full' - # Add a gap between start and end. Now contiguous because of step size - assert threshold_heuristic(slice(2, 9, 1), 9, 2, skip_thresh=2) == 'contiguous' - # To not-contiguous, even with step size 1 - assert threshold_heuristic(slice(2, 9, 1), 9, 2, skip_thresh=1) == None - # Back to full when skip covers gap - assert threshold_heuristic(slice(2, 9, 1), 9, 2, skip_thresh=4) == 'full' - # Until it doesn't cover the gap - assert threshold_heuristic(slice(2, 9, 1), 9, 2, skip_thresh=3) == 'contiguous' - - -# Some dummy heuristics for optimize_slicer -def _always(slicer, dim_len, stride): - return 'full' - - -def _partial(slicer, dim_len, stride): - return 'contiguous' - - -def _never(slicer, dim_len, stride): - return None - - -def test_optimize_slicer(): - # Analyze slice for fullness, contiguity, direction - # - # If all_full: - # - make positive slicer - # - decide if worth reading continuous block - # - if so, modify as_read, as_returned accordingly, set contiguous / full - # - if not, fill as_read for non-contiguous case - # If not all_full - # - make positive slicer - for all_full in (True, False): - for heuristic in (_always, _never, _partial): - for is_slowest in (True, False): - # following tests not affected by all_full or optimization - # full - always passes through - assert optimize_slicer(slice(None), 10, all_full, is_slowest, 4, heuristic) == ( - slice(None), - slice(None), - ) - # Even if full specified with explicit values - assert optimize_slicer(slice(10), 10, all_full, is_slowest, 4, heuristic) == ( - slice(None), - slice(None), - ) - assert optimize_slicer(slice(0, 10), 10, all_full, is_slowest, 4, heuristic) == ( - slice(None), - slice(None), - ) - assert optimize_slicer( - slice(0, 10, 1), 10, all_full, is_slowest, 4, heuristic - ) == (slice(None), slice(None)) - # Reversed full is still full, but with reversed post_slice - assert optimize_slicer( - slice(None, None, -1), 10, all_full, is_slowest, 4, heuristic - ) == (slice(None), slice(None, None, -1)) - # Contiguous is contiguous unless heuristic kicks in, in which case it may - # be 'full' - assert optimize_slicer(slice(9), 10, False, False, 4, _always) == (slice(0, 9, 1), slice(None)) - assert optimize_slicer(slice(9), 10, True, False, 4, _always) == (slice(None), slice(0, 9, 1)) - # Unless this is the slowest dimension, and all_true is True, in which case - # we don't update to full - assert optimize_slicer(slice(9), 10, True, True, 4, _always) == (slice(0, 9, 1), slice(None)) - # Nor if the heuristic won't update - assert optimize_slicer(slice(9), 10, True, False, 4, _never) == (slice(0, 9, 1), slice(None)) - assert optimize_slicer(slice(1, 10), 10, True, False, 4, _never) == ( - slice(1, 10, 1), - slice(None), - ) - # Reversed contiguous still contiguous - assert optimize_slicer(slice(8, None, -1), 10, False, False, 4, _never) == ( - slice(0, 9, 1), - slice(None, None, -1), - ) - assert optimize_slicer(slice(8, None, -1), 10, True, False, 4, _always) == ( - slice(None), - slice(8, None, -1), - ) - assert optimize_slicer(slice(8, None, -1), 10, False, False, 4, _never) == ( - slice(0, 9, 1), - slice(None, None, -1), - ) - assert optimize_slicer(slice(9, 0, -1), 10, False, False, 4, _never) == ( - slice(1, 10, 1), - slice(None, None, -1), - ) - # Non-contiguous - assert optimize_slicer(slice(0, 10, 2), 10, False, False, 4, _never) == ( - slice(0, 10, 2), - slice(None), - ) - # all_full triggers optimization, but optimization does nothing - assert optimize_slicer(slice(0, 10, 2), 10, True, False, 4, _never) == ( - slice(0, 10, 2), - slice(None), - ) - # all_full triggers optimization, optimization does something - assert optimize_slicer(slice(0, 10, 2), 10, True, False, 4, _always) == ( - slice(None), - slice(0, 10, 2), - ) - # all_full disables optimization, optimization does something - assert optimize_slicer(slice(0, 10, 2), 10, False, False, 4, _always) == ( - slice(0, 10, 2), - slice(None), - ) - # Non contiguous, reversed - assert optimize_slicer(slice(10, None, -2), 10, False, False, 4, _never) == ( - slice(1, 10, 2), - slice(None, None, -1), - ) - assert optimize_slicer(slice(10, None, -2), 10, True, False, 4, _always) == ( - slice(None), - slice(9, None, -2), - ) - # Short non-contiguous - assert optimize_slicer(slice(2, 8, 2), 10, False, False, 4, _never) == ( - slice(2, 8, 2), - slice(None), - ) - # with partial read - assert optimize_slicer(slice(2, 8, 2), 10, True, False, 4, _partial) == ( - slice(2, 8, 1), - slice(None, None, 2), - ) - # If this is the slowest changing dimension, heuristic can upgrade None to - # contiguous, but not (None, contiguous) to full - # we've done this one already - assert optimize_slicer(slice(0, 10, 2), 10, True, False, 4, _always) == ( - slice(None), - slice(0, 10, 2), - ) - # if slowest, just upgrade to contiguous - assert optimize_slicer(slice(0, 10, 2), 10, True, True, 4, _always) == ( - slice(0, 10, 1), - slice(None, None, 2), - ) - # contiguous does not upgrade to full - assert optimize_slicer(slice(9), 10, True, True, 4, _always) == (slice(0, 9, 1), slice(None)) - # integer - assert optimize_slicer(0, 10, True, False, 4, _never) == (0, 'dropped') - # can be negative - assert optimize_slicer(-1, 10, True, False, 4, _never) == (9, 'dropped') - # or float - assert optimize_slicer(0.9, 10, True, False, 4, _never) == (0, 'dropped') - # should never get 'contiguous' - with pytest.raises(ValueError): - optimize_slicer(0, 10, True, False, 4, _partial) - # full can be forced with heuristic - assert optimize_slicer(0, 10, True, False, 4, _always) == (slice(None), 0) - # but disabled for slowest changing dimension - assert optimize_slicer(0, 10, True, True, 4, _always) == (0, 'dropped') - - -def test_optimize_read_slicers(): - # Test function to optimize read slicers - assert optimize_read_slicers((1,), (10,), 4, _never) == ((1,), ()) - assert optimize_read_slicers((slice(None),), (10,), 4, _never) == ( - (slice(None),), - (slice(None),), - ) - assert optimize_read_slicers((slice(9),), (10,), 4, _never) == ( - (slice(0, 9, 1),), - (slice(None),), - ) - # optimize cannot update a continuous to a full if last - assert optimize_read_slicers((slice(9),), (10,), 4, _always) == ( - (slice(0, 9, 1),), - (slice(None),), - ) - # optimize can update non-contiguous to continuous even if last - # not optimizing - assert optimize_read_slicers((slice(0, 9, 2),), (10,), 4, _never) == ( - (slice(0, 9, 2),), - (slice(None),), - ) - # optimizing - assert optimize_read_slicers((slice(0, 9, 2),), (10,), 4, _always) == ( - (slice(0, 9, 1),), - (slice(None, None, 2),), - ) - # Optimize does nothing for integer when last - assert optimize_read_slicers((1,), (10,), 4, _always) == ((1,), ()) - # 2D - assert optimize_read_slicers((slice(None), slice(None)), (10, 6), 4, _never) == ( - (slice(None), slice(None)), - (slice(None), slice(None)), - ) - assert optimize_read_slicers((slice(None), 1), (10, 6), 4, _never) == ( - (slice(None), 1), - (slice(None),), - ) - assert optimize_read_slicers((1, slice(None)), (10, 6), 4, _never) == ( - (1, slice(None)), - (slice(None),), - ) - # Not optimizing a partial slice - assert optimize_read_slicers((slice(9), slice(None)), (10, 6), 4, _never) == ( - (slice(0, 9, 1), slice(None)), - (slice(None), slice(None)), - ) - # Optimizing a partial slice - assert optimize_read_slicers((slice(9), slice(None)), (10, 6), 4, _always) == ( - (slice(None), slice(None)), - (slice(0, 9, 1), slice(None)), - ) - # Optimize cannot update a continuous to a full if last - assert optimize_read_slicers((slice(None), slice(5)), (10, 6), 4, _always) == ( - (slice(None), slice(0, 5, 1)), - (slice(None), slice(None)), - ) - # optimize can update non-contiguous to full if not last - # not optimizing - assert optimize_read_slicers((slice(0, 9, 3), slice(None)), (10, 6), 4, _never) == ( - (slice(0, 9, 3), slice(None)), - (slice(None), slice(None)), - ) - # optimizing full - assert optimize_read_slicers((slice(0, 9, 3), slice(None)), (10, 6), 4, _always) == ( - (slice(None), slice(None)), - (slice(0, 9, 3), slice(None)), - ) - # optimizing partial - assert optimize_read_slicers((slice(0, 9, 3), slice(None)), (10, 6), 4, _partial) == ( - (slice(0, 9, 1), slice(None)), - (slice(None, None, 3), slice(None)), - ) - # optimize can update non-contiguous to continuous even if last - # not optimizing - assert optimize_read_slicers((slice(None), slice(0, 5, 2)), (10, 6), 4, _never) == ( - (slice(None), slice(0, 5, 2)), - (slice(None), slice(None)), - ) - # optimizing - assert optimize_read_slicers((slice(None), slice(0, 5, 2)), (10, 6), 4, _always) == ( - (slice(None), slice(0, 5, 1)), - (slice(None), slice(None, None, 2)), - ) - # Optimize does nothing for integer when last - assert optimize_read_slicers((slice(None), 1), (10, 6), 4, _always) == ( - (slice(None), 1), - (slice(None),), - ) - # Check gap threshold with 3D - depends0 = partial(threshold_heuristic, skip_thresh=10 * 4 - 1) - depends1 = partial(threshold_heuristic, skip_thresh=10 * 4) - assert optimize_read_slicers( - (slice(9), slice(None), slice(None)), (10, 6, 2), 4, depends0 - ) == ((slice(None), slice(None), slice(None)), (slice(0, 9, 1), slice(None), slice(None))) - assert optimize_read_slicers( - (slice(None), slice(5), slice(None)), (10, 6, 2), 4, depends0 - ) == ((slice(None), slice(0, 5, 1), slice(None)), (slice(None), slice(None), slice(None))) - assert optimize_read_slicers( - (slice(None), slice(5), slice(None)), (10, 6, 2), 4, depends1 - ) == ((slice(None), slice(None), slice(None)), (slice(None), slice(0, 5, 1), slice(None))) - # Check longs as integer slices - sn = slice(None) - assert optimize_read_slicers((1, 2, 3), (2, 3, 4), 4, _always) == ((sn, sn, 3), (1, 2)) - - -def test_slicers2segments(): - # Test function to construct segments from slice objects - assert slicers2segments((0,), (10,), 7, 4) == [[7, 4]] - assert slicers2segments((0, 1), (10, 6), 7, 4) == [[7 + 10 * 4, 4]] - assert slicers2segments((0, 1, 2), (10, 6, 4), 7, 4) == [[7 + 10 * 4 + 10 * 6 * 2 * 4, 4]] - assert slicers2segments((slice(None),), (10,), 7, 4) == [[7, 10 * 4]] - assert slicers2segments((0, slice(None)), (10, 6), 7, 4) == [ - [7 + 10 * 4 * i, 4] for i in range(6) - ] - assert slicers2segments((slice(None), 0), (10, 6), 7, 4) == [[7, 10 * 4]] - assert slicers2segments((slice(None), slice(None)), (10, 6), 7, 4) == [[7, 10 * 6 * 4]] - assert slicers2segments((slice(None), slice(None), 2), (10, 6, 4), 7, 4) == [ - [7 + 10 * 6 * 2 * 4, 10 * 6 * 4] - ] - - -def test_calc_slicedefs(): - # Check get_segments routine. The tests aren't well organized because I - # wrote them after the code. We live and (fail to) learn - segments, out_shape, new_slicing = calc_slicedefs((1,), (10,), 4, 7, 'F', _never) - assert segments == [[11, 4]] - assert new_slicing == () - assert out_shape == () - assert calc_slicedefs((slice(None),), (10,), 4, 7, 'F', _never) == ( - [[7, 40]], - (10,), - (), - ) - assert calc_slicedefs((slice(9),), (10,), 4, 7, 'F', _never) == ( - [[7, 36]], - (9,), - (), - ) - assert calc_slicedefs((slice(1, 9),), (10,), 4, 7, 'F', _never) == ( - [[11, 32]], - (8,), - (), - ) - # Two dimensions, single slice - assert calc_slicedefs((0,), (10, 6), 4, 7, 'F', _never) == ( - [[7, 4], [47, 4], [87, 4], [127, 4], [167, 4], [207, 4]], - (6,), - (), - ) - assert calc_slicedefs((0,), (10, 6), 4, 7, 'C', _never) == ( - [[7, 6 * 4]], - (6,), - (), - ) - # Two dimensions, contiguous not full - assert calc_slicedefs((1, slice(1, 5)), (10, 6), 4, 7, 'F', _never) == ( - [[51, 4], [91, 4], [131, 4], [171, 4]], - (4,), - (), - ) - assert calc_slicedefs((1, slice(1, 5)), (10, 6), 4, 7, 'C', _never) == ( - [[7 + 7 * 4, 16]], - (4,), - (), - ) - # With full slice first - assert calc_slicedefs((slice(None), slice(1, 5)), (10, 6), 4, 7, 'F', _never) == ( - [[47, 160]], - (10, 4), - (), - ) - # Check effect of heuristic on calc_slicedefs - # Even integer slices can generate full when heuristic says so - assert calc_slicedefs((1, slice(None)), (10, 6), 4, 7, 'F', _always) == ( - [[7, 10 * 6 * 4]], - (10, 6), - (1, slice(None)), - ) - # Except when last - assert calc_slicedefs((slice(None), 1), (10, 6), 4, 7, 'F', _always) == ( - [[7 + 10 * 4, 10 * 4]], - (10,), - (), - ) - - -def test_predict_shape(): - shapes = (15, 16, 17, 18) - for n_dim in range(len(shapes)): - shape = shapes[: n_dim + 1] - arr = np.arange(np.prod(shape)).reshape(shape) - slicers_list = [] - for i in range(n_dim): - slicers_list.append(_slices_for_len(shape[i])) - for sliceobj in product(*slicers_list): - assert predict_shape(sliceobj, shape) == arr[sliceobj].shape - # Try some Nones and ellipses - assert predict_shape((Ellipsis,), (2, 3)) == (2, 3) - assert predict_shape((Ellipsis, 1), (2, 3)) == (2,) - assert predict_shape((1, Ellipsis), (2, 3)) == (3,) - assert predict_shape((1, slice(None), Ellipsis), (2, 3)) == (3,) - assert predict_shape((None,), (2, 3)) == (1, 2, 3) - assert predict_shape((None, 1), (2, 3)) == (1, 3) - assert predict_shape((1, None, slice(None)), (2, 3)) == (1, 3) - assert predict_shape((1, slice(None), None), (2, 3)) == (3, 1) - - -def test_strided_scalar(): - # Utility to make numpy array of given shape from scalar using striding - for shape, scalar in product( - ((2,), (2, 3), (2, 3, 4)), - (1, 2, np.int16(3)), - ): - expected = np.zeros(shape, dtype=np.array(scalar).dtype) + scalar - observed = strided_scalar(shape, scalar) - assert_array_equal(observed, expected) - assert observed.shape == shape - assert observed.dtype == expected.dtype - assert_array_equal(observed.strides, 0) - # Strided scalars are set as not writeable - # This addresses a numpy 1.10 breakage of broadcasting a strided - # array without resizing (see GitHub PR #358) - assert not observed.flags.writeable - - def setval(x): - x[..., 0] = 99 - - # RuntimeError for numpy < 1.10 - with pytest.raises((RuntimeError, ValueError)): - setval(observed) - # Default scalar value is 0 - assert_array_equal(strided_scalar((2, 3, 4)), np.zeros((2, 3, 4))) - - -def _check_bytes(bytes, arr): - barr = np.ndarray(arr.shape, arr.dtype, buffer=bytes) - assert_array_equal(barr, arr) - - -def test_read_segments(): - # Test segment reading - fobj = BytesIO() - arr = np.arange(100, dtype=np.int16) - fobj.write(arr.tobytes()) - _check_bytes(read_segments(fobj, [(0, 200)], 200), arr) - _check_bytes(read_segments(fobj, [(0, 100), (100, 100)], 200), arr) - _check_bytes(read_segments(fobj, [(0, 50), (100, 50)], 100), np.r_[arr[:25], arr[50:75]]) - _check_bytes(read_segments(fobj, [(10, 40), (100, 50)], 90), np.r_[arr[5:25], arr[50:75]]) - _check_bytes(read_segments(fobj, [], 0), arr[0:0]) - # Error conditions - with pytest.raises(ValueError): - read_segments(fobj, [], 1) - with pytest.raises(ValueError): - read_segments(fobj, [(0, 200)], 199) - with pytest.raises(Exception): - read_segments(fobj, [(0, 100), (100, 200)], 199) - - -def test_read_segments_lock(): - # Test read_segment locking with multiple threads - fobj = BytesIO() - arr = np.array(np.random.randint(0, 256, 1000), dtype=np.uint8) - fobj.write(arr.tobytes()) - - # Encourage the interpreter to switch threads between a seek/read pair - def yielding_read(*args, **kwargs): - time.sleep(0.001) - return fobj._real_read(*args, **kwargs) - - fobj._real_read = fobj.read - fobj.read = yielding_read - - # Generate some random array segments to read from the file - def random_segments(nsegs): - segs = [] - nbytes = 0 - - for i in range(nsegs): - seglo = np.random.randint(0, 998) - seghi = np.random.randint(seglo + 1, 1000) - seglen = seghi - seglo - nbytes += seglen - segs.append([seglo, seglen]) - - return segs, nbytes - - # Get the data that should be returned for the given segments - def get_expected(segs): - segs = [arr[off : off + length] for off, length in segs] - return np.concatenate(segs) - - # Read from the file, check the result. We do this task simultaneously in - # many threads. Each thread that passes adds 1 to numpassed[0] - numpassed = [0] - lock = Lock() - - def runtest(): - seg, nbytes = random_segments(1) - expected = get_expected(seg) - _check_bytes(read_segments(fobj, seg, nbytes, lock), expected) - - seg, nbytes = random_segments(10) - expected = get_expected(seg) - _check_bytes(read_segments(fobj, seg, nbytes, lock), expected) - - with lock: - numpassed[0] += 1 - - threads = [Thread(target=runtest) for i in range(100)] - [t.start() for t in threads] - [t.join() for t in threads] - assert numpassed[0] == len(threads) - - -def _check_slicer(sliceobj, arr, fobj, offset, order, heuristic=threshold_heuristic): - new_slice = fileslice(fobj, sliceobj, arr.shape, arr.dtype, offset, order, heuristic) - assert_array_equal(arr[sliceobj], new_slice) - - -def slicer_samples(shape): - """Generator returns slice samples for given `shape`""" - ndim = len(shape) - slicers_list = [] - for i in range(ndim): - slicers_list.append(_slices_for_len(shape[i])) - yield from product(*slicers_list) - # Nones and ellipses - yield (None,) - if ndim == 0: - return - yield (None, 0) - yield (None, np.array(0)) - yield (0, None) - yield (np.array(0), None) - yield (Ellipsis, -1) - yield (Ellipsis, np.array(-1)) - yield (-1, Ellipsis) - yield (np.array(-1), Ellipsis) - yield (None, Ellipsis) - yield (Ellipsis, None) - yield (Ellipsis, None, None) - if ndim == 1: - return - yield (0, None, slice(None)) - yield (np.array(0), None, slice(None)) - yield (Ellipsis, -1, None) - yield (Ellipsis, np.array(-1), None) - yield (0, Ellipsis, None) - yield (np.array(0), Ellipsis, None) - if ndim == 2: - return - yield (slice(None), 0, -1, None) - yield (slice(None), np.array(0), np.array(-1), None) - yield (np.array(0), slice(None), np.array(-1), None) - - -def test_fileslice(): - shapes = (15, 16, 17) - for n_dim in range(1, len(shapes) + 1): - shape = shapes[:n_dim] - arr = np.arange(np.prod(shape)).reshape(shape) - for order in 'FC': - for offset in (0, 20): - fobj = BytesIO() - fobj.write(b'\0' * offset) - fobj.write(arr.tobytes(order=order)) - for sliceobj in slicer_samples(shape): - _check_slicer(sliceobj, arr, fobj, offset, order) - - -def test_fileslice_dtype(): - # Test that any valid dtype specifier works for fileslice - sliceobj = (slice(None), slice(2)) - for dt in (np.dtype('int32'), np.int32, 'i4', 'int32', '>i4', '= 63: - # Check conversion to int; the line below causes an error subtracting - # ints / uint64 values, at least for Python 3.3 and numpy dev 1.8 - big_int = np.uint64(2**64 - 1) - assert int(np.longdouble(big_int)) == big_int - - -def test_int_np_regression(): - # Test int works as expected for integers. - # We previously used a custom as_int() for integers because of a - # numpy 1.4.1 bug such that int(np.uint32(2**32-1) == -1 - for t in sctypes['int'] + sctypes['uint']: - info = np.iinfo(t) - mn, mx = np.array([info.min, info.max], dtype=t) - assert (mn, mx) == (int(mn), int(mx)) - - -def test_floor_exact_16(): - # A normal integer can generate an inf in float16 - assert floor_exact(2**31, np.float16) == np.inf - assert floor_exact(-(2**31), np.float16) == -np.inf - - -def test_floor_exact_64(): - # float64 - for e in range(53, 63): - start = np.float64(2**e) - across = start + np.arange(2048, dtype=np.float64) - gaps = set(np.diff(across)).difference([0]) - assert len(gaps) == 1 - gap = gaps.pop() - assert gap == int(gap) - test_val = 2 ** (e + 1) - 1 - assert floor_exact(test_val, np.float64) == 2 ** (e + 1) - int(gap) - - -def test_floor_exact(max_digits): - max_digits(4950) # max longdouble is ~10**4932 - - to_test = IEEE_floats + [float] - try: - type_info(np.longdouble)['nmant'] - except FloatingError: - # Significand bit count not reliable, don't test long double - pass - else: - to_test.append(np.longdouble) - # When numbers go above int64 - I believe, numpy comparisons break down, - # so we have to cast to int before comparison - int_flex = lambda x, t: int(floor_exact(x, t)) - int_ceex = lambda x, t: int(ceil_exact(x, t)) - for t in to_test: - # A number bigger than the range returns the max - info = type_info(t) - assert floor_exact(10**4933, t) == np.inf - assert ceil_exact(10**4933, t) == np.inf - # A number more negative returns -inf - assert floor_exact(-(10**4933), t) == -np.inf - assert ceil_exact(-(10**4933), t) == -np.inf - # Check around end of integer precision - nmant = info['nmant'] - for i in range(nmant + 1): - iv = 2**i - # up to 2**nmant should be exactly representable - for func in (int_flex, int_ceex): - assert func(iv, t) == iv - assert func(-iv, t) == -iv - assert func(iv - 1, t) == iv - 1 - assert func(-iv + 1, t) == -iv + 1 - if t is np.longdouble and (on_powerpc() or longdouble_precision_improved()): - # The nmant value for longdouble on PPC appears to be conservative, - # so that the tests for behavior above the nmant range fail. - # windows longdouble can change from float64 to Intel80 in some - # situations, in which case nmant will not be correct - continue - # Confirm to ourselves that 2**(nmant+1) can't be exactly represented - iv = 2 ** (nmant + 1) - assert int_flex(iv + 1, t) == iv - assert int_ceex(iv + 1, t) == iv + 2 - # negatives - assert int_flex(-iv - 1, t) == -iv - 2 - assert int_ceex(-iv - 1, t) == -iv - # The gap in representable numbers is 2 above 2**(nmant+1), 4 above - # 2**(nmant+2), and so on. - for i in range(5): - iv = 2 ** (nmant + 1 + i) - gap = 2 ** (i + 1) - assert int(t(iv) + t(gap)) == iv + gap - for j in range(1, gap): - assert int_flex(iv + j, t) == iv - assert int_flex(iv + gap + j, t) == iv + gap - assert int_ceex(iv + j, t) == iv + gap - assert int_ceex(iv + gap + j, t) == iv + 2 * gap - # negatives - for j in range(1, gap): - assert int_flex(-iv - j, t) == -iv - gap - assert int_flex(-iv - gap - j, t) == -iv - 2 * gap - assert int_ceex(-iv - j, t) == -iv - assert int_ceex(-iv - gap - j, t) == -iv - gap - - -def test_usable_binary128(): - # Check for usable binary128 - yes = have_binary128() - with np.errstate(over='ignore'): - exp_test = np.longdouble(2) ** 16383 - assert yes == ( - exp_test.dtype.itemsize == 16 - and np.isfinite(exp_test) - and _check_nmant(np.longdouble, 112) - ) diff --git a/nibabel/tests/test_funcs.py b/nibabel/tests/test_funcs.py deleted file mode 100644 index b4139f30ef..0000000000 --- a/nibabel/tests/test_funcs.py +++ /dev/null @@ -1,193 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Test for image funcs""" - -import numpy as np -import pytest -from numpy.testing import assert_array_equal - -from ..analyze import AnalyzeImage -from ..funcs import OrientationError, as_closest_canonical, concat_images -from ..loadsave import save -from ..nifti1 import Nifti1Image -from ..tmpdirs import InTemporaryDirectory - -_counter = 0 - - -def _as_fname(img): - global _counter - fname = f'img{_counter:3d}.nii' - _counter = _counter + 1 - save(img, fname) - return fname - - -def test_concat(): - # Smoke test: concat empty list. - with pytest.raises(ValueError): - concat_images([]) - - # Build combinations of 3D, 4D w/size[3] == 1, and 4D w/size[3] == 3 - all_shapes_5D = ((1, 4, 5, 3, 3), (7, 3, 1, 4, 5), (0, 2, 1, 4, 5)) - - affine = np.eye(4) - for dim in range(2, 6): - all_shapes_ND = tuple(shape[:dim] for shape in all_shapes_5D) - all_shapes_N1D_unary = tuple(shape + (1,) for shape in all_shapes_ND) - all_shapes = all_shapes_ND + all_shapes_N1D_unary - - # Loop over all possible combinations of images, in first and - # second position. - for data0_shape in all_shapes: - data0_numel = np.asarray(data0_shape).prod() - data0 = np.arange(data0_numel, dtype='int32').reshape(data0_shape) - img0_mem = Nifti1Image(data0, affine) - - for data1_shape in all_shapes: - data1_numel = np.asarray(data1_shape).prod() - data1 = np.arange(data1_numel, dtype='int32').reshape(data1_shape) - img1_mem = Nifti1Image(data1, affine) - img2_mem = Nifti1Image(data1, affine + 1) # bad affine - - # Loop over every possible axis, including None (explicit and implied) - for axis in list(range(-(dim - 2), (dim - 1))) + [None, '__default__']: - # Allow testing default vs. passing explicit param - if axis == '__default__': - np_concat_kwargs = dict(axis=-1) - concat_imgs_kwargs = dict() - axis = None # Convert downstream - elif axis is None: - np_concat_kwargs = dict(axis=-1) - concat_imgs_kwargs = dict(axis=axis) - else: - np_concat_kwargs = dict(axis=axis) - concat_imgs_kwargs = dict(axis=axis) - - # Create expected output - try: - # Error will be thrown if the np.concatenate fails. - # However, when axis=None, the concatenate is possible - # but our efficient logic (where all images are - # 3D and the same size) fails, so we also - # have to expect errors for those. - if axis is None: # 3D from here and below - all_data = np.concatenate( - [data0[..., np.newaxis], data1[..., np.newaxis]], - **np_concat_kwargs, - ) - else: # both 3D, appending on final axis - all_data = np.concatenate([data0, data1], **np_concat_kwargs) - expect_error = False - except ValueError: - # Shapes are not combinable - expect_error = True - - # Check filenames and in-memory images work - with InTemporaryDirectory(): - # Try mem-based, file-based, and mixed - imgs = [img0_mem, img1_mem, img2_mem] - img_files = [_as_fname(img) for img in imgs] - imgs_mixed = [imgs[0], img_files[1], imgs[2]] - for img0, img1, img2 in (imgs, img_files, imgs_mixed): - try: - all_imgs = concat_images([img0, img1], **concat_imgs_kwargs) - except ValueError as ve: - assert expect_error, str(ve) - else: - assert not expect_error, ( - 'Expected a concatenation error, but got none.' - ) - assert_array_equal(all_imgs.get_fdata(), all_data) - assert_array_equal(all_imgs.affine, affine) - - # check that not-matching affines raise error - with pytest.raises(ValueError): - concat_images([img0, img2], **concat_imgs_kwargs) - - # except if check_affines is False - try: - all_imgs = concat_images([img0, img1], **concat_imgs_kwargs) - except ValueError as ve: - assert expect_error, str(ve) - else: - assert not expect_error, ( - 'Expected a concatenation error, but got none.' - ) - assert_array_equal(all_imgs.get_fdata(), all_data) - assert_array_equal(all_imgs.affine, affine) - - -def test_closest_canonical(): - # Use 32-bit data so that the AnalyzeImage class doesn't complain - arr = np.arange(24, dtype=np.int32).reshape((2, 3, 4, 1)) - - # Test with an AnalyzeImage first - img = AnalyzeImage(arr, np.eye(4)) - xyz_img = as_closest_canonical(img) - assert img is xyz_img - - # And a case where the Analyze image has to be flipped - img = AnalyzeImage(arr, np.diag([-1, 1, 1, 1])) - xyz_img = as_closest_canonical(img) - assert img is not xyz_img - out_arr = xyz_img.get_fdata() - assert_array_equal(out_arr, np.flipud(arr)) - - # Now onto the NIFTI cases (where dim_info also has to be updated) - - # No funky stuff, returns same thing - img = Nifti1Image(arr, np.eye(4)) - # set freq/phase/slice dim so that we can check that we - # re-order them properly - img.header.set_dim_info(0, 1, 2) - xyz_img = as_closest_canonical(img) - assert img is xyz_img - - # a axis flip - img = Nifti1Image(arr, np.diag([-1, 1, 1, 1])) - img.header.set_dim_info(0, 1, 2) - xyz_img = as_closest_canonical(img) - assert img is not xyz_img - assert img.header.get_dim_info() == xyz_img.header.get_dim_info() - out_arr = xyz_img.get_fdata() - assert_array_equal(out_arr, np.flipud(arr)) - - # no error for enforce_diag in this case - xyz_img = as_closest_canonical(img, True) - # but there is if the affine is not diagonal - aff = np.eye(4) - aff[0, 1] = 0.1 - # although it's more or less canonical already - img = Nifti1Image(arr, aff) - xyz_img = as_closest_canonical(img) - assert img is xyz_img - # it's still not diagnonal - with pytest.raises(OrientationError): - as_closest_canonical(img, True) - - # an axis swap - aff = np.diag([1, 0, 0, 1]) - aff[1, 2] = 1 - aff[2, 1] = 1 - img = Nifti1Image(arr, aff) - img.header.set_dim_info(0, 1, 2) - - xyz_img = as_closest_canonical(img) - assert img is not xyz_img - # Check both the original and new objects - assert img.header.get_dim_info() == (0, 1, 2) - assert xyz_img.header.get_dim_info() == (0, 2, 1) - out_arr = xyz_img.get_fdata() - assert_array_equal(out_arr, np.transpose(arr, (0, 2, 1, 3))) - - # same axis swap but with None dim info (except for slice dim) - img.header.set_dim_info(None, None, 2) - xyz_img = as_closest_canonical(img) - assert xyz_img.header.get_dim_info() == (None, None, 1) diff --git a/nibabel/tests/test_image_api.py b/nibabel/tests/test_image_api.py deleted file mode 100644 index 5898762322..0000000000 --- a/nibabel/tests/test_image_api.py +++ /dev/null @@ -1,807 +0,0 @@ -"""Validate image API - -What is the image API? - -* ``img.dataobj`` - - * Returns ``np.ndarray`` from ``np.array(img.databj)`` - * Has attribute ``shape`` - -* ``img.header`` (image metadata) (changes in the image metadata should not - change any of ``dataobj``, ``affine``, ``shape``) -* ``img.affine`` (4x4 float ``np.ndarray`` relating spatial voxel coordinates - to world space) -* ``img.shape`` (shape of data as read with ``np.array(img.dataobj)`` -* ``img.get_fdata()`` (returns floating point data as read with - ``np.array(img.dataobj)`` and the cast to float); -* ``img.uncache()`` (``img.get_fdata()`` (recommended) and ``img.get_data()`` - (deprecated) are allowed to cache the result of the array creation. If they - do, this call empties that cache. Implement this as a no-op if - ``get_fdata()``, ``get_data()`` do not cache.) -* ``img[something]`` generates an informative TypeError -* ``img.in_memory`` is True for an array image, and for a proxy image that is - cached, but False otherwise. -""" - -import io -import pathlib -import sys -import warnings -from functools import partial -from itertools import product - -import numpy as np - -from ..optpkg import optional_package - -_, have_scipy, _ = optional_package('scipy') -_, have_h5py, _ = optional_package('h5py') - -import unittest - -import pytest -from numpy.testing import assert_allclose, assert_almost_equal, assert_array_equal - -from nibabel.arraywriters import WriterError -from nibabel.testing import ( - assert_data_similar, - bytesio_filemap, - bytesio_round_trip, - clear_and_catch_warnings, - deprecated_to, - expires, -) - -from .. import ( - AnalyzeImage, - GiftiImage, - MGHImage, - Minc1Image, - Minc2Image, - Nifti1Image, - Nifti1Pair, - Nifti2Image, - Nifti2Pair, - Spm2AnalyzeImage, - Spm99AnalyzeImage, - brikhead, - is_proxy, - minc1, - minc2, - parrec, -) -from ..casting import sctypes -from ..spatialimages import SpatialImage -from ..tmpdirs import InTemporaryDirectory -from .test_api_validators import ValidateAPI -from .test_brikhead import EXAMPLE_IMAGES as AFNI_EXAMPLE_IMAGES -from .test_minc1 import EXAMPLE_IMAGES as MINC1_EXAMPLE_IMAGES -from .test_minc2 import EXAMPLE_IMAGES as MINC2_EXAMPLE_IMAGES -from .test_parrec import EXAMPLE_IMAGES as PARREC_EXAMPLE_IMAGES - - -class GenericImageAPI(ValidateAPI): - """General image validation API""" - - # Whether this image type can do scaling of data - has_scaling = False - # Whether the image can be saved to disk / file objects - can_save = False - # Filename extension to which to save image; only used if `can_save` is - # True - standard_extension = '.img' - - def obj_params(self): - """Return generator returning (`img_creator`, `img_params`) tuples - - ``img_creator`` is a function taking no arguments and returning a fresh - image. We need to return this ``img_creator`` function rather than an - image instance so we can recreate the images fresh for each of multiple - tests run from the ``validate_xxx`` autogenerated test methods. This - allows the tests to modify the image without having an effect on the - later tests in the same function, because each test will create a fresh - image with ``img_creator``. - - Returns - ------- - func_params_gen : generator - Generator returning tuples with: - - * img_creator : callable - Callable returning a fresh image for testing - * img_params : mapping - Expected properties of image returned from ``img_creator`` - callable. Key, value pairs should include: - - * ``data`` : array returned from ``get_fdata()`` on image - OR - - ``data_summary`` : dict with data ``min``, ``max``, ``mean``; - * ``shape`` : shape of image; - * ``affine`` : shape (4, 4) affine array for image; - * ``dtype`` : dtype of data returned from ``np.asarray(dataobj)``; - * ``is_proxy`` : bool, True if image data is proxied; - - Notes - ----- - Passing ``data_summary`` instead of ``data`` allows you gentle user to - avoid having to have a saved copy of the entire data array from example - images for testing. - """ - raise NotImplementedError - - def validate_header(self, imaker, params): - # Check header API - img = imaker() - hdr = img.header # we can fetch it - # Read only - with pytest.raises(AttributeError): - img.header = hdr - - def validate_filenames(self, imaker, params): - # Validate the filename, file_map interface - - if not self.can_save: - raise unittest.SkipTest - img = imaker() - img.set_data_dtype(np.float32) # to avoid rounding in load / save - # Make sure the object does not have a file_map - img.file_map = None - # The bytesio_round_trip helper tests bytesio load / save via file_map - rt_img = bytesio_round_trip(img) - assert_array_equal(img.shape, rt_img.shape) - assert_almost_equal(img.get_fdata(), rt_img.get_fdata()) - assert_almost_equal(np.asanyarray(img.dataobj), np.asanyarray(rt_img.dataobj)) - # Give the image a file map - klass = type(img) - rt_img.file_map = bytesio_filemap(klass) - # This object can now be saved and loaded from its own file_map - rt_img.to_file_map() - rt_rt_img = klass.from_file_map(rt_img.file_map) - assert_almost_equal(img.get_fdata(), rt_rt_img.get_fdata()) - assert_almost_equal(np.asanyarray(img.dataobj), np.asanyarray(rt_img.dataobj)) - # get_ / set_ filename - fname = 'an_image' + self.standard_extension - for path in (fname, pathlib.Path(fname)): - img.set_filename(path) - assert img.get_filename() == str(path) - assert img.file_map['image'].filename == str(path) - # to_ / from_ filename - fname = 'another_image' + self.standard_extension - for path in (fname, pathlib.Path(fname)): - with InTemporaryDirectory(): - # Validate that saving or loading a file doesn't use deprecated methods internally - with clear_and_catch_warnings(): - warnings.filterwarnings( - 'error', category=DeprecationWarning, module=r'nibabel.*' - ) - img.to_filename(path) - rt_img = img.__class__.from_filename(path) - assert_array_equal(img.shape, rt_img.shape) - assert_almost_equal(img.get_fdata(), rt_img.get_fdata()) - assert_almost_equal(np.asanyarray(img.dataobj), np.asanyarray(rt_img.dataobj)) - del rt_img # to allow windows to delete the directory - - def validate_no_slicing(self, imaker, params): - img = imaker() - with pytest.raises(TypeError): - img['string'] - with pytest.raises(TypeError): - img[:] - - @expires('5.0.0') - def validate_get_data_deprecated(self, imaker, params): - img = imaker() - with deprecated_to('5.0.0'): - data = img.get_data() - assert_array_equal(np.asanyarray(img.dataobj), data) - - -class GetSetDtypeMixin: - """Adds dtype tests - - Add this one if your image has ``get_data_dtype`` and ``set_data_dtype``. - """ - - def validate_dtype(self, imaker, params): - # data / storage dtype - img = imaker() - # Need to rename this one - assert img.get_data_dtype().type == params['dtype'] - # dtype survives round trip - if self.has_scaling and self.can_save: - with np.errstate(invalid='ignore'): - rt_img = bytesio_round_trip(img) - assert rt_img.get_data_dtype().type == params['dtype'] - # Setting to a different dtype - img.set_data_dtype(np.float32) # assumed supported for all formats - assert img.get_data_dtype().type == np.float32 - # dtype survives round trip - if self.can_save: - rt_img = bytesio_round_trip(img) - assert rt_img.get_data_dtype().type == np.float32 - - -class DataInterfaceMixin(GetSetDtypeMixin): - """Test dataobj interface for images with array backing - - Use this mixin if your image has a ``dataobj`` property that contains an - array or an array-like thing. - """ - - meth_names = ('get_fdata',) - - def validate_data_interface(self, imaker, params): - # Check get data returns array, and caches - img = imaker() - assert img.shape == img.dataobj.shape - assert img.ndim == len(img.shape) - assert_data_similar(img.dataobj, params) - for meth_name in self.meth_names: - if params['is_proxy']: - self._check_proxy_interface(imaker, meth_name) - else: # Array image - self._check_array_interface(imaker, meth_name) - method = getattr(img, meth_name) - # Data shape is same as image shape - assert img.shape == method().shape - # Data ndim is same as image ndim - assert img.ndim == method().ndim - # Values to get_data caching parameter must be 'fill' or - # 'unchanged' - with pytest.raises(ValueError): - method(caching='something') - # dataobj is read only - fake_data = np.zeros(img.shape, dtype=img.get_data_dtype()) - with pytest.raises(AttributeError): - img.dataobj = fake_data - # So is in_memory - with pytest.raises(AttributeError): - img.in_memory = False - - def _check_proxy_interface(self, imaker, meth_name): - # Parameters assert this is an array proxy - img = imaker() - # Does is_proxy agree? - assert is_proxy(img.dataobj) - # Confirm it is not a numpy array - assert not isinstance(img.dataobj, np.ndarray) - # Confirm it can be converted to a numpy array with asarray - proxy_data = np.asarray(img.dataobj) - proxy_copy = proxy_data.copy() - # Not yet cached, proxy image: in_memory is False - assert not img.in_memory - # Load with caching='unchanged' - method = getattr(img, meth_name) - data = method(caching='unchanged') - # Still not cached - assert not img.in_memory - # Default load, does caching - data = method() - # Data now cached. in_memory is True if either of the get_data - # or get_fdata caches are not-None - assert img.in_memory - # We previously got proxy_data from disk, but data, which we - # have just fetched, is a fresh copy. - assert not proxy_data is data - # asarray on dataobj, applied above, returns same numerical - # values. This might not be true get_fdata operating on huge - # integers, but lets assume that's not true here. - assert_array_equal(proxy_data, data) - # Now caching='unchanged' does nothing, returns cached version - data_again = method(caching='unchanged') - assert data is data_again - # caching='fill' does nothing because the cache is already full - data_yet_again = method(caching='fill') - assert data is data_yet_again - # changing array data does not change proxy data, or reloaded - # data - data[:] = 42 - assert_array_equal(proxy_data, proxy_copy) - assert_array_equal(np.asarray(img.dataobj), proxy_copy) - # It does change the result of get_fdata - assert_array_equal(method(), 42) - # until we uncache - img.uncache() - # Which unsets in_memory - assert not img.in_memory - assert_array_equal(method(), proxy_copy) - # Check caching='fill' does cache data - img = imaker() - method = getattr(img, meth_name) - assert not img.in_memory - data = method(caching='fill') - assert img.in_memory - data_again = method() - assert data is data_again - # Check that caching refreshes for new floating point type. - img.uncache() - fdata = img.get_fdata() - assert fdata.dtype == np.float64 - fdata[:] = 42 - fdata_back = img.get_fdata() - assert_array_equal(fdata_back, 42) - assert fdata_back.dtype == np.float64 - # New data dtype, no caching, doesn't use or alter cache - fdata_new_dt = img.get_fdata(caching='unchanged', dtype='f4') - # We get back the original read, not the modified cache - # Allow for small rounding error when the data is scaled with 32-bit - # factors, rather than 64-bit factors and then cast to float-32 - # Use rtol/atol from numpy.allclose - assert_allclose(fdata_new_dt, proxy_data.astype('f4'), rtol=1e-05, atol=1e-08) - assert fdata_new_dt.dtype == np.float32 - # The original cache stays in place, for default float64 - assert_array_equal(img.get_fdata(), 42) - # And for not-default float32, because we haven't cached - fdata_new_dt[:] = 43 - fdata_new_dt = img.get_fdata(caching='unchanged', dtype='f4') - assert_allclose(fdata_new_dt, proxy_data.astype('f4'), rtol=1e-05, atol=1e-08) - # Until we reset with caching='fill', at which point we - # drop the original float64 cache, and have a float32 cache - fdata_new_dt = img.get_fdata(caching='fill', dtype='f4') - assert_allclose(fdata_new_dt, proxy_data.astype('f4'), rtol=1e-05, atol=1e-08) - # We're using the cache, for dtype='f4' reads - fdata_new_dt[:] = 43 - assert_array_equal(img.get_fdata(dtype='f4'), 43) - # We've lost the cache for float64 reads (no longer 42) - assert_array_equal(img.get_fdata(), proxy_data) - - def _check_array_interface(self, imaker, meth_name): - for caching in (None, 'fill', 'unchanged'): - self._check_array_caching(imaker, meth_name, caching) - - def _check_array_caching(self, imaker, meth_name, caching): - img = imaker() - method = getattr(img, meth_name) - get_data_func = method if caching is None else partial(method, caching=caching) - assert isinstance(img.dataobj, np.ndarray) - assert img.in_memory - data = get_data_func() - # Returned data same object as underlying dataobj if using - # old ``get_data`` method, or using newer ``get_fdata`` - # method, where original array was float64. - arr_dtype = img.dataobj.dtype - dataobj_is_data = arr_dtype == np.float64 or method == img.get_data - # Set something to the output array. - data[:] = 42 - get_result_changed = np.all(get_data_func() == 42) - assert get_result_changed == (dataobj_is_data or caching != 'unchanged') - if dataobj_is_data: - assert data is img.dataobj - # Changing array data changes - # data - assert_array_equal(np.asarray(img.dataobj), 42) - # Uncache has no effect - img.uncache() - assert_array_equal(get_data_func(), 42) - else: - assert not data is img.dataobj - assert not np.all(np.asarray(img.dataobj) == 42) - # Uncache does have an effect - img.uncache() - assert not np.all(get_data_func() == 42) - # in_memory is always true for array images, regardless of - # cache state. - img.uncache() - assert img.in_memory - if meth_name != 'get_fdata': - return - # Return original array from get_fdata only if the input array is the - # requested dtype. - float_types = sctypes['float'] - if arr_dtype not in float_types: - return - for float_type in float_types: - data = get_data_func(dtype=float_type) - assert (data is img.dataobj) == (arr_dtype == float_type) - - def validate_shape(self, imaker, params): - # Validate shape - img = imaker() - # Same as expected shape - assert img.shape == params['shape'] - # Same as array shape if passed - if 'data' in params: - assert img.shape == params['data'].shape - # Read only - with pytest.raises(AttributeError): - img.shape = np.eye(4) - - def validate_ndim(self, imaker, params): - # Validate shape - img = imaker() - # Same as expected ndim - assert img.ndim == len(params['shape']) - # Same as array ndim if passed - if 'data' in params: - assert img.ndim == params['data'].ndim - # Read only - with pytest.raises(AttributeError): - img.ndim = 5 - - def validate_mmap_parameter(self, imaker, params): - img = imaker() - fname = img.get_filename() - with InTemporaryDirectory(): - # Load test files with mmap parameters - # or - # Save a generated file so we can test it - if fname is None: - # Skip only formats we can't write - if not img.rw or not img.valid_exts: - return - fname = 'image' + img.valid_exts[0] - img.to_filename(fname) - rt_img = img.__class__.from_filename(fname, mmap=True) - assert_almost_equal(img.get_fdata(), rt_img.get_fdata()) - rt_img = img.__class__.from_filename(fname, mmap=False) - assert_almost_equal(img.get_fdata(), rt_img.get_fdata()) - rt_img = img.__class__.from_filename(fname, mmap='c') - assert_almost_equal(img.get_fdata(), rt_img.get_fdata()) - rt_img = img.__class__.from_filename(fname, mmap='r') - assert_almost_equal(img.get_fdata(), rt_img.get_fdata()) - # r+ is specifically not valid for images - with pytest.raises(ValueError): - img.__class__.from_filename(fname, mmap='r+') - with pytest.raises(ValueError): - img.__class__.from_filename(fname, mmap='invalid') - del rt_img # to allow windows to delete the directory - - -class HeaderShapeMixin: - """Tests that header shape can be set and got - - Add this one of your header supports ``get_data_shape`` and - ``set_data_shape``. - """ - - def validate_header_shape(self, imaker, params): - # Change shape in header, check this changes img.header - img = imaker() - hdr = img.header - shape = hdr.get_data_shape() - new_shape = (shape[0] + 1,) + shape[1:] - hdr.set_data_shape(new_shape) - assert img.header is hdr - assert img.header.get_data_shape() == new_shape - - -class AffineMixin: - """Adds test of affine property, method - - Add this one if your image has an ``affine`` property. - """ - - def validate_affine(self, imaker, params): - # Check affine API - img = imaker() - assert_almost_equal(img.affine, params['affine'], 6) - assert img.affine.dtype == np.float64 - img.affine[0, 0] = 1.5 - assert img.affine[0, 0] == 1.5 - # Read only - with pytest.raises(AttributeError): - img.affine = np.eye(4) - - -class SerializeMixin: - def validate_to_from_stream(self, imaker, params): - img = imaker() - klass = getattr(self, 'klass', img.__class__) - stream = io.BytesIO() - img.to_stream(stream) - - rt_img = klass.from_stream(stream) - assert self._header_eq(img.header, rt_img.header) - assert np.array_equal(img.get_fdata(), rt_img.get_fdata()) - - def validate_file_stream_equivalence(self, imaker, params): - img = imaker() - klass = getattr(self, 'klass', img.__class__) - with InTemporaryDirectory(): - fname = 'img' + self.standard_extension - img.to_filename(fname) - - with open('stream', 'wb') as fobj: - img.to_stream(fobj) - - # Check that writing gets us the same thing - contents1 = pathlib.Path(fname).read_bytes() - contents2 = pathlib.Path('stream').read_bytes() - assert contents1 == contents2 - - # Check that reading gets us the same thing - img_a = klass.from_filename(fname) - with open(fname, 'rb') as fobj: - img_b = klass.from_stream(fobj) - # This needs to happen while the filehandle is open - assert np.array_equal(img_a.get_fdata(), img_b.get_fdata()) - assert self._header_eq(img_a.header, img_b.header) - del img_a - del img_b - - def validate_to_from_bytes(self, imaker, params): - img = imaker() - klass = getattr(self, 'klass', img.__class__) - with InTemporaryDirectory(): - fname = 'img' + self.standard_extension - img.to_filename(fname) - - all_images = list(getattr(self, 'example_images', [])) + [{'fname': fname}] - for img_params in all_images: - img_a = klass.from_filename(img_params['fname']) - bytes_a = img_a.to_bytes() - - img_b = klass.from_bytes(bytes_a) - - assert img_b.to_bytes() == bytes_a - assert self._header_eq(img_a.header, img_b.header) - assert np.array_equal(img_a.get_fdata(), img_b.get_fdata()) - del img_a - del img_b - - @pytest.fixture(autouse=True) - def setup_method(self, httpserver, tmp_path): - """Make pytest fixtures available to validate functions""" - self.httpserver = httpserver - self.tmp_path = tmp_path - - def validate_from_url(http://github.com/self,%20imaker,%20params): - server = self.httpserver - - img = imaker() - img_bytes = img.to_bytes() - - server.expect_oneshot_request('/img').respond_with_data(img_bytes) - url = server.url_for('/img') - assert url.startswith('http://') # Check we'll trigger an HTTP handler - rt_img = img.__class__.from_http://github.com/url(url) - - assert rt_img.to_bytes() == img_bytes - assert self._header_eq(img.header, rt_img.header) - assert np.array_equal(img.get_fdata(), rt_img.get_fdata()) - del img - del rt_img - - @pytest.mark.xfail( - sys.version_info >= (3, 12), - reason='Response type for file: urls is not a stream in Python 3.12', - ) - def validate_from_file_url(http://github.com/self,%20imaker,%20params): - tmp_path = self.tmp_path - - img = imaker() - import uuid - - fname = tmp_path / f'img-{uuid.uuid4()}{self.standard_extension}' - img.to_filename(fname) - - rt_img = img.__class__.from_url(http://github.com/f'file:///%7Bfname%7D') - - assert self._header_eq(img.header, rt_img.header) - assert np.array_equal(img.get_fdata(), rt_img.get_fdata()) - del img - del rt_img - - @staticmethod - def _header_eq(header_a, header_b): - """Header equality check that can be overridden by a subclass of this test - - This allows us to retain the same tests above when testing an image that uses an - abstract class as a header, namely when testing the FileBasedImage API, which - raises a NotImplementedError for __eq__ - """ - return header_a == header_b - - -class LoadImageAPI( - GenericImageAPI, DataInterfaceMixin, AffineMixin, GetSetDtypeMixin, HeaderShapeMixin -): - # Callable returning an image from a filename - loader = None - # Sequence of dictionaries, where dictionaries have keys - # 'fname" in addition to keys for ``params`` (see obj_params docstring) - example_images = () - # Class of images to be tested - klass = None - - def obj_params(self): - for img_params in self.example_images: - yield lambda: self.loader(img_params['fname']), img_params - - def validate_path_maybe_image(self, imaker, params): - for img_params in self.example_images: - test, sniff = self.klass.path_maybe_image(img_params['fname']) - assert isinstance(test, bool) - if sniff is not None: - assert isinstance(sniff[0], bytes) - assert isinstance(sniff[1], str) - - -class MakeImageAPI(LoadImageAPI): - """Validation for images we can make with ``func(data, affine, header)``""" - - # A callable returning an image from ``image_maker(data, affine, header)`` - image_maker = None - # A callable returning a header from ``header_maker()`` - header_maker = None - # Example shapes for created images - example_shapes = ((2,), (2, 3), (2, 3, 4), (2, 3, 4, 5)) - # Supported dtypes for storing to disk - storable_dtypes = (np.uint8, np.int16, np.float32) - - def obj_params(self): - # Return any obj_params from superclass - for func, params in super().obj_params(): - yield func, params - # Create new images - aff = np.diag([1, 2, 3, 1]) - - def make_imaker(arr, aff, header=None): - return lambda: self.image_maker(arr, aff, header) - - def make_prox_imaker(arr, aff, hdr): - def prox_imaker(): - img = self.image_maker(arr, aff, hdr) - rt_img = bytesio_round_trip(img) - return self.image_maker(rt_img.dataobj, aff, rt_img.header) - - return prox_imaker - - for shape, stored_dtype in product(self.example_shapes, self.storable_dtypes): - # To make sure we do not trigger scaling, always use the - # stored_dtype for the input array. - arr = np.arange(np.prod(shape), dtype=stored_dtype).reshape(shape) - hdr = self.header_maker() - hdr.set_data_dtype(stored_dtype) - func = make_imaker(arr.copy(), aff, hdr) - params = dict(dtype=stored_dtype, affine=aff, data=arr, shape=shape, is_proxy=False) - yield make_imaker(arr.copy(), aff, hdr), params - if not self.can_save: - continue - # Create proxy images from these array images, by storing via BytesIO. - # We assume that loading from a fileobj creates a proxy image. - params['is_proxy'] = True - yield make_prox_imaker(arr.copy(), aff, hdr), params - - -class DtypeOverrideMixin(GetSetDtypeMixin): - """Test images that can accept ``dtype`` arguments to ``__init__`` and - ``to_file_map`` - """ - - def validate_init_dtype_override(self, imaker, params): - img = imaker() - klass = img.__class__ - for dtype in self.storable_dtypes: - if hasattr(img, 'affine'): - new_img = klass(img.dataobj, img.affine, header=img.header, dtype=dtype) - else: # XXX This is for CIFTI-2, these validators might need refactoring - new_img = klass(img.dataobj, header=img.header, dtype=dtype) - assert new_img.get_data_dtype() == dtype - - if self.has_scaling and self.can_save: - with np.errstate(invalid='ignore'): - rt_img = bytesio_round_trip(new_img) - assert rt_img.get_data_dtype() == dtype - - def validate_to_file_dtype_override(self, imaker, params): - if not self.can_save: - raise unittest.SkipTest - img = imaker() - orig_dtype = img.get_data_dtype() - fname = 'image' + self.standard_extension - with InTemporaryDirectory(): - for dtype in self.storable_dtypes: - try: - img.to_filename(fname, dtype=dtype) - except WriterError: - # It's possible to try to save to a dtype that requires - # scaling, and images without scale factors will fail. - # We're not testing that here. - continue - rt_img = img.__class__.from_filename(fname) - assert rt_img.get_data_dtype() == dtype - assert img.get_data_dtype() == orig_dtype - - -class ImageHeaderAPI(MakeImageAPI): - """When ``self.image_maker`` is an image class, make header from class""" - - def header_maker(self): - return self.image_maker.header_class() - - -class TestSpatialImageAPI(ImageHeaderAPI): - klass = image_maker = SpatialImage - can_save = False - - -class TestAnalyzeAPI(TestSpatialImageAPI, DtypeOverrideMixin): - """General image validation API instantiated for Analyze images""" - - klass = image_maker = AnalyzeImage - has_scaling = False - can_save = True - standard_extension = '.img' - # Supported dtypes for storing to disk - storable_dtypes = (np.uint8, np.int16, np.int32, np.float32, np.float64) - - -class TestSpm99AnalyzeAPI(TestAnalyzeAPI): - # SPM-type analyze need scipy for mat file IO - klass = image_maker = Spm99AnalyzeImage - has_scaling = True - can_save = have_scipy - - -class TestSpm2AnalyzeAPI(TestSpm99AnalyzeAPI): - klass = image_maker = Spm2AnalyzeImage - - -class TestNifti1PairAPI(TestSpm99AnalyzeAPI): - klass = image_maker = Nifti1Pair - can_save = True - - -class TestNifti1API(TestNifti1PairAPI, SerializeMixin): - klass = image_maker = Nifti1Image - standard_extension = '.nii' - - -class TestNifti2PairAPI(TestNifti1PairAPI): - klass = image_maker = Nifti2Pair - - -class TestNifti2API(TestNifti1API): - klass = image_maker = Nifti2Image - - -class TestMinc1API(ImageHeaderAPI): - klass = image_maker = Minc1Image - loader = minc1.load - example_images = MINC1_EXAMPLE_IMAGES - - -class TestMinc2API(TestMinc1API): - def setup_method(self): - if not have_h5py: - raise unittest.SkipTest('Need h5py for these tests') - - klass = image_maker = Minc2Image - loader = minc2.load - example_images = MINC2_EXAMPLE_IMAGES - - -class TestPARRECAPI(LoadImageAPI): - def loader(self, fname): - return parrec.load(fname) - - klass = parrec.PARRECImage - example_images = PARREC_EXAMPLE_IMAGES - - -# ECAT is a special case and needs more thought -# class TestEcatAPI(TestAnalyzeAPI): -# image_maker = ecat.EcatImage -# has_scaling = True -# can_save = True -# standard_extension = '.v' - - -class TestMGHAPI(ImageHeaderAPI, SerializeMixin): - klass = image_maker = MGHImage - example_shapes = ((2, 3, 4), (2, 3, 4, 5)) # MGH can only do >= 3D - has_scaling = True - can_save = True - standard_extension = '.mgh' - - -class TestGiftiAPI(LoadImageAPI, SerializeMixin): - klass = image_maker = GiftiImage - can_save = True - standard_extension = '.gii' - - -class TestAFNIAPI(LoadImageAPI): - loader = brikhead.load - klass = image_maker = brikhead.AFNIImage - example_images = AFNI_EXAMPLE_IMAGES diff --git a/nibabel/tests/test_image_load_save.py b/nibabel/tests/test_image_load_save.py deleted file mode 100644 index 0e5fd57d08..0000000000 --- a/nibabel/tests/test_image_load_save.py +++ /dev/null @@ -1,307 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Tests for loader function""" - -import logging -import pathlib -import shutil -from io import BytesIO -from os.path import dirname -from os.path import join as pjoin -from tempfile import mkdtemp - -import numpy as np -import pytest -from numpy.testing import assert_array_almost_equal, assert_array_equal - -from .. import ( - AnalyzeImage, - MGHImage, - Minc1Image, - Minc2Image, - Nifti1Image, - Nifti1Pair, - Nifti2Image, - Nifti2Pair, - Spm2AnalyzeImage, - Spm99AnalyzeImage, - all_image_classes, -) -from .. import analyze as ana -from .. import loadsave as nils -from .. import nifti1 as ni1 -from .. import spm2analyze as spm2 -from .. import spm99analyze as spm99 -from ..optpkg import optional_package -from ..spatialimages import SpatialImage -from ..testing import deprecated_to, expires -from ..tmpdirs import InTemporaryDirectory -from ..volumeutils import native_code, swapped_code - -_, have_scipy, _ = optional_package('scipy') # No scipy=>no SPM-format writing -DATA_PATH = pjoin(dirname(__file__), 'data') -MGH_DATA_PATH = pjoin(dirname(__file__), '..', 'freesurfer', 'tests', 'data') - - -def round_trip(img): - # round trip a nifti single - return Nifti1Image.from_bytes(img.to_bytes()) - - -def test_conversion_spatialimages(caplog): - shape = (2, 4, 6) - affine = np.diag([1, 2, 3, 1]) - klasses = [ - klass for klass in all_image_classes if klass.rw and issubclass(klass, SpatialImage) - ] - for npt in np.float32, np.int16: - data = np.arange(np.prod(shape), dtype=npt).reshape(shape) - for r_class in klasses: - if not r_class.makeable: - continue - img = r_class(data, affine) - img.set_data_dtype(npt) - for w_class in klasses: - if not w_class.makeable: - continue - # Suppress header field mismatch reports - with caplog.at_level(logging.CRITICAL): - img2 = w_class.from_image(img) - assert_array_equal(img2.get_fdata(), data) - assert_array_equal(img2.affine, affine) - - -def test_save_load_endian(): - shape = (2, 4, 6) - affine = np.diag([1, 2, 3, 1]) - data = np.arange(np.prod(shape), dtype='f4').reshape(shape) - # Native endian image - img = Nifti1Image(data, affine) - assert img.header.endianness == native_code - img2 = round_trip(img) - assert img2.header.endianness == native_code - assert_array_equal(img2.get_fdata(), data) - assert_array_equal(np.asanyarray(img2.dataobj), data) - # byte swapped endian image - bs_hdr = img.header.as_byteswapped() - bs_img = Nifti1Image(data, affine, bs_hdr) - assert bs_img.header.endianness == swapped_code - # of course the data is the same because it's not written to disk - assert_array_equal(bs_img.get_fdata(), data) - assert_array_equal(np.asanyarray(bs_img.dataobj), data) - # Check converting to another image - cbs_img = AnalyzeImage.from_image(bs_img) - # this will make the header native by doing the header conversion - cbs_hdr = cbs_img.header - assert cbs_hdr.endianness == native_code - # and the byte order follows it back into another image - cbs_img2 = Nifti1Image.from_image(cbs_img) - cbs_hdr2 = cbs_img2.header - assert cbs_hdr2.endianness == native_code - # Try byteswapped round trip - bs_img2 = round_trip(bs_img) - bs_data2 = np.asanyarray(bs_img2.dataobj) - bs_fdata2 = bs_img2.get_fdata() - # now the data dtype was swapped endian, so the read data is too - assert bs_data2.dtype.byteorder == swapped_code - assert bs_img2.header.endianness == swapped_code - assert_array_equal(bs_data2, data) - # but get_fdata uses native endian - assert bs_fdata2.dtype.byteorder != swapped_code - assert_array_equal(bs_fdata2, data) - # Now mix up byteswapped data and non-byteswapped header - mixed_img = Nifti1Image(bs_data2, affine) - assert mixed_img.header.endianness == native_code - m_img2 = round_trip(mixed_img) - assert m_img2.header.endianness == native_code - assert_array_equal(m_img2.get_fdata(), data) - - -def test_save_load(): - shape = (2, 4, 6) - npt = np.float32 - data = np.arange(np.prod(shape), dtype=npt).reshape(shape) - affine = np.diag([1, 2, 3, 1]) - affine[:3, 3] = [3, 2, 1] - img = ni1.Nifti1Image(data, affine) - img.set_data_dtype(npt) - with InTemporaryDirectory(): - nifn = 'an_image.nii' - sifn = 'another_image.img' - ni1.save(img, nifn) - re_img = nils.load(nifn) - assert isinstance(re_img, ni1.Nifti1Image) - assert_array_equal(re_img.get_fdata(), data) - assert_array_equal(re_img.affine, affine) - # These and subsequent del statements are to prevent confusing - # windows errors when trying to open files or delete the - # temporary directory. - del re_img - if have_scipy: # skip we we cannot read .mat files - spm2.save(img, sifn) - re_img2 = nils.load(sifn) - assert isinstance(re_img2, spm2.Spm2AnalyzeImage) - assert_array_equal(re_img2.get_fdata(), data) - assert_array_equal(re_img2.affine, affine) - del re_img2 - spm99.save(img, sifn) - re_img3 = nils.load(sifn) - assert isinstance(re_img3, spm99.Spm99AnalyzeImage) - assert_array_equal(re_img3.get_fdata(), data) - assert_array_equal(re_img3.affine, affine) - ni1.save(re_img3, nifn) - del re_img3 - re_img = nils.load(nifn) - assert isinstance(re_img, ni1.Nifti1Image) - assert_array_equal(re_img.get_fdata(), data) - assert_array_equal(re_img.affine, affine) - del re_img - - -def test_two_to_one(): - # test going from two to one file in save - shape = (2, 4, 6) - npt = np.float32 - data = np.arange(np.prod(shape), dtype=npt).reshape(shape) - affine = np.diag([1, 2, 3, 1]) - affine[:3, 3] = [3, 2, 1] - # single file format - img = ni1.Nifti1Image(data, affine) - assert img.header['magic'] == b'n+1' - str_io = BytesIO() - img.file_map['image'].fileobj = str_io - # check that the single format vox offset stays at zero - img.to_file_map() - assert img.header['magic'] == b'n+1' - assert img.header['vox_offset'] == 0 - # make a new pair image, with the single image header - pimg = ni1.Nifti1Pair(data, affine, img.header) - isio = BytesIO() - hsio = BytesIO() - pimg.file_map['image'].fileobj = isio - pimg.file_map['header'].fileobj = hsio - pimg.to_file_map() - # the offset stays at zero (but is 352 on disk) - assert pimg.header['magic'] == b'ni1' - assert pimg.header['vox_offset'] == 0 - assert_array_equal(pimg.get_fdata(), data) - # same for from_image, going from single image to pair format - ana_img = ana.AnalyzeImage.from_image(img) - assert ana_img.header['vox_offset'] == 0 - # back to the single image, save it again to a stringio - str_io = BytesIO() - img.file_map['image'].fileobj = str_io - img.to_file_map() - assert img.header['vox_offset'] == 0 - aimg = ana.AnalyzeImage.from_image(img) - assert aimg.header['vox_offset'] == 0 - aimg = spm99.Spm99AnalyzeImage.from_image(img) - assert aimg.header['vox_offset'] == 0 - aimg = spm2.Spm2AnalyzeImage.from_image(img) - assert aimg.header['vox_offset'] == 0 - nfimg = ni1.Nifti1Pair.from_image(img) - assert nfimg.header['vox_offset'] == 0 - # now set the vox offset directly - hdr = nfimg.header - hdr['vox_offset'] = 16 - assert nfimg.header['vox_offset'] == 16 - # check it gets properly set by the nifti single image - nfimg = ni1.Nifti1Image.from_image(img) - assert nfimg.header['vox_offset'] == 0 - - -def test_negative_load_save(): - shape = (1, 2, 5) - data = np.arange(10).reshape(shape) - 10.0 - affine = np.eye(4) - hdr = ni1.Nifti1Header() - hdr.set_data_dtype(np.int16) - img = Nifti1Image(data, affine, hdr) - str_io = BytesIO() - img.file_map['image'].fileobj = str_io - img.to_file_map() - str_io.seek(0) - re_img = Nifti1Image.from_file_map(img.file_map) - assert_array_almost_equal(re_img.get_fdata(), data, 4) - - -def test_filename_save(): - # This is to test the logic in the load and save routines, relating - # extensions to filetypes - # Tuples of class, ext, loadedclass - inklass_ext_loadklasses = ( - (Nifti1Image, '.nii', Nifti1Image), - (Nifti2Image, '.nii', Nifti2Image), - (Nifti1Pair, '.nii', Nifti1Image), - (Nifti2Pair, '.nii', Nifti2Image), - (Nifti1Image, '.img', Nifti1Pair), - (Nifti2Image, '.img', Nifti2Pair), - (Nifti1Pair, '.img', Nifti1Pair), - (Nifti2Pair, '.img', Nifti2Pair), - (Nifti1Image, '.hdr', Nifti1Pair), - (Nifti2Image, '.hdr', Nifti2Pair), - (Nifti1Pair, '.hdr', Nifti1Pair), - (Nifti2Pair, '.hdr', Nifti2Pair), - (Minc1Image, '.nii', Nifti1Image), - (Minc1Image, '.img', Nifti1Pair), - (Spm2AnalyzeImage, '.nii', Nifti1Image), - (Spm2AnalyzeImage, '.img', Spm2AnalyzeImage), - (Spm99AnalyzeImage, '.nii', Nifti1Image), - (Spm99AnalyzeImage, '.img', Spm2AnalyzeImage), - (AnalyzeImage, '.nii', Nifti1Image), - (AnalyzeImage, '.img', Spm2AnalyzeImage), - ) - shape = (2, 4, 6) - affine = np.diag([1, 2, 3, 1]) - data = np.arange(np.prod(shape), dtype='f4').reshape(shape) - for inklass, out_ext, loadklass in inklass_ext_loadklasses: - if not have_scipy: - # We can't load a SPM analyze type without scipy. These types have - # a 'mat' file (the type we can't load) - if ('mat', '.mat') in loadklass.files_types: - continue - img = inklass(data, affine) - try: - pth = mkdtemp() - fname = pjoin(pth, 'image' + out_ext) - for path in (fname, pathlib.Path(fname)): - nils.save(img, path) - rt_img = nils.load(path) - assert_array_almost_equal(rt_img.get_fdata(), data) - assert type(rt_img) is loadklass - # delete image to allow file close. Otherwise windows - # raises an error when trying to delete the directory - del rt_img - finally: - shutil.rmtree(pth) - - -@expires('5.0.0') -def test_guessed_image_type(): - # Test whether we can guess the image type from example files - with deprecated_to('5.0.0'): - assert nils.guessed_image_type(pjoin(DATA_PATH, 'example4d.nii.gz')) == Nifti1Image - assert nils.guessed_image_type(pjoin(DATA_PATH, 'nifti1.hdr')) == Nifti1Pair - assert nils.guessed_image_type(pjoin(DATA_PATH, 'example_nifti2.nii.gz')) == Nifti2Image - assert nils.guessed_image_type(pjoin(DATA_PATH, 'nifti2.hdr')) == Nifti2Pair - assert nils.guessed_image_type(pjoin(DATA_PATH, 'tiny.mnc')) == Minc1Image - assert nils.guessed_image_type(pjoin(DATA_PATH, 'small.mnc')) == Minc2Image - assert nils.guessed_image_type(pjoin(DATA_PATH, 'test.mgz')) == MGHImage - assert nils.guessed_image_type(pjoin(DATA_PATH, 'analyze.hdr')) == Spm2AnalyzeImage - - -def test_fail_save(): - with InTemporaryDirectory(): - dataobj = np.ones((10, 10, 10), dtype=np.float16) - affine = np.eye(4, dtype=np.float32) - img = SpatialImage(dataobj, affine) - # Fails because float16 is not supported. - with pytest.raises(AttributeError): - nils.save(img, 'foo.nii.gz') - del img diff --git a/nibabel/tests/test_image_types.py b/nibabel/tests/test_image_types.py deleted file mode 100644 index a9c41763a7..0000000000 --- a/nibabel/tests/test_image_types.py +++ /dev/null @@ -1,130 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Tests for is_image / may_contain_header functions""" - -import copy -from os.path import basename, dirname -from os.path import join as pjoin - -import numpy as np - -from .. import ( - MGHImage, - Minc1Image, - Minc2Image, - Nifti1Image, - Nifti1Pair, - Nifti2Image, - Nifti2Pair, - Spm2AnalyzeImage, - all_image_classes, -) - -DATA_PATH = pjoin(dirname(__file__), 'data') - - -def test_sniff_and_guessed_image_type(img_klasses=all_image_classes): - # Loop over all test cases: - # * whether a sniff is provided or not - # * randomizing the order of image classes - # * over all known image types - - # For each, we expect: - # * When the file matches the expected class, things should - # either work, or fail if we're doing bad stuff. - # * When the file is a mismatch, the functions should not throw. - def test_image_class(img_path, expected_img_klass): - """Compare an image of one image class to all others. - - The function should make sure that it loads the image with the expected - class, but failing when given a bad sniff (when the sniff is used).""" - - def check_img(img_path, img_klass, sniff_mode, sniff, expect_success, msg): - """Embedded function to do the actual checks expected.""" - - if sniff_mode == 'no_sniff': - # Don't pass any sniff--not even "None" - is_img, new_sniff = img_klass.path_maybe_image(img_path) - elif sniff_mode in ('empty', 'irrelevant', 'bad_sniff'): - # Add img_path to binaryblock sniff parameters - is_img, new_sniff = img_klass.path_maybe_image(img_path, (sniff, img_path)) - else: - # Pass a sniff, but don't reuse across images. - is_img, new_sniff = img_klass.path_maybe_image(img_path, sniff) - - if expect_success: - # Check that the sniff returned is appropriate. - new_msg = f'{img_klass.__name__} returned sniff==None ({msg})' - expected_sizeof_hdr = getattr(img_klass.header_class, 'sizeof_hdr', 0) - current_sizeof_hdr = 0 if new_sniff is None else len(new_sniff[0]) - assert current_sizeof_hdr >= expected_sizeof_hdr, new_msg - - # Check that the image type was recognized. - new_msg = ( - f'{basename(img_path)} ({msg}) image ' - f'is{"" if is_img else " not"} ' - f'a {img_klass.__name__} image.' - ) - assert is_img, new_msg - - if sniff_mode == 'vanilla': - return new_sniff - else: - return sniff - - sizeof_hdr = getattr(expected_img_klass.header_class, 'sizeof_hdr', 0) - - for sniff_mode, sniff in dict( - vanilla=None, # use the sniff of the previous item - no_sniff=None, # Don't pass a sniff - none=None, # pass None as the sniff, should query in fn - empty=b'', # pass an empty sniff, should query in fn - irrelevant=b'a' * (sizeof_hdr - 1), # A too-small sniff, query - bad_sniff=b'a' * sizeof_hdr, # Bad sniff, should fail - ).items(): - for klass in img_klasses: - if klass == expected_img_klass: - # Class will load unless you pass a bad sniff, - # or the header ignores the sniff - expect_success = sniff_mode != 'bad_sniff' or sizeof_hdr == 0 - else: - expect_success = False # Not sure the relationships - - # Reuse the sniff... but it will only change for some - # sniff_mode values. - msg = f'{expected_img_klass.__name__}/ {sniff_mode}/ {expect_success}' - sniff = check_img( - img_path, - klass, - sniff_mode=sniff_mode, - sniff=sniff, - expect_success=expect_success, - msg=msg, - ) - - # Test whether we can guess the image type from example files - for img_filename, image_klass in [ - ('example4d.nii.gz', Nifti1Image), - ('nifti1.hdr', Nifti1Pair), - ('example_nifti2.nii.gz', Nifti2Image), - ('nifti2.hdr', Nifti2Pair), - ('tiny.mnc', Minc1Image), - ('small.mnc', Minc2Image), - ('test.mgz', MGHImage), - ('analyze.hdr', Spm2AnalyzeImage), - ]: - # print('Testing: %s %s' % (img_filename, image_klass.__name__)) - test_image_class(pjoin(DATA_PATH, img_filename), image_klass) - - -def test_sniff_and_guessed_image_type_randomized(): - """Re-test image classes, but in a randomized order.""" - img_klasses = copy.copy(all_image_classes) - np.random.shuffle(img_klasses) - test_sniff_and_guessed_image_type(img_klasses=img_klasses) diff --git a/nibabel/tests/test_imageclasses.py b/nibabel/tests/test_imageclasses.py deleted file mode 100644 index 90ef966d2d..0000000000 --- a/nibabel/tests/test_imageclasses.py +++ /dev/null @@ -1,44 +0,0 @@ -"""Testing imageclasses module""" - -from os.path import dirname -from os.path import join as pjoin - -import numpy as np - -import nibabel as nib -from nibabel.analyze import AnalyzeImage -from nibabel.imageclasses import spatial_axes_first -from nibabel.nifti1 import Nifti1Image -from nibabel.nifti2 import Nifti2Image -from nibabel.optpkg import optional_package - -have_h5py = optional_package('h5py')[1] - -DATA_DIR = pjoin(dirname(__file__), 'data') - -MINC_3DS = ('minc1_1_scale.mnc',) -MINC_4DS = ('minc1_4d.mnc',) -if have_h5py: - MINC_3DS = MINC_3DS + ('minc2_1_scale.mnc',) - MINC_4DS = MINC_4DS + ('minc2_4d.mnc',) - - -def test_spatial_axes_first(): - # Function tests is spatial axes are first three axes in image - # Always True for Nifti and friends - affine = np.eye(4) - for shape in ((2, 3), (4, 3, 2), (5, 4, 1, 2), (2, 3, 5, 2, 1)): - for img_class in (AnalyzeImage, Nifti1Image, Nifti2Image): - data = np.zeros(shape) - img = img_class(data, affine) - assert spatial_axes_first(img) - # True for MINC images < 4D - for fname in MINC_3DS: - img = nib.load(pjoin(DATA_DIR, fname)) - assert len(img.shape) == 3 - assert spatial_axes_first(img) - # False for MINC images < 4D - for fname in MINC_4DS: - img = nib.load(pjoin(DATA_DIR, fname)) - assert len(img.shape) == 4 - assert not spatial_axes_first(img) diff --git a/nibabel/tests/test_imageglobals.py b/nibabel/tests/test_imageglobals.py deleted file mode 100644 index 9de72e87c6..0000000000 --- a/nibabel/tests/test_imageglobals.py +++ /dev/null @@ -1,19 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Tests for imageglobals module""" - -from .. import imageglobals as igs - - -def test_errorlevel(): - orig_level = igs.error_level - for level in (10, 20, 30): - with igs.ErrorLevel(level): - assert igs.error_level == level - assert igs.error_level == orig_level diff --git a/nibabel/tests/test_imagestats.py b/nibabel/tests/test_imagestats.py deleted file mode 100644 index 8adfc910a8..0000000000 --- a/nibabel/tests/test_imagestats.py +++ /dev/null @@ -1,27 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Tests for image statistics""" - -import numpy as np - -from .. import Nifti1Image, imagestats - - -def test_mask_volume(): - # Test mask volume computation - - mask_data = np.zeros((20, 20, 20), dtype='u1') - mask_data[5:15, 5:15, 5:15] = 1 - img = Nifti1Image(mask_data, np.eye(4)) - - vol_mm3 = imagestats.mask_volume(img) - vol_vox = imagestats.count_nonzero_voxels(img) - - assert vol_mm3 == 1000.0 - assert vol_vox == 1000 diff --git a/nibabel/tests/test_init.py b/nibabel/tests/test_init.py deleted file mode 100644 index d339c4e26b..0000000000 --- a/nibabel/tests/test_init.py +++ /dev/null @@ -1,61 +0,0 @@ -import pathlib -import unittest -from importlib.resources import files -from unittest import mock - -import pytest - -import nibabel as nib - - -@pytest.mark.parametrize( - ('verbose', 'v_args'), [(-2, ['-qq']), (-1, ['-q']), (0, []), (1, ['-v']), (2, ['-vv'])] -) -@pytest.mark.parametrize('doctests', (True, False)) -@pytest.mark.parametrize('coverage', (True, False)) -def test_nibabel_test(verbose, v_args, doctests, coverage): - expected_args = v_args + ['--doctest-modules', '--cov', 'nibabel', '--pyargs', 'nibabel'] - if not doctests: - expected_args.remove('--doctest-modules') - if not coverage: - expected_args[-4:-2] = [] - - with mock.patch('pytest.main') as pytest_main: - nib.test(verbose=verbose, doctests=doctests, coverage=coverage) - - args, kwargs = pytest_main.call_args - assert args == () - assert kwargs == {'args': expected_args} - - -def test_nibabel_test_errors(): - with pytest.raises(NotImplementedError): - nib.test(label='fast') - with pytest.raises(NotImplementedError): - nib.test(raise_warnings=[]) - with pytest.raises(NotImplementedError): - nib.test(timer=True) - with pytest.raises(ValueError): - nib.test(verbose='-v') - - -def test_nibabel_bench(): - config_path = files('nibabel') / 'benchmarks/pytest.benchmark.ini' - if not isinstance(config_path, pathlib.Path): - raise unittest.SkipTest('Package is not unpacked; could get temp path') - - expected_args = ['-c', str(config_path), '--pyargs', 'nibabel'] - - with mock.patch('pytest.main') as pytest_main: - nib.bench(verbose=0) - - args, kwargs = pytest_main.call_args - assert args == () - assert kwargs == {'args': expected_args} - - with mock.patch('pytest.main') as pytest_main: - nib.bench(verbose=0, extra_argv=[]) - - args, kwargs = pytest_main.call_args - assert args == () - assert kwargs == {'args': expected_args} diff --git a/nibabel/tests/test_loadsave.py b/nibabel/tests/test_loadsave.py deleted file mode 100644 index 035cbb56c7..0000000000 --- a/nibabel/tests/test_loadsave.py +++ /dev/null @@ -1,212 +0,0 @@ -"""Testing loadsave module""" - -import pathlib -import shutil -from os.path import dirname -from os.path import join as pjoin -from tempfile import TemporaryDirectory - -import numpy as np - -from .. import ( - Nifti1Image, - Nifti1Pair, - Nifti2Image, - Nifti2Pair, - Spm2AnalyzeImage, - Spm99AnalyzeImage, -) -from ..filebasedimages import ImageFileError -from ..loadsave import _signature_matches_extension, load, read_img_data -from ..openers import Opener -from ..optpkg import optional_package -from ..testing import deprecated_to, expires -from ..tmpdirs import InTemporaryDirectory - -_, have_scipy, _ = optional_package('scipy') -_, have_pyzstd, _ = optional_package('pyzstd') - -import pytest -from numpy.testing import assert_almost_equal, assert_array_equal - -data_path = pjoin(dirname(__file__), 'data') - - -@expires('5.0.0') -def test_read_img_data(): - fnames_test = [ - 'example4d.nii.gz', - 'example_nifti2.nii.gz', - 'minc1_1_scale.mnc', - 'minc1_4d.mnc', - 'test.mgz', - 'tiny.mnc', - ] - fnames_test += [pathlib.Path(p) for p in fnames_test] - for fname in fnames_test: - fpath = pjoin(data_path, fname) - if isinstance(fname, pathlib.Path): - fpath = pathlib.Path(fpath) - img = load(fpath) - data = img.get_fdata() - with deprecated_to('5.0.0'): - data2 = read_img_data(img) - assert_array_equal(data, data2) - # These examples have null scaling - assert prefer=unscaled is the same - dao = img.dataobj - if hasattr(dao, 'slope') and hasattr(img.header, 'raw_data_from_fileobj'): - assert (dao.slope, dao.inter) == (1, 0) - with deprecated_to('5.0.0'): - assert_array_equal(read_img_data(img, prefer='unscaled'), data) - # Assert all caps filename works as well - with TemporaryDirectory() as tmpdir: - up_fpath = pjoin(tmpdir, str(fname).upper()) - if isinstance(fname, pathlib.Path): - up_fpath = pathlib.Path(up_fpath) - shutil.copyfile(fpath, up_fpath) - img = load(up_fpath) - assert_array_equal(img.dataobj, data) - del img - - -def test_file_not_found(): - with pytest.raises(FileNotFoundError): - load('does_not_exist.nii.gz') - - -def test_load_empty_image(): - with InTemporaryDirectory(): - open('empty.nii', 'w').close() - with pytest.raises(ImageFileError) as err: - load('empty.nii') - assert str(err.value).startswith('Empty file: ') - - -@pytest.mark.parametrize('extension', ['.gz', '.bz2', '.zst']) -def test_load_bad_compressed_extension(tmp_path, extension): - if extension == '.zst' and not have_pyzstd: - pytest.skip() - file_path = tmp_path / f'img.nii{extension}' - file_path.write_bytes(b'bad') - with pytest.raises(ImageFileError, match=r'.*is not a .* file'): - load(file_path) - - -@pytest.mark.parametrize('extension', ['.gz', '.bz2', '.zst']) -def test_load_good_extension_with_bad_data(tmp_path, extension): - if extension == '.zst' and not have_pyzstd: - pytest.skip() - file_path = tmp_path / f'img.nii{extension}' - with Opener(file_path, 'wb') as fobj: - fobj.write(b'bad') - with pytest.raises(ImageFileError, match=r'Cannot work out file type of .*'): - load(file_path) - - -def test_signature_matches_extension(tmp_path): - gz_signature = b'\x1f\x8b' - good_file = tmp_path / 'good.gz' - good_file.write_bytes(gz_signature) - bad_file = tmp_path / 'bad.gz' - bad_file.write_bytes(b'bad') - matches, msg = _signature_matches_extension(tmp_path / 'uncompressed.nii') - assert matches - assert msg == '' - matches, msg = _signature_matches_extension(tmp_path / 'missing.gz') - assert not matches - assert msg.startswith('Could not read') - matches, msg = _signature_matches_extension(bad_file) - assert not matches - assert 'is not a' in msg - matches, msg = _signature_matches_extension(good_file) - assert matches - assert msg == '' - matches, msg = _signature_matches_extension(tmp_path / 'missing.nii') - assert matches - assert msg == '' - - -@expires('5.0.0') -def test_read_img_data_nifti(): - shape = (2, 3, 4) - data = np.random.normal(size=shape) - out_dtype = np.dtype(np.int16) - classes = (Nifti1Pair, Nifti1Image, Nifti2Pair, Nifti2Image) - if have_scipy: - classes += (Spm99AnalyzeImage, Spm2AnalyzeImage) - with InTemporaryDirectory(): - for i, img_class in enumerate(classes): - img = img_class(data, np.eye(4)) - img.set_data_dtype(out_dtype) - # No filemap => error - with deprecated_to('5.0.0'), pytest.raises(ImageFileError): - read_img_data(img) - # Make a filemap - froot = f'an_image_{i}' - img.file_map = img.filespec_to_file_map(froot) - # Trying to read from this filemap will generate an error because - # we are going to read from files that do not exist - with deprecated_to('5.0.0'), pytest.raises(OSError): - read_img_data(img) - img.to_file_map() - # Load - now the scaling and offset correctly applied - img_fname = img.file_map['image'].filename - img_back = load(img_fname) - data_back = img_back.get_fdata() - with deprecated_to('5.0.0'): - assert_array_equal(data_back, read_img_data(img_back)) - # This is the same as if we loaded the image and header separately - hdr_fname = img.file_map['header'].filename if 'header' in img.file_map else img_fname - with open(hdr_fname, 'rb') as fobj: - hdr_back = img_back.header_class.from_fileobj(fobj) - with open(img_fname, 'rb') as fobj: - scaled_back = hdr_back.data_from_fileobj(fobj) - assert_array_equal(data_back, scaled_back) - # Unscaled is the same as returned from raw_data_from_fileobj - with open(img_fname, 'rb') as fobj: - unscaled_back = hdr_back.raw_data_from_fileobj(fobj) - with deprecated_to('5.0.0'): - assert_array_equal(unscaled_back, read_img_data(img_back, prefer='unscaled')) - # If we futz with the scaling in the header, the result changes - with deprecated_to('5.0.0'): - assert_array_equal(data_back, read_img_data(img_back)) - has_inter = hdr_back.has_data_intercept - old_slope = hdr_back['scl_slope'] - old_inter = hdr_back['scl_inter'] if has_inter else 0 - est_unscaled = (data_back - old_inter) / old_slope - with deprecated_to('5.0.0'): - actual_unscaled = read_img_data(img_back, prefer='unscaled') - assert_almost_equal(est_unscaled, actual_unscaled) - img_back.header['scl_slope'] = 2.1 - if has_inter: - new_inter = 3.14 - img_back.header['scl_inter'] = 3.14 - else: - new_inter = 0 - # scaled scaling comes from new parameters in header - with deprecated_to('5.0.0'): - assert np.allclose(actual_unscaled * 2.1 + new_inter, read_img_data(img_back)) - # Unscaled array didn't change - with deprecated_to('5.0.0'): - assert_array_equal(actual_unscaled, read_img_data(img_back, prefer='unscaled')) - # Check the offset too - img.header.set_data_offset(1024) - # Delete arrays still pointing to file, so Windows can reuse - del actual_unscaled, unscaled_back - img.to_file_map() - # Write an integer of zeros after - with open(img_fname, 'ab') as fobj: - fobj.write(b'\x00\x00') - img_back = load(img_fname) - data_back = img_back.get_fdata() - with deprecated_to('5.0.0'): - assert_array_equal(data_back, read_img_data(img_back)) - img_back.header.set_data_offset(1026) - # Check we pick up new offset - exp_offset = np.zeros((data.size,), data.dtype) + old_inter - exp_offset[:-1] = np.ravel(data_back, order='F')[1:] - exp_offset = np.reshape(exp_offset, shape, order='F') - with deprecated_to('5.0.0'): - assert_array_equal(exp_offset, read_img_data(img_back)) - # Delete stuff that might hold onto file references - del img, img_back, data_back diff --git a/nibabel/tests/test_minc1.py b/nibabel/tests/test_minc1.py deleted file mode 100644 index 8f88bf802d..0000000000 --- a/nibabel/tests/test_minc1.py +++ /dev/null @@ -1,216 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## - -import bz2 -import gzip -from io import BytesIO -from os.path import join as pjoin - -import numpy as np -import pytest -from numpy.testing import assert_array_equal - -from .. import Nifti1Image, load, minc1 -from ..externals.netcdf import netcdf_file -from ..minc1 import Minc1File, Minc1Image, MincHeader -from ..optpkg import optional_package -from ..testing import assert_data_similar, data_path -from ..tmpdirs import InTemporaryDirectory -from . import test_spatialimages as tsi -from .test_fileslice import slicer_samples - -pyzstd, HAVE_ZSTD, _ = optional_package('pyzstd') - -EG_FNAME = pjoin(data_path, 'tiny.mnc') - -# Example images in format expected for ``test_image_api``, adding ``zooms`` -# item. -EXAMPLE_IMAGES = [ - dict( - fname=pjoin(data_path, 'tiny.mnc'), - shape=(10, 20, 20), - dtype=np.uint8, - affine=np.array( - [ - [0, 0, 2.0, -20], - [0, 2.0, 0, -20], - [2.0, 0, 0, -10], - [0, 0, 0, 1], - ] - ), - zooms=(2.0, 2.0, 2.0), - # These values from SPM2 - data_summary=dict(min=0.20784314, max=0.74901961, mean=0.60602819), - is_proxy=True, - ), - dict( - fname=pjoin(data_path, 'minc1_1_scale.mnc'), - shape=(10, 20, 20), - dtype=np.uint8, - affine=np.array( - [ - [0, 0, 2.0, -20], - [0, 2.0, 0, -20], - [2.0, 0, 0, -10], - [0, 0, 0, 1], - ] - ), - zooms=(2.0, 2.0, 2.0), - # These values from mincstats - data_summary=dict(min=0.2082842439, max=0.2094327615, mean=0.2091292083), - is_proxy=True, - ), - dict( - fname=pjoin(data_path, 'minc1_4d.mnc'), - shape=(2, 10, 20, 20), - dtype=np.uint8, - affine=np.array( - [ - [0, 0, 2.0, -20], - [0, 2.0, 0, -20], - [2.0, 0, 0, -10], - [0, 0, 0, 1], - ] - ), - zooms=(1.0, 2.0, 2.0, 2.0), - # These values from mincstats - data_summary=dict(min=0.2078431373, max=1.498039216, mean=0.9090422837), - is_proxy=True, - ), - dict( - fname=pjoin(data_path, 'minc1-no-att.mnc'), - shape=(10, 20, 20), - dtype=np.uint8, - affine=np.array( - [ - [0, 0, 1.0, 0], - [0, 1.0, 0, 0], - [1.0, 0, 0, 0], - [0, 0, 0, 1], - ] - ), - zooms=(1.0, 1.0, 1.0), - # These values from SPM2/mincstats - data_summary=dict(min=0.20784314, max=0.74901961, mean=0.6061103), - is_proxy=True, - ), -] - - -class _TestMincFile: - module = minc1 - file_class = Minc1File - fname = EG_FNAME - opener = netcdf_file - test_files = EXAMPLE_IMAGES - - def test_mincfile(self): - for tp in self.test_files: - mnc_obj = self.opener(tp['fname'], 'r') - mnc = self.file_class(mnc_obj) - assert mnc.get_data_dtype().type == tp['dtype'] - assert mnc.get_data_shape() == tp['shape'] - assert mnc.get_zooms() == tp['zooms'] - assert_array_equal(mnc.get_affine(), tp['affine']) - data = mnc.get_scaled_data() - assert data.shape == tp['shape'] - # Can't close mmapped NetCDF with live mmap arrays - del mnc, data - - def test_mincfile_slicing(self): - # Test slicing and scaling of mincfile data - for tp in self.test_files: - mnc_obj = self.opener(tp['fname'], 'r') - mnc = self.file_class(mnc_obj) - data = mnc.get_scaled_data() - for slicedef in ( - (slice(None),), - (1,), - (slice(None), 1), - (1, slice(None)), - (slice(None), 1, 1), - (1, slice(None), 1), - (1, 1, slice(None)), - ): - sliced_data = mnc.get_scaled_data(slicedef) - assert_array_equal(sliced_data, data[slicedef]) - # Can't close mmapped NetCDF with live mmap arrays - del mnc, data - - def test_load(self): - # Check highest level load of minc works - for tp in self.test_files: - img = load(tp['fname']) - data = img.get_fdata() - assert data.shape == tp['shape'] - # min, max, mean values from read in SPM2 / minctools - assert_data_similar(data, tp) - # check if mnc can be converted to nifti - ni_img = Nifti1Image.from_image(img) - assert_array_equal(ni_img.affine, tp['affine']) - assert_array_equal(ni_img.get_fdata(), data) - - def test_array_proxy_slicing(self): - # Test slicing of array proxy - for tp in self.test_files: - img = load(tp['fname']) - arr = img.get_fdata() - prox = img.dataobj - assert prox.is_proxy - for sliceobj in slicer_samples(img.shape): - assert_array_equal(arr[sliceobj], prox[sliceobj]) - - -class TestMinc1File(_TestMincFile): - def test_compressed(self): - # we can read minc compressed - # Not so for MINC2; hence this small sub-class - for tp in self.test_files: - content = open(tp['fname'], 'rb').read() - openers_exts = [(gzip.open, '.gz'), (bz2.BZ2File, '.bz2')] - if HAVE_ZSTD: # add .zst to test if installed - openers_exts += [(pyzstd.ZstdFile, '.zst')] - with InTemporaryDirectory(): - for opener, ext in openers_exts: - fname = 'test.mnc' + ext - fobj = opener(fname, 'wb') - fobj.write(content) - fobj.close() - img = self.module.load(fname) - data = img.get_fdata() - assert_data_similar(data, tp) - del img - - -# Test the Minc header -def test_header_data_io(): - bio = BytesIO() - hdr = MincHeader() - arr = np.arange(24).reshape((2, 3, 4)) - with pytest.raises(NotImplementedError): - hdr.data_to_fileobj(arr, bio) - with pytest.raises(NotImplementedError): - hdr.data_from_fileobj(bio) - - -class TestMinc1Image(tsi.TestSpatialImage): - image_class = Minc1Image - eg_images = (pjoin(data_path, 'tiny.mnc'),) - module = minc1 - - def test_data_to_from_fileobj(self): - # Check data_from_fileobj of header raises an error - for fpath in self.eg_images: - img = self.module.load(fpath) - bio = BytesIO() - arr = np.arange(24).reshape((2, 3, 4)) - with pytest.raises(NotImplementedError): - img.header.data_to_fileobj(arr, bio) - with pytest.raises(NotImplementedError): - img.header.data_from_fileobj(bio) diff --git a/nibabel/tests/test_minc2.py b/nibabel/tests/test_minc2.py deleted file mode 100644 index 4c2973a728..0000000000 --- a/nibabel/tests/test_minc2.py +++ /dev/null @@ -1,133 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## - -from os.path import join as pjoin - -import numpy as np -import pytest - -from .. import minc2 -from ..minc2 import Minc2File, Minc2Image -from ..optpkg import optional_package -from ..testing import data_path -from . import test_minc1 as tm2 - -h5py, have_h5py, setup_module = optional_package('h5py') - -# Example images in format expected for ``test_image_api``, adding ``zooms`` -# item. -EXAMPLE_IMAGES = [ - dict( - fname=pjoin(data_path, 'small.mnc'), - shape=(18, 28, 29), - dtype=np.int16, - affine=np.array( - [ - [0, 0, 7.0, -98], - [0, 8.0, 0, -134], - [9.0, 0, 0, -72], - [0, 0, 0, 1], - ] - ), - zooms=(9.0, 8.0, 7.0), - # These values from mincstats - data_summary=dict(min=0.1185331417, max=92.87690699, mean=31.2127952), - is_proxy=True, - ), - dict( - fname=pjoin(data_path, 'minc2_1_scale.mnc'), - shape=(10, 20, 20), - dtype=np.uint8, - affine=np.array( - [ - [0, 0, 2.0, -20], - [0, 2.0, 0, -20], - [2.0, 0, 0, -10], - [0, 0, 0, 1], - ] - ), - zooms=(2.0, 2.0, 2.0), - # These values from mincstats - data_summary=dict(min=0.2082842439, max=0.2094327615, mean=0.2091292083), - is_proxy=True, - ), - dict( - fname=pjoin(data_path, 'minc2_4d.mnc'), - shape=(2, 10, 20, 20), - dtype=np.uint8, - affine=np.array( - [ - [0, 0, 2.0, -20], - [0, 2.0, 0, -20], - [2.0, 0, 0, -10], - [0, 0, 0, 1], - ] - ), - zooms=(1.0, 2.0, 2.0, 2.0), - # These values from mincstats - data_summary=dict(min=0.2078431373, max=1.498039216, mean=0.9090422837), - is_proxy=True, - ), - dict( - fname=pjoin(data_path, 'minc2-no-att.mnc'), - shape=(10, 20, 20), - dtype=np.uint8, - affine=np.array( - [ - [0, 0, 1.0, 0], - [0, 1.0, 0, 0], - [1.0, 0, 0, 0], - [0, 0, 0, 1], - ] - ), - zooms=(1.0, 1.0, 1.0), - # These values from SPM2/mincstats - data_summary=dict(min=0.20784314, max=0.74901961, mean=0.6061103), - is_proxy=True, - ), - dict( - fname=pjoin(data_path, 'minc2-4d-d.mnc'), - shape=(5, 16, 16, 16), - dtype=np.float64, - affine=np.array( - [ - [1.0, 0.0, 0.0, -6.96], - [0.0, 1.0, 0.0, -12.453], - [0.0, 0.0, 1.0, -9.48], - [0.0, 0.0, 0.0, 1.0], - ] - ), - zooms=(1.0, 1.0, 1.0, 1.0), - # These values from mincstats - data_summary=dict(min=0.0, max=5.0, mean=2.00078125), - is_proxy=True, - ), -] - -if have_h5py: - - class TestMinc2File(tm2._TestMincFile): - module = minc2 - file_class = Minc2File - opener = h5py.File - test_files = EXAMPLE_IMAGES - - class TestMinc2Image(tm2.TestMinc1Image): - image_class = Minc2Image - eg_images = (pjoin(data_path, 'small.mnc'),) - module = minc2 - - -def test_bad_diminfo(): - fname = pjoin(data_path, 'minc2_baddim.mnc') - # File has a bad spacing field 'xspace' when it should be - # `irregular`, `regular__` or absent (default to regular__). - # We interpret an invalid spacing as absent, but warn. - with pytest.warns(UserWarning): - Minc2Image.from_filename(fname) diff --git a/nibabel/tests/test_minc2_data.py b/nibabel/tests/test_minc2_data.py deleted file mode 100644 index a5ea38a8a9..0000000000 --- a/nibabel/tests/test_minc2_data.py +++ /dev/null @@ -1,167 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Test we can correctly import example MINC2_PATH files""" - -import os -from os.path import join as pjoin - -import numpy as np -from numpy.testing import assert_almost_equal, assert_array_equal - -from .. import Nifti1Image -from .. import load as top_load -from ..optpkg import optional_package -from .nibabel_data import get_nibabel_data, needs_nibabel_data - -h5py, have_h5py, setup_module = optional_package('h5py') - -MINC2_PATH = pjoin(get_nibabel_data(), 'nitest-minc2') - - -def _make_affine(coses, zooms, starts): - R = np.column_stack(coses) - Z = np.diag(zooms) - affine = np.eye(4) - affine[:3, :3] = np.dot(R, Z) - affine[:3, 3] = np.dot(R, starts) - return affine - - -class TestEPIFrame: - opener = staticmethod(top_load) - x_cos = [1, 0, 0] - y_cos = [0.0, 1, 0] - z_cos = [0, 0, 1] - zooms = [-0.8984375, -0.8984375, 3.0] - starts = [117.25609125, 138.89861125, -54.442028] - example_params = dict( - fname=os.path.join(MINC2_PATH, 'mincex_EPI-frame.mnc'), - shape=(40, 256, 256), - type=np.int16, - affine=_make_affine((z_cos, y_cos, x_cos), zooms[::-1], starts[::-1]), - zooms=[abs(v) for v in zooms[::-1]], - # These values from mincstats - min=0.0, - max=1273, - mean=93.52085367, - ) - - @needs_nibabel_data('nitest-minc2') - def test_load(self): - # Check highest level load of minc works - img = self.opener(self.example_params['fname']) - assert img.shape == self.example_params['shape'] - assert_almost_equal(img.header.get_zooms(), self.example_params['zooms'], 5) - assert_almost_equal(img.affine, self.example_params['affine'], 4) - assert img.get_data_dtype().type == self.example_params['type'] - # Check correspondence of data and recorded shape - data = img.get_fdata() - assert data.shape == self.example_params['shape'] - # min, max, mean values from read in SPM2 - assert_almost_equal(data.min(), self.example_params['min'], 4) - assert_almost_equal(data.max(), self.example_params['max'], 4) - assert_almost_equal(data.mean(), self.example_params['mean'], 4) - # check if mnc can be converted to nifti - ni_img = Nifti1Image.from_image(img) - assert_almost_equal(ni_img.affine, self.example_params['affine'], 2) - assert_array_equal(ni_img.get_fdata(), data) - - -class TestB0(TestEPIFrame): - x_cos = [0.9970527523765, 0.0, 0.0767190261828617] - y_cos = [0.0, 1.0, -6.9388939e-18] - z_cos = [-0.0767190261828617, 6.9184432614435e-18, 0.9970527523765] - zooms = [-0.8984375, -0.8984375, 6.49999990444107] - starts = [105.473101260826, 151.74885125, -61.8714747993248] - example_params = dict( - fname=os.path.join(MINC2_PATH, 'mincex_diff-B0.mnc'), - shape=(19, 256, 256), - type=np.int16, - affine=_make_affine((z_cos, y_cos, x_cos), zooms[::-1], starts[::-1]), - zooms=[abs(v) for v in zooms[::-1]], - # These values from mincstats - min=4.566971917, - max=3260.121093, - mean=163.8305553, - ) - - -class TestFA(TestEPIFrame): - example_params = TestB0.example_params.copy() - new_params = dict( - fname=os.path.join(MINC2_PATH, 'mincex_diff-FA.mnc'), - # These values from mincstats - min=0.008068881038, - max=1.224754546, - mean=0.7520087469, - ) - example_params.update(new_params) - - -class TestGado(TestEPIFrame): - x_cos = [0.999695413509548, -0.0174524064372835, 0.0174497483512505] - y_cos = [0.0174497483512505, 0.999847695156391, 0.000304586490452135] - z_cos = [-0.0174524064372835, 0.0, 0.999847695156391] - zooms = [1, -1, -1] - starts = [-75.76775, 115.80462, 81.38605] - example_params = dict( - fname=os.path.join(MINC2_PATH, 'mincex_gado-contrast.mnc'), - shape=(100, 170, 146), - type=np.int16, - affine=_make_affine((z_cos, y_cos, x_cos), zooms[::-1], starts[::-1]), - zooms=[abs(v) for v in zooms[::-1]], - # These values from mincstats - min=0, - max=938668.8698, - mean=128169.3488, - ) - - -class TestT1(TestEPIFrame): - x_cos = [1, 0, 0] - y_cos = [0, 1, 0] - z_cos = [0, 0, 1] - zooms = [1, 1, 1] - starts = [-90, -126, -12] - example_params = dict( - fname=os.path.join(MINC2_PATH, 'mincex_t1.mnc'), - shape=(110, 217, 181), - type=np.int16, - affine=_make_affine((z_cos, y_cos, x_cos), zooms[::-1], starts[::-1]), - zooms=[abs(v) for v in zooms[::-1]], - # These values from mincstats - min=0, - max=100, - mean=23.1659928, - ) - - -class TestPD(TestEPIFrame): - example_params = TestT1.example_params.copy() - new_params = dict( - fname=os.path.join(MINC2_PATH, 'mincex_pd.mnc'), - # These values from mincstats - min=0, - max=102.5024482, - mean=23.82625718, - ) - example_params.update(new_params) - - -class TestMask(TestEPIFrame): - example_params = TestT1.example_params.copy() - new_params = dict( - fname=os.path.join(MINC2_PATH, 'mincex_mask.mnc'), - type=np.uint8, - # These values from mincstats - min=0, - max=1, - mean=0.3817466618, - ) - example_params.update(new_params) diff --git a/nibabel/tests/test_mriutils.py b/nibabel/tests/test_mriutils.py deleted file mode 100644 index 02b9da5482..0000000000 --- a/nibabel/tests/test_mriutils.py +++ /dev/null @@ -1,27 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Testing mriutils module""" - -import pytest -from numpy.testing import assert_almost_equal - -from ..mriutils import MRIError, calculate_dwell_time - - -def test_calculate_dwell_time(): - # Test dwell time calculation - # This tests only that the calculation does what it appears to; needs some - # external check - assert_almost_equal(calculate_dwell_time(3.3, 2, 3), 3.3 / (42.576 * 3.4 * 3 * 3)) - # Echo train length of 1 is valid, but returns 0 dwell time - assert_almost_equal(calculate_dwell_time(3.3, 1, 3), 0) - with pytest.raises(MRIError): - calculate_dwell_time(3.3, 0, 3.0) - with pytest.raises(MRIError): - calculate_dwell_time(3.3, 2, -0.1) diff --git a/nibabel/tests/test_nibabel_data.py b/nibabel/tests/test_nibabel_data.py deleted file mode 100644 index 7e319ac3f5..0000000000 --- a/nibabel/tests/test_nibabel_data.py +++ /dev/null @@ -1,30 +0,0 @@ -"""Tests for ``get_nibabel_data``""" - -import os -from os.path import dirname, isdir, realpath -from os.path import join as pjoin - -from . import nibabel_data as nibd - -MY_DIR = dirname(__file__) - - -def setup_module(): - nibd.environ = {} - - -def teardown_module(): - nibd.environ = os.environ - - -def test_get_nibabel_data(): - # Test getting directory - local_data = realpath(pjoin(MY_DIR, '..', '..', 'nibabel-data')) - if isdir(local_data): - assert nibd.get_nibabel_data() == local_data - else: - assert nibd.get_nibabel_data() == '' - nibd.environ['NIBABEL_DATA_DIR'] = 'not_a_path' - assert nibd.get_nibabel_data() == '' - nibd.environ['NIBABEL_DATA_DIR'] = MY_DIR - assert nibd.get_nibabel_data() == MY_DIR diff --git a/nibabel/tests/test_nifti1.py b/nibabel/tests/test_nifti1.py deleted file mode 100644 index acdcb337b6..0000000000 --- a/nibabel/tests/test_nifti1.py +++ /dev/null @@ -1,1611 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Tests for nifti reading package""" - -import os -import struct -import unittest -import warnings -from io import BytesIO - -import numpy as np -import pytest -from numpy.testing import assert_almost_equal, assert_array_almost_equal, assert_array_equal - -from nibabel import nifti1 as nifti1 -from nibabel.affines import from_matvec -from nibabel.casting import have_binary128, type_info -from nibabel.eulerangles import euler2mat -from nibabel.nifti1 import ( - Nifti1DicomExtension, - Nifti1Extension, - Nifti1Extensions, - Nifti1Header, - Nifti1Image, - Nifti1Pair, - Nifti1PairHeader, - data_type_codes, - extension_codes, - load, - slice_order_codes, -) -from nibabel.optpkg import optional_package -from nibabel.pkg_info import cmp_pkg_version -from nibabel.spatialimages import HeaderDataError -from nibabel.tmpdirs import InTemporaryDirectory - -from ..freesurfer import load as mghload -from ..orientations import aff2axcodes -from ..testing import ( - bytesio_filemap, - bytesio_round_trip, - clear_and_catch_warnings, - data_path, - runif_extra_has, - suppress_warnings, -) -from . import test_analyze as tana -from . import test_spm99analyze as tspm -from .nibabel_data import get_nibabel_data, needs_nibabel_data -from .test_arraywriters import IUINT_TYPES, rt_err_estimate -from .test_orientations import ALL_ORNTS - -header_file = os.path.join(data_path, 'nifti1.hdr') -image_file = os.path.join(data_path, 'example4d.nii.gz') - -pydicom, have_dicom, _ = optional_package('pydicom') -dicom_test = unittest.skipUnless(have_dicom, 'Could not import pydicom') - - -# Example transformation matrix -R = [[0, -1, 0], [1, 0, 0], [0, 0, 1]] # rotation matrix -Z = [2.0, 3.0, 4.0] # zooms -T = [20, 30, 40] # translations -A = np.eye(4) -A[:3, :3] = np.array(R) * Z # broadcasting does the job -A[:3, 3] = T - - -class TestNifti1PairHeader(tana.TestAnalyzeHeader, tspm.HeaderScalingMixin): - header_class = Nifti1PairHeader - example_file = header_file - quat_dtype = np.float32 - supported_np_types = tana.TestAnalyzeHeader.supported_np_types.union( - (np.int8, np.uint16, np.uint32, np.int64, np.uint64, np.complex128) - ) - if have_binary128(): - supported_np_types = supported_np_types.union((np.longdouble, np.clongdouble)) - tana.add_duplicate_types(supported_np_types) - - def test_empty(self): - tana.TestAnalyzeHeader.test_empty(self) - hdr = self.header_class() - assert hdr['magic'] == hdr.pair_magic - assert hdr['scl_slope'] == 1 - assert hdr['vox_offset'] == 0 - - def test_from_eg_file(self): - hdr = self.header_class.from_fileobj(open(self.example_file, 'rb')) - assert hdr.endianness == '<' - assert hdr['magic'] == hdr.pair_magic - assert hdr['sizeof_hdr'] == self.sizeof_hdr - - def test_data_scaling(self): - # Test scaling in header - super().test_data_scaling() - hdr = self.header_class() - data = np.arange(0, 3, 0.5).reshape((1, 2, 3)) - hdr.set_data_shape(data.shape) - hdr.set_data_dtype(np.float32) - S = BytesIO() - # Writing to float dtype with scaling gives slope, intercept as (1, 0) - hdr.data_to_fileobj(data, S, rescale=True) - assert_array_equal(hdr.get_slope_inter(), (1, 0)) - rdata = hdr.data_from_fileobj(S) - assert_array_almost_equal(data, rdata) - # Writing to integer datatype with scaling gives non-identity scaling - hdr.set_data_dtype(np.int8) - hdr.set_slope_inter(1, 0) - hdr.data_to_fileobj(data, S, rescale=True) - assert not np.allclose(hdr.get_slope_inter(), (1, 0)) - rdata = hdr.data_from_fileobj(S) - assert_array_almost_equal(data, rdata) - # Without scaling does rounding, doesn't alter scaling - hdr.set_slope_inter(1, 0) - with np.errstate(invalid='ignore'): - hdr.data_to_fileobj(data, S, rescale=False) - assert_array_equal(hdr.get_slope_inter(), (1, 0)) - rdata = hdr.data_from_fileobj(S) - assert_array_almost_equal(np.round(data), rdata) - - def test_big_scaling(self): - # Test that upcasting works for huge scalefactors - # See tests for apply_read_scaling in test_volumeutils - hdr = self.header_class() - hdr.set_data_shape((2, 1, 1)) - hdr.set_data_dtype(np.int16) - sio = BytesIO() - dtt = np.float32 - # This will generate a huge scalefactor - finf = type_info(dtt) - data = np.array([finf['min'], finf['max']], dtype=dtt)[:, None, None] - hdr.data_to_fileobj(data, sio) - data_back = hdr.data_from_fileobj(sio) - assert np.allclose(data, data_back) - - def test_slope_inter(self): - hdr = self.header_class() - nan, inf, minf = np.nan, np.inf, -np.inf - HDE = HeaderDataError - assert hdr.get_slope_inter() == (1.0, 0.0) - for in_tup, exp_err, out_tup, raw_values in ( - # Null scalings - ((None, None), None, (None, None), (nan, nan)), - ((nan, None), None, (None, None), (nan, nan)), - ((None, nan), None, (None, None), (nan, nan)), - ((nan, nan), None, (None, None), (nan, nan)), - # Can only be one null - ((None, 0), HDE, (None, None), (nan, 0)), - ((nan, 0), HDE, (None, None), (nan, 0)), - ((1, None), HDE, (None, None), (1, nan)), - ((1, nan), HDE, (None, None), (1, nan)), - # Bad slope plus anything generates an error - ((0, 0), HDE, (None, None), (0, 0)), - ((0, None), HDE, (None, None), (0, nan)), - ((0, nan), HDE, (None, None), (0, nan)), - ((0, inf), HDE, (None, None), (0, inf)), - ((0, minf), HDE, (None, None), (0, minf)), - ((inf, 0), HDE, (None, None), (inf, 0)), - ((inf, None), HDE, (None, None), (inf, nan)), - ((inf, nan), HDE, (None, None), (inf, nan)), - ((inf, inf), HDE, (None, None), (inf, inf)), - ((inf, minf), HDE, (None, None), (inf, minf)), - ((minf, 0), HDE, (None, None), (minf, 0)), - ((minf, None), HDE, (None, None), (minf, nan)), - ((minf, nan), HDE, (None, None), (minf, nan)), - ((minf, inf), HDE, (None, None), (minf, inf)), - ((minf, minf), HDE, (None, None), (minf, minf)), - # Good slope and bad inter generates error for get_slope_inter - ((2, None), HDE, HDE, (2, nan)), - ((2, nan), HDE, HDE, (2, nan)), - ((2, inf), HDE, HDE, (2, inf)), - ((2, minf), HDE, HDE, (2, minf)), - # Good slope and inter - you guessed it - ((2, 0), None, (2, 0), (2, 0)), - ((2, 1), None, (2, 1), (2, 1)), - ): - hdr = self.header_class() - if not exp_err is None: - with pytest.raises(exp_err): - hdr.set_slope_inter(*in_tup) - in_list = [v if not v is None else np.nan for v in in_tup] - hdr['scl_slope'], hdr['scl_inter'] = in_list - else: - hdr.set_slope_inter(*in_tup) - if isinstance(out_tup, Exception): - with pytest.raises(out_tup): - hdr.get_slope_inter() - else: - assert hdr.get_slope_inter() == out_tup - # Check set survives through checking - hdr = self.header_class.from_header(hdr, check=True) - assert hdr.get_slope_inter() == out_tup - assert_array_equal([hdr['scl_slope'], hdr['scl_inter']], raw_values) - - def test_nifti_qfac_checks(self): - # Test qfac is 1 or -1 - hdr = self.header_class() - # 1, -1 OK - hdr['pixdim'][0] = 1 - self.log_chk(hdr, 0) - hdr['pixdim'][0] = -1 - self.log_chk(hdr, 0) - # 0 is not - hdr['pixdim'][0] = 0 - fhdr, message, raiser = self.log_chk(hdr, 20) - assert fhdr['pixdim'][0] == 1 - assert message == 'pixdim[0] (qfac) should be 1 (default) or -1; setting qfac to 1' - - def test_nifti_qsform_checks(self): - # qform, sform checks - HC = self.header_class - # qform, sform - hdr = HC() - hdr['qform_code'] = -1 - fhdr, message, raiser = self.log_chk(hdr, 30) - assert fhdr['qform_code'] == 0 - assert message == 'qform_code -1 not valid; setting to 0' - hdr = HC() - hdr['sform_code'] = -1 - fhdr, message, raiser = self.log_chk(hdr, 30) - assert fhdr['sform_code'] == 0 - assert message == 'sform_code -1 not valid; setting to 0' - - def test_nifti_xform_codes(self): - # Verify that all xform codes can be set in both qform and sform - hdr = self.header_class() - affine = np.eye(4) - for code in nifti1.xform_codes.keys(): - hdr.set_qform(affine, code) - assert hdr['qform_code'] == nifti1.xform_codes[code] - hdr.set_sform(affine, code) - assert hdr['sform_code'] == nifti1.xform_codes[code] - - # Raise KeyError on unknown code - for bad_code in (-1, 6, 10): - with pytest.raises(KeyError): - hdr.set_qform(affine, bad_code) - with pytest.raises(KeyError): - hdr.set_sform(affine, bad_code) - - def test_magic_offset_checks(self): - # magic and offset - HC = self.header_class - hdr = HC() - hdr['magic'] = 'ooh' - fhdr, message, raiser = self.log_chk(hdr, 45) - assert fhdr['magic'] == b'ooh' - assert ( - message == "magic string 'ooh' is not valid; " - 'leaving as is, but future errors are likely' - ) - # For pairs, any offset is OK, but should be divisible by 16 - # Singles need offset of at least 352 (nifti1) or 540 (nifti2) bytes, - # with the divide by 16 rule - svo = hdr.single_vox_offset - for magic, ok, bad_spm in ( - (hdr.pair_magic, 32, 40), - (hdr.single_magic, svo + 32, svo + 40), - ): - hdr['magic'] = magic - hdr['vox_offset'] = 0 - self.assert_no_log_err(hdr) - hdr['vox_offset'] = ok - self.assert_no_log_err(hdr) - hdr['vox_offset'] = bad_spm - fhdr, message, raiser = self.log_chk(hdr, 30) - assert fhdr['vox_offset'] == bad_spm - assert ( - message == f'vox offset (={bad_spm:g}) not divisible by 16, ' - 'not SPM compatible; leaving at current value' - ) - # Check minimum offset (if offset set) - hdr['magic'] = hdr.single_magic - hdr['vox_offset'] = 10 - fhdr, message, raiser = self.log_chk(hdr, 40) - assert fhdr['vox_offset'] == hdr.single_vox_offset - assert ( - message == 'vox offset 10 too low for single ' - 'file nifti1; setting to minimum value ' - 'of ' + str(hdr.single_vox_offset) - ) - - def test_freesurfer_large_vector_hack(self): - # For large vector images, Freesurfer appears to set dim[1] to -1 and - # then use glmin for the vector length (an i4) - HC = self.header_class - # The standard case - hdr = HC() - hdr.set_data_shape((2, 3, 4)) - assert hdr.get_data_shape() == (2, 3, 4) - assert hdr['glmin'] == 0 - # Just left of the freesurfer case - dim_type = hdr.template_dtype['dim'].base - glmin = hdr.template_dtype['glmin'].base - too_big = int(np.iinfo(dim_type).max) + 1 - hdr.set_data_shape((too_big - 1, 1, 1)) - assert hdr.get_data_shape() == (too_big - 1, 1, 1) - # The freesurfer case - full_shape = (too_big, 1, 1, 1, 1, 1, 1) - for dim in range(3, 8): - # First element in 'dim' field is number of dimensions - expected_dim = np.array([dim, -1, 1, 1, 1, 1, 1, 1]) - with suppress_warnings(): - hdr.set_data_shape(full_shape[:dim]) - assert hdr.get_data_shape() == full_shape[:dim] - assert_array_equal(hdr['dim'], expected_dim) - assert hdr['glmin'] == too_big - # Allow the fourth dimension to vary - with suppress_warnings(): - hdr.set_data_shape((too_big, 1, 1, 4)) - assert hdr.get_data_shape() == (too_big, 1, 1, 4) - assert_array_equal(hdr['dim'][:5], np.array([4, -1, 1, 1, 4])) - # This only works when the first 3 dimensions are -1, 1, 1 - pytest.raises(HeaderDataError, hdr.set_data_shape, (too_big,)) - pytest.raises(HeaderDataError, hdr.set_data_shape, (too_big, 1)) - pytest.raises(HeaderDataError, hdr.set_data_shape, (too_big, 1, 2)) - pytest.raises(HeaderDataError, hdr.set_data_shape, (too_big, 2, 1)) - pytest.raises(HeaderDataError, hdr.set_data_shape, (1, too_big)) - pytest.raises(HeaderDataError, hdr.set_data_shape, (1, too_big, 1)) - pytest.raises(HeaderDataError, hdr.set_data_shape, (1, 1, too_big)) - pytest.raises(HeaderDataError, hdr.set_data_shape, (1, 1, 1, too_big)) - # Outside range of glmin raises error - far_too_big = int(np.iinfo(glmin).max) + 1 - with suppress_warnings(): - hdr.set_data_shape((far_too_big - 1, 1, 1)) - assert hdr.get_data_shape() == (far_too_big - 1, 1, 1) - with pytest.raises(HeaderDataError): - hdr.set_data_shape((far_too_big, 1, 1)) - # glmin of zero raises error (implausible vector length) - hdr.set_data_shape((-1, 1, 1)) - hdr['glmin'] = 0 - with pytest.raises(HeaderDataError): - hdr.get_data_shape() - # Lists or tuples or arrays will work for setting shape - for shape in ((too_big - 1, 1, 1), (too_big, 1, 1)): - for constructor in (list, tuple, np.array): - with suppress_warnings(): - hdr.set_data_shape(constructor(shape)) - assert hdr.get_data_shape() == shape - - @needs_nibabel_data('nitest-freesurfer') - def test_freesurfer_ico7_hack(self): - HC = self.header_class - hdr = HC() - full_shape = (163842, 1, 1, 1, 1, 1, 1) - # Test that using ico7 shape automatically uses factored dimensions - for dim in range(3, 8): - expected_dim = np.array([dim, 27307, 1, 6, 1, 1, 1, 1]) - hdr.set_data_shape(full_shape[:dim]) - assert hdr.get_data_shape() == full_shape[:dim] - assert_array_equal(hdr._structarr['dim'], expected_dim) - # Only works on dimensions >= 3 - pytest.raises(HeaderDataError, hdr.set_data_shape, full_shape[:1]) - pytest.raises(HeaderDataError, hdr.set_data_shape, full_shape[:2]) - # Bad shapes - pytest.raises(HeaderDataError, hdr.set_data_shape, (163842, 2, 1)) - pytest.raises(HeaderDataError, hdr.set_data_shape, (163842, 1, 2)) - pytest.raises(HeaderDataError, hdr.set_data_shape, (1, 163842, 1)) - pytest.raises(HeaderDataError, hdr.set_data_shape, (1, 1, 163842)) - pytest.raises(HeaderDataError, hdr.set_data_shape, (1, 1, 1, 163842)) - # Test consistency of data in .mgh and mri_convert produced .nii - nitest_path = os.path.join(get_nibabel_data(), 'nitest-freesurfer') - mgh = mghload(os.path.join(nitest_path, 'fsaverage', 'surf', 'lh.orig.avg.area.mgh')) - nii = load( - os.path.join(nitest_path, 'derivative', 'fsaverage', 'surf', 'lh.orig.avg.area.nii') - ) - assert mgh.shape == nii.shape - assert_array_equal(mgh.get_fdata(), nii.get_fdata()) - assert_array_equal(nii.header._structarr['dim'][1:4], np.array([27307, 1, 6])) - # Test writing produces consistent nii files - with InTemporaryDirectory(): - nii.to_filename('test.nii') - nii2 = load('test.nii') - assert nii.shape == nii2.shape - assert_array_equal(nii.get_fdata(), nii2.get_fdata()) - assert_array_equal(nii.affine, nii2.affine) - - def test_qform_sform(self): - HC = self.header_class - hdr = HC() - assert_array_equal(hdr.get_qform(), np.eye(4)) - empty_sform = np.zeros((4, 4)) - empty_sform[-1, -1] = 1 - assert_array_equal(hdr.get_sform(), empty_sform) - assert hdr.get_qform(coded=True) == (None, 0) - assert hdr.get_sform(coded=True) == (None, 0) - # Affines with no shears - nice_aff = np.diag([2, 3, 4, 1]) - another_aff = np.diag([3, 4, 5, 1]) - # Affine with shears - nasty_aff = from_matvec(np.arange(9).reshape((3, 3)), [9, 10, 11]) - nasty_aff[0, 0] = 1 # Make full rank - fixed_aff = unshear_44(nasty_aff) - assert not np.allclose(fixed_aff, nasty_aff) - for in_meth, out_meth in ((hdr.set_qform, hdr.get_qform), (hdr.set_sform, hdr.get_sform)): - in_meth(nice_aff, 2) - aff, code = out_meth(coded=True) - assert_array_equal(aff, nice_aff) - assert code == 2 - assert_array_equal(out_meth(), nice_aff) # non coded - # Affine may be passed if code == 0, and will get set into header, - # but the returned affine with 'coded=True' will be None. - in_meth(another_aff, 0) - assert out_meth(coded=True) == (None, 0) # coded -> None - assert_array_almost_equal(out_meth(), another_aff) # else -> input - # Default qform code when previous == 0 is 2 - in_meth(nice_aff) - aff, code = out_meth(coded=True) - assert code == 2 - # Unless code was non-zero before - in_meth(nice_aff, 1) - in_meth(nice_aff) - aff, code = out_meth(coded=True) - assert code == 1 - # Can set code without modifying affine, by passing affine=None - assert_array_equal(aff, nice_aff) # affine same as before - in_meth(None, 3) - aff, code = out_meth(coded=True) - assert_array_equal(aff, nice_aff) # affine same as before - assert code == 3 - # affine is None on its own, or with code==0, resets code to 0 - in_meth(None, 0) - assert out_meth(coded=True) == (None, 0) - in_meth(None) - assert out_meth(coded=True) == (None, 0) - # List works as input - in_meth(nice_aff.tolist()) - assert_array_equal(out_meth(), nice_aff) - # Qform specifics - # inexact set (with shears) is OK - hdr.set_qform(nasty_aff, 1) - assert_array_almost_equal(hdr.get_qform(), fixed_aff) - # Unless allow_shears is False - with pytest.raises(HeaderDataError): - hdr.set_qform(nasty_aff, 1, False) - # Reset sform, give qform a code, to test sform - hdr.set_sform(None) - hdr.set_qform(nice_aff, 1) - # Check sform unchanged by setting qform - assert hdr.get_sform(coded=True) == (None, 0) - # Setting does change the sform output - hdr.set_sform(nasty_aff, 1) - aff, code = hdr.get_sform(coded=True) - assert_array_equal(aff, nasty_aff) - assert code == 1 - - def test_datatypes(self): - hdr = self.header_class() - for code in data_type_codes.value_set(): - dt = data_type_codes.type[code] - if dt == np.void: - continue - hdr.set_data_dtype(code) - assert hdr.get_data_dtype() == data_type_codes.dtype[code] - # Check that checks also see new datatypes - hdr.set_data_dtype(np.complex128) - hdr.check_fix() - - def test_quaternion(self): - hdr = self.header_class() - hdr['quatern_b'] = 0 - hdr['quatern_c'] = 0 - hdr['quatern_d'] = 0 - assert np.allclose(hdr.get_qform_quaternion(), [1.0, 0, 0, 0]) - hdr['quatern_b'] = 1 - hdr['quatern_c'] = 0 - hdr['quatern_d'] = 0 - assert np.allclose(hdr.get_qform_quaternion(), [0, 1, 0, 0]) - # Check threshold set correctly for float32 - hdr['quatern_b'] = 1 + np.finfo(self.quat_dtype).eps - assert_array_almost_equal(hdr.get_qform_quaternion(), [0, 1, 0, 0]) - - def test_qform(self): - # Test roundtrip case - ehdr = self.header_class() - ehdr.set_qform(A) - qA = ehdr.get_qform() - assert np.allclose(A, qA, atol=1e-5) - assert np.allclose(Z, ehdr['pixdim'][1:4]) - xfas = nifti1.xform_codes - assert ehdr['qform_code'] == xfas['aligned'] - ehdr.set_qform(A, 'scanner') - assert ehdr['qform_code'] == xfas['scanner'] - ehdr.set_qform(A, xfas['aligned']) - assert ehdr['qform_code'] == xfas['aligned'] - # Test pixdims[1,2,3] are checked for negatives - for dims in ((-1, 1, 1), (1, -1, 1), (1, 1, -1)): - ehdr['pixdim'][1:4] = dims - with pytest.raises(HeaderDataError): - ehdr.get_qform() - - def test_sform(self): - # Test roundtrip case - ehdr = self.header_class() - ehdr.set_sform(A) - sA = ehdr.get_sform() - assert np.allclose(A, sA, atol=1e-5) - xfas = nifti1.xform_codes - assert ehdr['sform_code'] == xfas['aligned'] - ehdr.set_sform(A, 'scanner') - assert ehdr['sform_code'] == xfas['scanner'] - ehdr.set_sform(A, xfas['aligned']) - assert ehdr['sform_code'] == xfas['aligned'] - - def test_dim_info(self): - ehdr = self.header_class() - assert ehdr.get_dim_info() == (None, None, None) - for info in ( - (0, 2, 1), - (None, None, None), - (0, 2, None), - (0, None, None), - (None, 2, 1), - (None, None, 1), - ): - ehdr.set_dim_info(*info) - assert ehdr.get_dim_info() == info - - def test_slice_times(self): - hdr = self.header_class() - # error if slice dimension not specified - with pytest.raises(HeaderDataError): - hdr.get_slice_times() - hdr.set_dim_info(slice=2) - # error if slice dimension outside shape - with pytest.raises(HeaderDataError): - hdr.get_slice_times() - hdr.set_data_shape((1, 1, 7)) - # error if slice duration not set - with pytest.raises(HeaderDataError): - hdr.get_slice_times() - 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: f'{val:2.1f}' if val is not None else 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'] - assert print_me(hdr.get_slice_times()) == [ - '0.0', - '0.1', - '0.2', - '0.3', - '0.4', - '0.5', - '0.6', - ] - hdr['slice_start'] = 1 - hdr['slice_end'] = 5 - assert print_me(hdr.get_slice_times()) == [None, '0.0', '0.1', '0.2', '0.3', '0.4', None] - hdr['slice_code'] = slice_order_codes['sequential decreasing'] - assert print_me(hdr.get_slice_times()) == [None, '0.4', '0.3', '0.2', '0.1', '0.0', None] - hdr['slice_code'] = slice_order_codes['alternating increasing'] - assert print_me(hdr.get_slice_times()) == [None, '0.0', '0.3', '0.1', '0.4', '0.2', None] - hdr['slice_code'] = slice_order_codes['alternating decreasing'] - assert print_me(hdr.get_slice_times()) == [None, '0.2', '0.4', '0.1', '0.3', '0.0', None] - hdr['slice_code'] = slice_order_codes['alternating increasing 2'] - assert print_me(hdr.get_slice_times()) == [None, '0.2', '0.0', '0.3', '0.1', '0.4', None] - hdr['slice_code'] = slice_order_codes['alternating decreasing 2'] - assert print_me(hdr.get_slice_times()) == [None, '0.4', '0.1', '0.3', '0.0', '0.2', None] - # test set - hdr = self.header_class() - hdr.set_dim_info(slice=2) - # need slice dim to correspond with shape - times = [None, 0.2, 0.4, 0.1, 0.3, 0.0, None] - with pytest.raises(HeaderDataError): - hdr.set_slice_times(times) - hdr.set_data_shape([1, 1, 7]) - with pytest.raises(HeaderDataError): - # wrong length - hdr.set_slice_times(times[:-1]) - with pytest.raises(HeaderDataError): - # all None - hdr.set_slice_times((None,) * len(times)) - n_mid_times = times.copy() - n_mid_times[3] = None - with pytest.raises(HeaderDataError): - # None in middle - hdr.set_slice_times(n_mid_times) - funny_times = times.copy() - funny_times[3] = 0.05 - with pytest.raises(HeaderDataError): - # can't get single slice duration - hdr.set_slice_times(funny_times) - hdr.set_slice_times(times) - assert hdr.get_value_label('slice_code') == 'alternating decreasing' - assert hdr['slice_start'] == 1 - assert hdr['slice_end'] == 5 - assert_array_almost_equal(hdr['slice_duration'], 0.1) - - # Ambiguous case - hdr2 = self.header_class() - hdr2.set_dim_info(slice=2) - hdr2.set_slice_duration(0.1) - hdr2.set_data_shape((1, 1, 2)) - with pytest.warns(UserWarning) as w: - hdr2.set_slice_times([0.1, 0]) - assert len(w) == 1 - # but always must be choosing sequential one first - assert hdr2.get_value_label('slice_code') == 'sequential decreasing' - # and the other direction - with pytest.warns(UserWarning) as w: - hdr2.set_slice_times([0, 0.1]) - assert len(w) == 1 - assert hdr2.get_value_label('slice_code') == 'sequential increasing' - - def test_intents(self): - ehdr = self.header_class() - ehdr.set_intent('t test', (10,), name='some score') - assert ehdr.get_intent() == ('t test', (10.0,), 'some score') - # unknown intent name or code - unknown name will fail even when - # allow_unknown=True - with pytest.raises(KeyError): - ehdr.set_intent('no intention') - with pytest.raises(KeyError): - ehdr.set_intent('no intention', allow_unknown=True) - with pytest.raises(KeyError): - ehdr.set_intent(32767) - # too many parameters - with pytest.raises(HeaderDataError): - ehdr.set_intent('t test', (10, 10)) - # too few parameters - with pytest.raises(HeaderDataError): - ehdr.set_intent('f test', (10,)) - # check unset parameters are set to 0, and name to '' - ehdr.set_intent('t test') - assert (ehdr['intent_p1'], ehdr['intent_p2'], ehdr['intent_p3']) == (0, 0, 0) - assert ehdr['intent_name'] == b'' - ehdr.set_intent('t test', (10,)) - assert (ehdr['intent_p2'], ehdr['intent_p3']) == (0, 0) - # store intent that is not in nifti1.intent_codes recoder - ehdr.set_intent(9999, allow_unknown=True) - assert ehdr.get_intent() == ('unknown code 9999', (), '') - assert ehdr.get_intent('code') == (9999, (), '') - ehdr.set_intent(9999, name='custom intent', allow_unknown=True) - assert ehdr.get_intent() == ('unknown code 9999', (), 'custom intent') - assert ehdr.get_intent('code') == (9999, (), 'custom intent') - # store unknown intent with parameters. set_intent will set the - # parameters, but get_intent won't return them - ehdr.set_intent(code=9999, params=(1, 2, 3), allow_unknown=True) - assert ehdr.get_intent() == ('unknown code 9999', (), '') - assert ehdr.get_intent('code') == (9999, (), '') - # unknown intent requires either zero, or three, parameters - with pytest.raises(HeaderDataError): - ehdr.set_intent(999, (1,), allow_unknown=True) - with pytest.raises(HeaderDataError): - ehdr.set_intent(999, (1, 2), allow_unknown=True) - - def test_set_slice_times(self): - hdr = self.header_class() - hdr.set_dim_info(slice=2) - hdr.set_data_shape([1, 1, 7]) - hdr.set_slice_duration(0.1) - times = [0] * 6 - pytest.raises(HeaderDataError, hdr.set_slice_times, times) - times = [None] * 7 - pytest.raises(HeaderDataError, hdr.set_slice_times, times) - times = [None, 0, 1, None, 3, 4, None] - pytest.raises(HeaderDataError, hdr.set_slice_times, times) - times = [None, 0, 1, 2.1, 3, 4, None] - pytest.raises(HeaderDataError, hdr.set_slice_times, times) - times = [None, 0, 4, 3, 2, 1, None] - pytest.raises(HeaderDataError, hdr.set_slice_times, times) - times = [0, 1, 2, 3, 4, 5, 6] - hdr.set_slice_times(times) - assert hdr['slice_code'] == 1 - assert hdr['slice_start'] == 0 - assert hdr['slice_end'] == 6 - assert hdr['slice_duration'] == 1.0 - times = [None, 0, 1, 2, 3, 4, None] - hdr.set_slice_times(times) - assert hdr['slice_code'] == 1 - assert hdr['slice_start'] == 1 - assert hdr['slice_end'] == 5 - assert hdr['slice_duration'] == 1.0 - times = [None, 0.4, 0.3, 0.2, 0.1, 0, None] - hdr.set_slice_times(times) - assert np.allclose(hdr['slice_duration'], 0.1) - times = [None, 4, 3, 2, 1, 0, None] - hdr.set_slice_times(times) - assert hdr['slice_code'] == 2 - times = [None, 0, 3, 1, 4, 2, None] - hdr.set_slice_times(times) - assert hdr['slice_code'] == 3 - times = [None, 2, 4, 1, 3, 0, None] - hdr.set_slice_times(times) - assert hdr['slice_code'] == 4 - times = [None, 2, 0, 3, 1, 4, None] - hdr.set_slice_times(times) - assert hdr['slice_code'] == 5 - times = [None, 4, 1, 3, 0, 2, None] - hdr.set_slice_times(times) - assert hdr['slice_code'] == 6 - - def test_xyzt_units(self): - hdr = self.header_class() - assert hdr.get_xyzt_units() == ('unknown', 'unknown') - hdr.set_xyzt_units('mm', 'sec') - assert hdr.get_xyzt_units() == ('mm', 'sec') - hdr.set_xyzt_units() - assert hdr.get_xyzt_units() == ('unknown', 'unknown') - - def test_recoded_fields(self): - hdr = self.header_class() - assert hdr.get_value_label('qform_code') == 'unknown' - hdr['qform_code'] = 3 - assert hdr.get_value_label('qform_code') == 'talairach' - assert hdr.get_value_label('sform_code') == 'unknown' - hdr['sform_code'] = 3 - assert hdr.get_value_label('sform_code') == 'talairach' - assert hdr.get_value_label('intent_code') == 'none' - hdr.set_intent('t test', (10,), name='some score') - assert hdr.get_value_label('intent_code') == 't test' - assert hdr.get_value_label('slice_code') == 'unknown' - hdr['slice_code'] = 4 # alternating decreasing - assert hdr.get_value_label('slice_code') == 'alternating decreasing' - - -def unshear_44(affine): - RZS = affine[:3, :3] - zooms = np.sqrt(np.sum(RZS * RZS, axis=0)) - R = RZS / zooms - P, S, Qs = np.linalg.svd(R) - PR = np.dot(P, Qs) - return from_matvec(PR * zooms, affine[:3, 3]) - - -class TestNifti1SingleHeader(TestNifti1PairHeader): - header_class = Nifti1Header - - def test_empty(self): - tana.TestAnalyzeHeader.test_empty(self) - hdr = self.header_class() - assert hdr['magic'] == hdr.single_magic - assert hdr['scl_slope'] == 1 - assert hdr['vox_offset'] == 0 - - def test_binblock_is_file(self): - # Override test that binary string is the same as the file on disk; in - # the case of the single file version of the header, we need to append - # the extension string (4 0s) - hdr = self.header_class() - str_io = BytesIO() - hdr.write_to(str_io) - assert str_io.getvalue() == hdr.binaryblock + b'\x00' * 4 - - def test_float128(self): - hdr = self.header_class() - # Allow for Windows visual studio where longdouble is float64 - ld_dt = np.dtype(np.longdouble) - if have_binary128() or ld_dt == np.dtype(np.float64): - hdr.set_data_dtype(np.longdouble) - assert hdr.get_data_dtype() == ld_dt - else: - with pytest.raises(HeaderDataError): - hdr.set_data_dtype(np.longdouble) - - -class TestNifti1Pair(tana.TestAnalyzeImage, tspm.ImageScalingMixin): - # Run analyze-flavor spatialimage tests - image_class = Nifti1Pair - supported_np_types = TestNifti1PairHeader.supported_np_types - - def test_int64_warning_or_error(self): - # Verify that initializing with (u)int64 data and no - # header/dtype info produces a warning/error - img_klass = self.image_class - hdr_klass = img_klass.header_class - for dtype in (np.int64, np.uint64): - data = np.arange(24, dtype=dtype).reshape((2, 3, 4)) - # Starts as a warning, transitions to error at 5.0 - if cmp_pkg_version('5.0') <= 0: - cm = pytest.raises(ValueError) - else: - cm = pytest.warns(FutureWarning) - with cm: - img_klass(data, np.eye(4)) - # No problems if we're explicit, though - with clear_and_catch_warnings(): - warnings.simplefilter('error') - img_klass(data, np.eye(4), dtype=dtype) - hdr = hdr_klass() - hdr.set_data_dtype(dtype) - img_klass(data, np.eye(4), hdr) - - def test_none_qsform(self): - # Check that affine gets set to q/sform if header is None - img_klass = self.image_class - hdr_klass = img_klass.header_class - shape = (2, 3, 4) - data = np.arange(24, dtype='f4').reshape((2, 3, 4)) - # With specified affine - aff = from_matvec(euler2mat(0.1, 0.2, 0.3), [11, 12, 13]) - for hdr in (None, hdr_klass()): - img = img_klass(data, aff, hdr) - assert_almost_equal(img.affine, aff) - assert_almost_equal(img.header.get_sform(), aff) - assert_almost_equal(img.header.get_qform(), aff) - # Even if affine is default for empty header - hdr = hdr_klass() - hdr.set_data_shape(shape) - default_aff = hdr.get_best_affine() - img = img_klass(data, default_aff, None) - assert_almost_equal(img.header.get_sform(), default_aff) - assert_almost_equal(img.header.get_qform(), default_aff) - # If affine is None, s/qform not set - img = img_klass(data, None, None) - assert_almost_equal(img.header.get_sform(), np.diag([0, 0, 0, 1])) - assert_almost_equal(img.header.get_qform(), np.eye(4)) - - def _qform_rt(self, img): - # Round trip image after setting qform, sform codes - hdr = img.header - hdr['qform_code'] = 3 - hdr['sform_code'] = 4 - # Save / reload using bytes IO objects - for value in img.file_map.values(): - value.fileobj = BytesIO() - img.to_file_map() - return img.from_file_map(img.file_map) - - def test_qform_cycle(self): - # Qform load save cycle - img_klass = self.image_class - # None affine - img = img_klass(np.zeros((2, 3, 4)), None) - hdr_back = self._qform_rt(img).header - assert hdr_back['qform_code'] == 3 - assert hdr_back['sform_code'] == 4 - # Try non-None affine - img = img_klass(np.zeros((2, 3, 4)), np.eye(4)) - hdr_back = self._qform_rt(img).header - assert hdr_back['qform_code'] == 3 - assert hdr_back['sform_code'] == 4 - # Modify affine in-place - does it hold? - img.affine[0, 0] = 9 - img.to_file_map() - img_back = img.from_file_map(img.file_map) - exp_aff = np.diag([9, 1, 1, 1]) - assert_array_equal(img_back.affine, exp_aff) - hdr_back = img.header - assert_array_equal(hdr_back.get_sform(), exp_aff) - assert_array_equal(hdr_back.get_qform(), exp_aff) - - def test_header_update_affine(self): - # Test that updating occurs only if affine is not allclose - img = self.image_class(np.zeros((2, 3, 4)), np.eye(4)) - hdr = img.header - aff = img.affine - aff[:] = np.diag([1.1, 1.1, 1.1, 1]) # inexact floats - hdr.set_qform(aff, 2) - hdr.set_sform(aff, 2) - img.update_header() - assert hdr['sform_code'] == 2 - assert hdr['qform_code'] == 2 - - def test_set_qform(self): - img = self.image_class(np.zeros((2, 3, 4)), np.diag([2.2, 3.3, 4.3, 1])) - hdr = img.header - new_affine = np.diag([1.1, 1.1, 1.1, 1]) - # Affine is same as sform (best affine) - assert_array_almost_equal(img.affine, hdr.get_best_affine()) - # Reset affine to something different again - aff_affine = np.diag([3.3, 4.5, 6.6, 1]) - img.affine[:] = aff_affine - assert_array_almost_equal(img.affine, aff_affine) - # Set qform using new_affine - img.set_qform(new_affine, 1) - assert_array_almost_equal(img.get_qform(), new_affine) - assert hdr['qform_code'] == 1 - # Image get is same as header get - assert_array_almost_equal(img.get_qform(), new_affine) - # Coded version of get gets same information - qaff, code = img.get_qform(coded=True) - assert code == 1 - assert_array_almost_equal(qaff, new_affine) - # Image affine now reset to best affine (which is sform) - assert_array_almost_equal(img.affine, hdr.get_best_affine()) - # Reset image affine and try update_affine == False - img.affine[:] = aff_affine - img.set_qform(new_affine, 1, update_affine=False) - assert_array_almost_equal(img.affine, aff_affine) - # Clear qform using None, zooms unchanged - assert_array_almost_equal(hdr.get_zooms(), [1.1, 1.1, 1.1]) - img.set_qform(None) - qaff, code = img.get_qform(coded=True) - assert (qaff, code) == (None, 0) - assert_array_almost_equal(hdr.get_zooms(), [1.1, 1.1, 1.1]) - # Best affine similarly - assert_array_almost_equal(img.affine, hdr.get_best_affine()) - # If sform is not set, qform should update affine - img.set_sform(None) - img.set_qform(new_affine, 1) - qaff, code = img.get_qform(coded=True) - assert code == 1 - assert_array_almost_equal(img.affine, new_affine) - new_affine[0, 1] = 2 - # If affine has has shear, should raise Error if strip_shears=False - img.set_qform(new_affine, 2) - with pytest.raises(HeaderDataError): - img.set_qform(new_affine, 2, False) - # Unexpected keyword raises error - with pytest.raises(TypeError): - img.get_qform(strange=True) - # updating None affine, None header does not work, because None header - # results in setting the sform to default - img = self.image_class(np.zeros((2, 3, 4)), None) - new_affine = np.eye(4) - img.set_qform(new_affine, 2) - assert_array_almost_equal(img.affine, img.header.get_best_affine()) - # Unless we unset the sform - img.set_sform(None, update_affine=True) - assert_array_almost_equal(img.affine, new_affine) - - def test_set_sform(self): - orig_aff = np.diag([2.2, 3.3, 4.3, 1]) - img = self.image_class(np.zeros((2, 3, 4)), orig_aff) - hdr = img.header - new_affine = np.diag([1.1, 1.1, 1.1, 1]) - qform_affine = np.diag([1.2, 1.2, 1.2, 1]) - # Reset image affine to something different again - aff_affine = np.diag([3.3, 4.5, 6.6, 1]) - img.affine[:] = aff_affine - assert_array_almost_equal(img.affine, aff_affine) - # Sform, Qform codes are 'aligned', 'unknown' by default - assert (hdr['sform_code'], hdr['qform_code']) == (2, 0) - # Set sform using new_affine when qform is 0 - img.set_sform(new_affine, 1) - assert hdr['sform_code'] == 1 - assert_array_almost_equal(hdr.get_sform(), new_affine) - # Image get is same as header get - assert_array_almost_equal(img.get_sform(), new_affine) - # Coded version gives same result - saff, code = img.get_sform(coded=True) - assert code == 1 - assert_array_almost_equal(saff, new_affine) - # Because we've reset the sform with update_affine, the affine changes - assert_array_almost_equal(img.affine, hdr.get_best_affine()) - # Reset image affine and try update_affine == False - img.affine[:] = aff_affine - img.set_sform(new_affine, 1, update_affine=False) - assert_array_almost_equal(img.affine, aff_affine) - # zooms do not get updated when qform is 0 - assert_array_almost_equal(img.get_qform(), orig_aff) - assert_array_almost_equal(hdr.get_zooms(), [2.2, 3.3, 4.3]) - img.set_qform(None) - assert_array_almost_equal(hdr.get_zooms(), [2.2, 3.3, 4.3]) - # Set sform using new_affine when qform is set - img.set_qform(qform_affine, 1) - img.set_sform(new_affine, 1) - saff, code = img.get_sform(coded=True) - assert code == 1 - assert_array_almost_equal(saff, new_affine) - assert_array_almost_equal(img.affine, new_affine) - # zooms follow qform - assert_array_almost_equal(hdr.get_zooms(), [1.2, 1.2, 1.2]) - # Clear sform using None, best_affine should fall back on qform - img.set_sform(None) - assert hdr['sform_code'] == 0 - assert hdr['qform_code'] == 1 - # Sform holds previous affine from last set - assert_array_almost_equal(hdr.get_sform(), saff) - # Image affine follows qform - assert_array_almost_equal(img.affine, qform_affine) - assert_array_almost_equal(hdr.get_best_affine(), img.affine) - # Unexpected keyword raises error - with pytest.raises(TypeError): - img.get_sform(strange=True) - # updating None affine should also work - img = self.image_class(np.zeros((2, 3, 4)), None) - new_affine = np.eye(4) - img.set_sform(new_affine, 2) - assert_array_almost_equal(img.affine, new_affine) - - def test_sqform_code_type(self): - # make sure get_s/qform returns codes as integers - img = self.image_class(np.zeros((2, 3, 4)), None) - assert isinstance(img.get_sform(coded=True)[1], int) - assert isinstance(img.get_qform(coded=True)[1], int) - img.set_sform(None, 3) - img.set_qform(None, 3) - assert isinstance(img.get_sform(coded=True)[1], int) - assert isinstance(img.get_qform(coded=True)[1], int) - img.set_sform(None, 2.0) - img.set_qform(None, 4.0) - assert isinstance(img.get_sform(coded=True)[1], int) - assert isinstance(img.get_qform(coded=True)[1], int) - img.set_sform(None, img.get_sform(coded=True)[1]) - img.set_qform(None, img.get_qform(coded=True)[1]) - - def test_hdr_diff(self): - # Check an offset beyond data does not raise an error - img = self.image_class(np.zeros((2, 3, 4)), np.eye(4)) - ext = dict(img.files_types)['image'] - hdr_len = len(img.header.binaryblock) - img.header['vox_offset'] = hdr_len + 400 - with InTemporaryDirectory(): - img.to_filename('another_file' + ext) - - def test_load_save(self): - IC = self.image_class - img_ext = IC.files_types[0][1] - shape = (2, 4, 6) - npt = np.float32 - data = np.arange(np.prod(shape), dtype=npt).reshape(shape) - affine = np.diag([1, 2, 3, 1]) - img = IC(data, affine) - assert img.header.get_data_offset() == 0 - assert img.shape == shape - img.set_data_dtype(npt) - img2 = bytesio_round_trip(img) - assert_array_equal(img2.get_fdata(), data) - with InTemporaryDirectory() as tmpdir: - for ext in ('', '.gz', '.bz2'): - fname = os.path.join(tmpdir, 'test' + img_ext + ext) - img.to_filename(fname) - img3 = IC.load(fname) - assert isinstance(img3, img.__class__) - assert_array_equal(img3.get_fdata(), data) - assert img3.header == img.header - assert isinstance( - np.asanyarray(img3.dataobj), np.memmap if ext == '' else np.ndarray - ) - # del to avoid windows errors of form 'The process cannot - # access the file because it is being used' - del img3 - - def test_load_pixdims(self): - # Make sure load preserves separate qform, pixdims, sform - IC = self.image_class - HC = IC.header_class - arr = np.arange(24).reshape((2, 3, 4)) - qaff = np.diag([2, 3, 4, 1]) - saff = np.diag([5, 6, 7, 1]) - hdr = HC() - hdr.set_qform(qaff) - assert_array_equal(hdr.get_qform(), qaff) - hdr.set_sform(saff) - assert_array_equal(hdr.get_sform(), saff) - simg = IC(arr, None, hdr) - img_hdr = simg.header - # Check qform, sform, pixdims are the same - assert_array_equal(img_hdr.get_qform(), qaff) - assert_array_equal(img_hdr.get_sform(), saff) - assert_array_equal(img_hdr.get_zooms(), [2, 3, 4]) - # Save to stringio - re_simg = bytesio_round_trip(simg) - assert_array_equal(re_simg.get_fdata(), arr) - # Check qform, sform, pixdims are the same - rimg_hdr = re_simg.header - assert_array_equal(rimg_hdr.get_qform(), qaff) - assert_array_equal(rimg_hdr.get_sform(), saff) - assert_array_equal(rimg_hdr.get_zooms(), [2, 3, 4]) - - def test_affines_init(self): - # Test we are doing vaguely spec-related qform things. The 'spec' here - # is some thoughts by Mark Jenkinson: - # http://nifti.nimh.nih.gov/nifti-1/documentation/nifti1fields/nifti1fields_pages/qsform_brief_usage - IC = self.image_class - arr = np.arange(24, dtype='f4').reshape((2, 3, 4)) - aff = np.diag([2, 3, 4, 1]) - # Default is sform set, qform not set - img = IC(arr, aff) - hdr = img.header - assert hdr['qform_code'] == 0 - assert hdr['sform_code'] == 2 - assert_array_equal(hdr.get_zooms(), [2, 3, 4]) - # This is also true for affines with header passed - qaff = np.diag([3, 4, 5, 1]) - saff = np.diag([6, 7, 8, 1]) - hdr.set_qform(qaff, code='scanner') - hdr.set_sform(saff, code='talairach') - assert_array_equal(hdr.get_zooms(), [3, 4, 5]) - img = IC(arr, aff, hdr) - new_hdr = img.header - # Again affine is sort of anonymous space - assert new_hdr['qform_code'] == 0 - assert new_hdr['sform_code'] == 2 - assert_array_equal(new_hdr.get_sform(), aff) - assert_array_equal(new_hdr.get_zooms(), [2, 3, 4]) - # But if no affine passed, codes and matrices stay the same - img = IC(arr, None, hdr) - new_hdr = img.header - assert new_hdr['qform_code'] == 1 # scanner - assert_array_equal(new_hdr.get_qform(), qaff) - assert new_hdr['sform_code'] == 3 # Still talairach - assert_array_equal(new_hdr.get_sform(), saff) - # Pixdims as in the original header - assert_array_equal(new_hdr.get_zooms(), [3, 4, 5]) - - def test_read_no_extensions(self): - IC = self.image_class - arr = np.arange(24, dtype='f4').reshape((2, 3, 4)) - img = IC(arr, np.eye(4)) - assert len(img.header.extensions) == 0 - img_rt = bytesio_round_trip(img) - assert len(img_rt.header.extensions) == 0 - # Check simple round trip with large offset - img.header.set_data_offset(1024) - img_rt = bytesio_round_trip(img) - assert len(img_rt.header.extensions) == 0 - - def _get_raw_scaling(self, hdr): - return hdr['scl_slope'], hdr['scl_inter'] - - def _set_raw_scaling(self, hdr, slope, inter): - # Brutal set of slope and inter - hdr['scl_slope'] = slope - hdr['scl_inter'] = inter - - def test_write_scaling(self): - # Check we can set slope, inter on write - for slope, inter, e_slope, e_inter in ( - (1, 0, 1, 0), - (2, 0, 2, 0), - (2, 1, 2, 1), - (0, 0, 1, 0), - (np.inf, 0, 1, 0), - ): - with np.errstate(invalid='ignore'): - self._check_write_scaling(slope, inter, e_slope, e_inter) - - def test_dynamic_dtype_aliases(self): - for in_dt, mn, mx, alias, effective_dt in [ - (np.uint8, 0, 255, 'compat', np.uint8), - (np.int8, 0, 127, 'compat', np.uint8), - (np.int8, -128, 127, 'compat', np.int16), - (np.int16, -32768, 32767, 'compat', np.int16), - (np.uint16, 0, 32767, 'compat', np.int16), - (np.uint16, 0, 65535, 'compat', np.int32), - (np.int32, -(2**31), 2**31 - 1, 'compat', np.int32), - (np.uint32, 0, 2**31 - 1, 'compat', np.int32), - (np.uint32, 0, 2**32 - 1, 'compat', None), - (np.int64, -(2**31), 2**31 - 1, 'compat', np.int32), - (np.uint64, 0, 2**31 - 1, 'compat', np.int32), - (np.int64, 0, 2**32 - 1, 'compat', None), - (np.uint64, 0, 2**32 - 1, 'compat', None), - (np.float32, 0, 1e30, 'compat', np.float32), - (np.float64, 0, 1e30, 'compat', np.float32), - (np.float64, 0, 1e40, 'compat', None), - (np.int64, 0, 255, 'smallest', np.uint8), - (np.int64, 0, 256, 'smallest', np.int16), - (np.int64, -1, 255, 'smallest', np.int16), - (np.int64, 0, 32768, 'smallest', np.int32), - (np.int64, 0, 4294967296, 'smallest', None), - (np.float32, 0, 1, 'smallest', None), - (np.float64, 0, 1, 'smallest', None), - ]: - arr = np.arange(24, dtype=in_dt).reshape((2, 3, 4)) - arr[0, 0, :2] = [mn, mx] - img = self.image_class(arr, np.eye(4), dtype=alias) - # Stored as alias - assert img.get_data_dtype() == alias - if effective_dt is None: - with pytest.raises(ValueError): - img.get_data_dtype(finalize=True) - continue - # Finalizing sets and clears the alias - assert img.get_data_dtype(finalize=True) == effective_dt - assert img.get_data_dtype() == effective_dt - # Re-set to alias - img.set_data_dtype(alias) - assert img.get_data_dtype() == alias - img_rt = bytesio_round_trip(img) - assert img_rt.get_data_dtype() == effective_dt - # Serializing does not finalize the source image - assert img.get_data_dtype() == alias - - def test_static_dtype_aliases(self): - for alias, effective_dt in [ - ('mask', np.uint8), - ]: - for orig_dt in ('u1', 'i8', 'f4'): - arr = np.arange(24, dtype=orig_dt).reshape((2, 3, 4)) - img = self.image_class(arr, np.eye(4), dtype=alias) - assert img.get_data_dtype() == effective_dt - img_rt = bytesio_round_trip(img) - assert img_rt.get_data_dtype() == effective_dt - - -class TestNifti1Image(TestNifti1Pair): - # Run analyze-flavor spatialimage tests - image_class = Nifti1Image - - def test_offset_errors(self): - # Test that explicit offset too low raises error - IC = self.image_class - arr = np.arange(24, dtype='f4').reshape((2, 3, 4)) - img = IC(arr, np.eye(4)) - assert img.header.get_data_offset() == 0 - # Saving with zero offset is OK - img_rt = bytesio_round_trip(img) - assert img_rt.header.get_data_offset() == 0 - # Saving with too low offset explicitly set gives error - fm = bytesio_filemap(IC) - img.header.set_data_offset(16) - with pytest.raises(HeaderDataError): - img.to_file_map(fm) - - -def test_extension_basics(): - raw = '123' - ext = Nifti1Extension('comment', raw) - assert ext.get_sizeondisk() == 16 - assert ext.get_content() == raw - assert ext.get_code() == 6 - # Test that extensions already aligned to 16 bytes are not padded - ext = Nifti1Extension('comment', b'x' * 24) - assert ext.get_sizeondisk() == 32 - - -def test_ext_eq(): - ext = Nifti1Extension('comment', '123') - assert ext == ext - assert not ext != ext - ext2 = Nifti1Extension('comment', '124') - assert ext != ext2 - assert not ext == ext2 - - -def test_extension_content_access(): - ext = Nifti1Extension('comment', b'123') - # Unmangled content access - assert ext.get_content() == b'123' - - # Raw, text and JSON access - assert ext.content == b'123' - assert ext.text == '123' - assert ext.json() == 123 - - # Encoding can be set - ext.encoding = 'ascii' - assert ext.text == '123' - - # Test that encoding errors are caught - ascii_ext = Nifti1Extension('comment', 'hôpital'.encode()) - ascii_ext.encoding = 'ascii' - with pytest.raises(UnicodeDecodeError): - ascii_ext.text - - json_ext = Nifti1Extension('unknown', b'{"a": 1}') - assert json_ext.content == b'{"a": 1}' - assert json_ext.text == '{"a": 1}' - assert json_ext.json() == {'a': 1} - - -def test_legacy_underscore_content(): - """Verify that subclasses that depended on access to ._content continue to work.""" - import io - import json - - class MyLegacyExtension(Nifti1Extension): - def _mangle(self, value): - return json.dumps(value).encode() - - def _unmangle(self, value): - if isinstance(value, bytes): - value = value.decode() - return json.loads(value) - - ext = MyLegacyExtension(0, '{}') - - assert isinstance(ext._content, dict) - # Object identity is not broken by multiple accesses - assert ext._content is ext._content - - ext._content['val'] = 1 - - fobj = io.BytesIO() - ext.write_to(fobj) - assert fobj.getvalue() == b'\x20\x00\x00\x00\x00\x00\x00\x00{"val": 1}' + bytes(14) - - -def test_extension_codes(): - for k in extension_codes.keys(): - Nifti1Extension(k, 'somevalue') - - -def test_extension_list(): - ext_c0 = Nifti1Extensions() - ext_c1 = Nifti1Extensions() - assert ext_c0 == ext_c1 - ext = Nifti1Extension('comment', '123') - ext_c1.append(ext) - assert not ext_c0 == ext_c1 - ext_c0.append(ext) - assert ext_c0 == ext_c1 - - -def test_extension_io(): - bio = BytesIO() - ext1 = Nifti1Extension(6, b'Extra comment') - ext1.write_to(bio, False) - bio.seek(0) - ebacks = Nifti1Extensions.from_fileobj(bio, -1, False) - assert len(ebacks) == 1 - assert ext1 == ebacks[0] - # Check the start is what we expect - exp_dtype = np.dtype([('esize', 'i4'), ('ecode', 'i4')]) - bio.seek(0) - buff = np.ndarray(shape=(), dtype=exp_dtype, buffer=bio.read(16)) - assert buff['esize'] == 32 - assert buff['ecode'] == 6 - # Try another extension on top - bio.seek(32) - ext2 = Nifti1Extension(6, b'Comment') - ext2.write_to(bio, False) - bio.seek(0) - ebacks = Nifti1Extensions.from_fileobj(bio, -1, False) - assert len(ebacks) == 2 - assert ext1 == ebacks[0] - assert ext2 == ebacks[1] - # Rewrite but deliberately setting esize wrongly - bio.truncate(0) - bio.seek(0) - ext1.write_to(bio, False) - bio.seek(0) - start = np.zeros((1,), dtype=exp_dtype) - start['esize'] = 24 - start['ecode'] = 6 - bio.write(start.tobytes()) - bio.seek(24) - ext2.write_to(bio, False) - # Result should still be OK, but with a warning - bio.seek(0) - with warnings.catch_warnings(record=True) as warns: - ebacks = Nifti1Extensions.from_fileobj(bio, -1, False) - assert len(warns) == 1 - assert warns[0].category == UserWarning - assert len(ebacks) == 2 - assert ext1 == ebacks[0] - assert ext2 == ebacks[1] - - -def test_nifti_extensions(): - nim = load(image_file) - # basic checks of the available extensions - hdr = nim.header - exts_container = hdr.extensions - assert len(exts_container) == 2 - assert exts_container.count('comment') == 2 - assert exts_container.count('afni') == 0 - assert exts_container.get_codes() == [6, 6] - assert (exts_container.get_sizeondisk()) % 16 == 0 - # first extension should be short one - assert exts_container[0].get_content() == b'extcomment1' - # add one - afniext = Nifti1Extension('afni', '') - exts_container.append(afniext) - assert exts_container.get_codes() == [6, 6, 4] - assert exts_container.count('comment') == 2 - assert exts_container.count('afni') == 1 - assert (exts_container.get_sizeondisk()) % 16 == 0 - # delete one - del exts_container[1] - assert exts_container.get_codes() == [6, 4] - assert exts_container.count('comment') == 1 - assert exts_container.count('afni') == 1 - - -@dicom_test -def test_nifti_dicom_extension(): - nim = load(image_file) - hdr = nim.header - exts_container = hdr.extensions - - # create an empty dataset if no content provided (to write a new header) - dcmext = Nifti1DicomExtension(2, b'') - assert dcmext.get_content().__class__ == pydicom.dataset.Dataset - assert len(dcmext.get_content().values()) == 0 - - # create an empty dataset if no content provided (to write a new header) - dcmext = Nifti1DicomExtension(2, None) - assert dcmext.get_content().__class__ == pydicom.dataset.Dataset - assert len(dcmext.get_content().values()) == 0 - - # use a dataset if provided - ds = pydicom.dataset.Dataset() - ds.add_new((0x10, 0x20), 'LO', 'NiPy') - dcmext = Nifti1DicomExtension(2, ds) - assert dcmext.get_content().__class__ == pydicom.dataset.Dataset - assert len(dcmext.get_content().values()) == 1 - assert dcmext.get_content().PatientID == 'NiPy' - - # create a single dicom tag (Patient ID, [0010,0020]) with Explicit VR / LE - dcmbytes_explicit = struct.pack('2H2sH4s', 0x10, 0x20, b'LO', 4, b'NiPy') - hdr_be = Nifti1Header(endianness='>') # Big Endian Nifti1Header - dcmext = Nifti1DicomExtension(2, dcmbytes_explicit_be, parent_hdr=hdr_be) - assert dcmext.__class__ == Nifti1DicomExtension - assert dcmext._is_implicit_VR is False - assert dcmext.get_code() == 2 - assert dcmext.get_content().PatientID == 'NiPy' - assert dcmext.get_content()[0x10, 0x20].value == 'NiPy' - assert len(dcmext.get_content().values()) == 1 - assert dcmext._mangle(dcmext.get_content()) == dcmbytes_explicit_be - assert dcmext.get_sizeondisk() % 16 == 0 - - # Check that a dicom dataset is written w/ BE encoding when not created - # using BE bytestring when given a BE nifti header - dcmext = Nifti1DicomExtension(2, ds, parent_hdr=hdr_be) - assert dcmext._mangle(dcmext.get_content()) == dcmbytes_explicit_be - - # dicom extension access from nifti extensions - assert exts_container.count('dicom') == 0 - exts_container.append(dcmext) - assert exts_container.count('dicom') == 1 - assert exts_container.get_codes() == [6, 6, 2] - assert dcmext._mangle(dcmext.get_content()) == dcmbytes_explicit_be - assert dcmext.get_sizeondisk() % 16 == 0 - - # creating an extension with bad content should raise - with pytest.raises(TypeError): - Nifti1DicomExtension(2, 0) - - -class TestNifti1General: - """Test class to test nifti1 in general - - Tests here which mix the pair and the single type, and that should only be - run once (not for each type) because they are slow - """ - - single_class = Nifti1Image - pair_class = Nifti1Pair - module = nifti1 - example_file = image_file - - def test_loadsave_cycle(self): - nim = self.module.load(self.example_file) - # ensure we have extensions - hdr = nim.header - exts_container = hdr.extensions - assert len(exts_container) > 0 - # write into the air ;-) - lnim = bytesio_round_trip(nim) - hdr = lnim.header - lexts_container = hdr.extensions - assert exts_container == lexts_container - # build int16 image - data = np.ones((2, 3, 4, 5), dtype='int16') - img = self.single_class(data, np.eye(4)) - hdr = img.header - assert hdr.get_data_dtype() == np.int16 - # default should have no scaling - assert_array_equal(hdr.get_slope_inter(), (None, None)) - # set scaling - hdr.set_slope_inter(2, 8) - assert hdr.get_slope_inter() == (2, 8) - # now build new image with updated header - wnim = self.single_class(data, np.eye(4), header=hdr) - assert wnim.get_data_dtype() == np.int16 - # Header scaling reset to default by image creation - assert wnim.header.get_slope_inter() == (None, None) - # But we can reset it again after image creation - wnim.header.set_slope_inter(2, 8) - assert wnim.header.get_slope_inter() == (2, 8) - # write into the air again ;-) - lnim = bytesio_round_trip(wnim) - assert lnim.get_data_dtype() == np.int16 - # Scaling applied - assert_array_equal(lnim.get_fdata(), data * 2.0 + 8.0) - # slope, inter reset by image creation, but saved in proxy - assert lnim.header.get_slope_inter() == (None, None) - assert (lnim.dataobj.slope, lnim.dataobj.inter) == (2, 8) - - def test_load(self): - # test module level load. We try to load a nii and an .img and a .hdr - # and expect to get a nifti back of single or pair type - arr = np.arange(24, dtype='f4').reshape((2, 3, 4)) - aff = np.diag([2, 3, 4, 1]) - simg = self.single_class(arr, aff) - pimg = self.pair_class(arr, aff) - save = self.module.save - load = self.module.load - with InTemporaryDirectory(): - for img in (simg, pimg): - save(img, 'test.nii') - assert_array_equal(arr, load('test.nii').get_fdata()) - save(simg, 'test.img') - assert_array_equal(arr, load('test.img').get_fdata()) - save(simg, 'test.hdr') - assert_array_equal(arr, load('test.hdr').get_fdata()) - - def test_float_int_min_max(self): - # Conversion between float and int - # Parallel test to arraywriters - aff = np.eye(4) - for in_dt in (np.float32, np.float64): - finf = type_info(in_dt) - arr = np.array([finf['min'], finf['max']], dtype=in_dt) - for out_dt in IUINT_TYPES: - img = self.single_class(arr, aff) - img_back = bytesio_round_trip(img) - arr_back_sc = img_back.get_fdata() - assert np.allclose(arr, arr_back_sc) - - def test_float_int_spread(self): - # Test rounding error for spread of values - # Parallel test to arraywriters - powers = np.arange(-10, 10, 0.5) - arr = np.concatenate((-(10**powers), 10**powers)) - aff = np.eye(4) - for in_dt in (np.float32, np.float64): - arr_t = arr.astype(in_dt) - for out_dt in IUINT_TYPES: - img = self.single_class(arr_t, aff) - img_back = bytesio_round_trip(img) - arr_back_sc = img_back.get_fdata() - slope, inter = img_back.header.get_slope_inter() - # Get estimate for error - max_miss = rt_err_estimate(arr_t, arr_back_sc.dtype, slope, inter) - # Simulate allclose test with large atol - diff = np.abs(arr_t - arr_back_sc) - rdiff = diff / np.abs(arr_t) - assert np.all((diff <= max_miss) | (rdiff <= 1e-5)) - - def test_rt_bias(self): - # Check for bias in round trip - # Parallel test to arraywriters - rng = np.random.RandomState(20111214) - mu, std, count = 100, 10, 100 - arr = rng.normal(mu, std, size=(count,)) - eps = np.finfo(np.float32).eps - aff = np.eye(4) - for in_dt in (np.float32, np.float64): - arr_t = arr.astype(in_dt) - for out_dt in IUINT_TYPES: - img = self.single_class(arr_t, aff) - img_back = bytesio_round_trip(img) - arr_back_sc = img_back.get_fdata() - slope, inter = img_back.header.get_slope_inter() - bias = np.mean(arr_t - arr_back_sc) - # Get estimate for error - max_miss = rt_err_estimate(arr_t, arr_back_sc.dtype, slope, inter) - # Hokey use of max_miss as a std estimate - bias_thresh = np.max([max_miss / np.sqrt(count), eps]) - assert np.abs(bias) < bias_thresh - - def test_reoriented_dim_info(self): - # Check that dim_info is reoriented correctly - arr = np.arange(24, dtype='f4').reshape((2, 3, 4)) - # Start as RAS - aff = np.diag([2, 3, 4, 1]) - simg = self.single_class(arr, aff) - for freq, phas, slic in ( - (0, 1, 2), - (0, 2, 1), - (1, 0, 2), - (2, 0, 1), - (None, None, None), - (0, 2, None), - (0, None, None), - (None, 2, 1), - (None, None, 1), - ): - simg.header.set_dim_info(freq, phas, slic) - fdir = 'RAS'[freq] if freq is not None else None - pdir = 'RAS'[phas] if phas is not None else None - sdir = 'RAS'[slic] if slic is not None else None - for ornt in ALL_ORNTS: - rimg = simg.as_reoriented(np.array(ornt)) - axcode = aff2axcodes(rimg.affine) - dirs = ''.join(axcode).replace('P', 'A').replace('I', 'S').replace('L', 'R') - new_freq, new_phas, new_slic = rimg.header.get_dim_info() - new_fdir = dirs[new_freq] if new_freq is not None else None - new_pdir = dirs[new_phas] if new_phas is not None else None - new_sdir = dirs[new_slic] if new_slic is not None else None - assert (new_fdir, new_pdir, new_sdir) == (fdir, pdir, sdir) - - -@runif_extra_has('slow') -def test_large_nifti1(): - image_shape = (91, 109, 91, 1200) - img = Nifti1Image(np.ones(image_shape, dtype=np.float32), affine=np.eye(4)) - # Dump and load the large image. - with InTemporaryDirectory(): - img.to_filename('test.nii.gz') - del img - data = load('test.nii.gz').get_fdata() - # Check that the data are all ones - assert image_shape == data.shape - n_ones = np.sum(data == 1.0) - assert np.prod(image_shape) == n_ones diff --git a/nibabel/tests/test_nifti2.py b/nibabel/tests/test_nifti2.py deleted file mode 100644 index 01d44c1595..0000000000 --- a/nibabel/tests/test_nifti2.py +++ /dev/null @@ -1,112 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Tests for nifti2 reading package""" - -import os - -import numpy as np -from numpy.testing import assert_array_equal - -from .. import nifti2 -from ..nifti1 import Nifti1Extension, Nifti1Header, Nifti1PairHeader -from ..nifti2 import Nifti2Header, Nifti2Image, Nifti2Pair, Nifti2PairHeader -from ..testing import data_path -from . import test_nifti1 as tn1 - -header_file = os.path.join(data_path, 'nifti2.hdr') -image_file = os.path.join(data_path, 'example_nifti2.nii.gz') - - -class _Nifti2Mixin: - example_file = header_file - sizeof_hdr = Nifti2Header.sizeof_hdr - quat_dtype = np.float64 - - def test_freesurfer_large_vector_hack(self): - # Disable this check - pass - - def test_freesurfer_ico7_hack(self): - # Disable this check - pass - - def test_eol_check(self): - # Check checking of EOL check field - HC = self.header_class - hdr = HC() - good_eol = (13, 10, 26, 10) - assert_array_equal(hdr['eol_check'], good_eol) - hdr['eol_check'] = 0 - fhdr, message, raiser = self.log_chk(hdr, 20) - assert_array_equal(fhdr['eol_check'], good_eol) - assert message == 'EOL check all 0; setting EOL check to 13, 10, 26, 10' - hdr['eol_check'] = (13, 10, 0, 10) - fhdr, message, raiser = self.log_chk(hdr, 40) - assert_array_equal(fhdr['eol_check'], good_eol) - assert ( - message == 'EOL check not 0 or 13, 10, 26, 10; ' - 'data may be corrupted by EOL conversion; ' - 'setting EOL check to 13, 10, 26, 10' - ) - - -class TestNifti2PairHeader(_Nifti2Mixin, tn1.TestNifti1PairHeader): - header_class = Nifti2PairHeader - example_file = header_file - - -class TestNifti2SingleHeader(_Nifti2Mixin, tn1.TestNifti1SingleHeader): - header_class = Nifti2Header - example_file = header_file - - -class TestNifti2Image(tn1.TestNifti1Image): - # Run analyze-flavor spatialimage tests - image_class = Nifti2Image - - -class TestNifti2Pair(tn1.TestNifti1Pair): - # Run analyze-flavor spatialimage tests - image_class = Nifti2Pair - - -class TestNifti2General(tn1.TestNifti1General): - """Test class to test nifti2 in general - - Tests here which mix the pair and the single type, and that should only be - run once (not for each type) because they are slow - """ - - single_class = Nifti2Image - pair_class = Nifti2Pair - module = nifti2 - example_file = image_file - - -def test_nifti12_conversion(): - shape = (2, 3, 4) - dtype_type = np.int64 - ext1 = Nifti1Extension(6, b'My comment') - ext2 = Nifti1Extension(6, b'Fresh comment') - for in_type, out_type in ( - (Nifti1Header, Nifti2Header), - (Nifti1PairHeader, Nifti2Header), - (Nifti1PairHeader, Nifti2PairHeader), - (Nifti2Header, Nifti1Header), - (Nifti2PairHeader, Nifti1Header), - (Nifti2PairHeader, Nifti1PairHeader), - ): - in_hdr = in_type() - in_hdr.set_data_shape(shape) - in_hdr.set_data_dtype(dtype_type) - in_hdr.extensions[:] = [ext1, ext2] - out_hdr = out_type.from_header(in_hdr) - assert out_hdr.get_data_shape() == shape - assert out_hdr.get_data_dtype() == dtype_type - assert in_hdr.extensions == out_hdr.extensions diff --git a/nibabel/tests/test_onetime.py b/nibabel/tests/test_onetime.py deleted file mode 100644 index d6b4579534..0000000000 --- a/nibabel/tests/test_onetime.py +++ /dev/null @@ -1,47 +0,0 @@ -from functools import cached_property - -from nibabel.onetime import ResetMixin, setattr_on_read -from nibabel.testing import deprecated_to, expires - - -class A(ResetMixin): - @cached_property - def y(self): - return self.x / 2.0 - - @cached_property - def z(self): - return self.x / 3.0 - - def __init__(self, x=1.0): - self.x = x - - -@expires('5.0.0') -def test_setattr_on_read(): - with deprecated_to('5.0.0'): - - class MagicProp: - @setattr_on_read - def a(self): - return object() - - x = MagicProp() - assert 'a' not in x.__dict__ - obj = x.a - assert 'a' in x.__dict__ - # Each call to object() produces a unique object. Verify we get the same one every time. - assert x.a is obj - - -def test_ResetMixin(): - a = A(10) - assert 'y' not in a.__dict__ - assert a.y == 5 - assert 'y' in a.__dict__ - a.x = 20 - assert a.y == 5 - # Call reset and no error should be raised even though z was never accessed - a.reset() - assert 'y' not in a.__dict__ - assert a.y == 10 diff --git a/nibabel/tests/test_openers.py b/nibabel/tests/test_openers.py deleted file mode 100644 index 05d0e04cd0..0000000000 --- a/nibabel/tests/test_openers.py +++ /dev/null @@ -1,478 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Test for openers module""" - -import contextlib -import hashlib -import os -import time -import unittest -from gzip import GzipFile -from io import BytesIO, UnsupportedOperation -from unittest import mock - -import pytest -from packaging.version import Version - -from ..openers import HAVE_INDEXED_GZIP, BZ2File, DeterministicGzipFile, ImageOpener, Opener -from ..optpkg import optional_package -from ..tmpdirs import InTemporaryDirectory - -pyzstd, HAVE_ZSTD, _ = optional_package('pyzstd') - - -class Lunk: - # bare file-like for testing - closed = False - - def __init__(self, message): - self.message = message - - def write(self): - pass - - def read(self, size=-1, /): - return self.message - - -def test_Opener(): - # Test default mode is 'rb' - fobj = Opener(__file__) - assert fobj.mode == 'rb' - fobj.close() - # That it's a context manager - with Opener(__file__) as fobj: - assert fobj.mode == 'rb' - # That we can set the mode - with Opener(__file__, 'r') as fobj: - assert fobj.mode == 'r' - # with keyword arguments - with Opener(__file__, mode='r') as fobj: - assert fobj.mode == 'r' - # fileobj returns fileobj passed through - message = b"Wine? Wouldn't you?" - for obj in (BytesIO(message), Lunk(message)): - with Opener(obj) as fobj: - assert fobj.read() == message - # Which does not close the object - assert not obj.closed - # mode is gently ignored - fobj = Opener(obj, mode='r') - - -def test_Opener_various(): - # Check we can do all sorts of files here - message = b'Oh what a giveaway' - bz2_fileno = hasattr(BZ2File, 'fileno') - if HAVE_INDEXED_GZIP: - import indexed_gzip as igzip - with InTemporaryDirectory(): - sobj = BytesIO() - files_to_test = ['test.txt', 'test.txt.gz', 'test.txt.bz2', sobj] - if HAVE_ZSTD: - files_to_test += ['test.txt.zst'] - for input in files_to_test: - with Opener(input, 'wb') as fobj: - fobj.write(message) - assert fobj.tell() == len(message) - if input == sobj: - input.seek(0) - with Opener(input, 'rb') as fobj: - message_back = fobj.read() - assert message == message_back - if input == sobj: - # Fileno is unsupported for BytesIO - with pytest.raises(UnsupportedOperation): - fobj.fileno() - elif input.endswith('.bz2') and not bz2_fileno: - with pytest.raises(AttributeError): - fobj.fileno() - # indexed gzip is used by default, and drops file - # handles by default, so we don't have a fileno. - elif ( - input.endswith('gz') - and HAVE_INDEXED_GZIP - and Version(igzip.__version__) >= Version('0.7.0') - ): - with pytest.raises(igzip.NoHandleError): - fobj.fileno() - else: - # Just check there is a fileno - assert fobj.fileno() != 0 - - -class MockIndexedGzipFile(GzipFile): - def __init__(self, *args, **kwargs): - self._drop_handles = kwargs.pop('drop_handles', False) - super().__init__(*args, **kwargs) - - -@contextlib.contextmanager -def patch_indexed_gzip(state): - # Make it look like we do (state==True) or do not (state==False) have - # the indexed gzip module. - if state: - values = (True, MockIndexedGzipFile) - else: - values = (False, GzipFile) - with ( - mock.patch('nibabel.openers.HAVE_INDEXED_GZIP', values[0]), - mock.patch('nibabel.openers.IndexedGzipFile', values[1], create=True), - ): - yield - - -def test_Opener_gzip_type(tmp_path): - # Test that GzipFile or IndexedGzipFile are used as appropriate - - data = b'this is some test data' - fname = tmp_path / 'test.gz' - - # make some test data - with GzipFile(fname, mode='wb') as f: - f.write(data) - - # Each test is specified by a tuple containing: - # (indexed_gzip present, Opener kwargs, expected file type) - tests = [ - (False, {'mode': 'rb', 'keep_open': True}, GzipFile), - (False, {'mode': 'rb', 'keep_open': False}, GzipFile), - (False, {'mode': 'wb', 'keep_open': True}, GzipFile), - (False, {'mode': 'wb', 'keep_open': False}, GzipFile), - (True, {'mode': 'rb', 'keep_open': True}, MockIndexedGzipFile), - (True, {'mode': 'rb', 'keep_open': False}, MockIndexedGzipFile), - (True, {'mode': 'wb', 'keep_open': True}, GzipFile), - (True, {'mode': 'wb', 'keep_open': False}, GzipFile), - ] - - for test in tests: - igzip_present, kwargs, expected = test - with patch_indexed_gzip(igzip_present): - opener = Opener(fname, **kwargs) - assert isinstance(opener.fobj, expected) - # Explicit close to appease Windows - del opener - - -class TestImageOpener(unittest.TestCase): - def test_vanilla(self): - # Test that ImageOpener does add '.mgz' as gzipped file type - with InTemporaryDirectory(): - with ImageOpener('test.gz', 'w') as fobj: - assert hasattr(fobj.fobj, 'compress') - with ImageOpener('test.mgz', 'w') as fobj: - assert hasattr(fobj.fobj, 'compress') - - @mock.patch.dict('nibabel.openers.ImageOpener.compress_ext_map') - def test_new_association(self): - def file_opener(fileish, mode): - return open(fileish, mode) - - # Add the association - n_associations = len(ImageOpener.compress_ext_map) - ImageOpener.compress_ext_map['.foo'] = (file_opener, ('mode',)) - assert n_associations + 1 == len(ImageOpener.compress_ext_map) - assert '.foo' in ImageOpener.compress_ext_map - - with InTemporaryDirectory(): - with ImageOpener('test.foo', 'w'): - pass - assert os.path.exists('test.foo') - - # Check this doesn't add anything to parent - assert '.foo' not in Opener.compress_ext_map - - -def test_file_like_wrapper(): - # Test wrapper using BytesIO (full API) - message = b'History of the nude in' - sobj = BytesIO() - fobj = Opener(sobj) - assert fobj.tell() == 0 - fobj.write(message) - assert fobj.tell() == len(message) - fobj.seek(0) - assert fobj.tell() == 0 - assert fobj.read(6) == message[:6] - assert not fobj.closed - fobj.close() - assert fobj.closed - # Added the fileobj name - assert fobj.name is None - - -def test_compressionlevel(): - # Check default and set compression level - with open(__file__, 'rb') as fobj: - my_self = fobj.read() - # bzip2 needs a fairly large file to show differences in compression level - many_selves = my_self * 50 - # Test we can set default compression at class level - - class MyOpener(Opener): - default_compresslevel = 5 - - with InTemporaryDirectory(): - for ext in ('gz', 'bz2', 'GZ', 'gZ', 'BZ2', 'Bz2'): - for opener, default_val in ((Opener, 1), (MyOpener, 5)): - sizes = {} - for compresslevel in ('default', 1, 5): - fname = 'test.' + ext - kwargs = {'mode': 'wb'} - if compresslevel != 'default': - kwargs['compresslevel'] = compresslevel - with opener(fname, **kwargs) as fobj: - fobj.write(many_selves) - with open(fname, 'rb') as fobj: - my_selves_smaller = fobj.read() - sizes[compresslevel] = len(my_selves_smaller) - assert sizes['default'] == sizes[default_val] - assert sizes[1] > sizes[5] - - -def test_compressed_ext_case(): - # Test openers usually ignore case for compressed exts - contents = b'palindrome of Bolton is notlob' - - class StrictOpener(Opener): - compress_ext_icase = False - - exts = ('gz', 'bz2', 'GZ', 'gZ', 'BZ2', 'Bz2') - if HAVE_ZSTD: - exts += ('zst', 'ZST', 'Zst') - with InTemporaryDirectory(): - # Make a basic file to check type later - with open(__file__, 'rb') as a_file: - file_class = type(a_file) - for ext in exts: - fname = 'test.' + ext - with Opener(fname, 'wb') as fobj: - fobj.write(contents) - with Opener(fname, 'rb') as fobj: - assert fobj.read() == contents - os.unlink(fname) - with StrictOpener(fname, 'wb') as fobj: - fobj.write(contents) - with StrictOpener(fname, 'rb') as fobj: - assert fobj.read() == contents - lext = ext.lower() - if lext != ext: # extension should not be recognized -> file - assert isinstance(fobj.fobj, file_class) - elif lext == 'gz': - try: - from ..openers import IndexedGzipFile - except ImportError: - IndexedGzipFile = GzipFile - assert isinstance(fobj.fobj, (GzipFile, IndexedGzipFile)) - elif lext == 'zst': - assert isinstance(fobj.fobj, pyzstd.ZstdFile) - else: - assert isinstance(fobj.fobj, BZ2File) - - -def test_name(): - # The wrapper gives everything a name, maybe None - sobj = BytesIO() - lunk = Lunk('in ART') - with InTemporaryDirectory(): - files_to_test = ['test.txt', 'test.txt.gz', 'test.txt.bz2', sobj, lunk] - if HAVE_ZSTD: - files_to_test += ['test.txt.zst'] - for input in files_to_test: - exp_name = input if type(input) == str else None - with Opener(input, 'wb') as fobj: - assert fobj.name == exp_name - - -def test_set_extensions(): - # Test that we can add extensions that are compressed - with InTemporaryDirectory(): - with Opener('test.gz', 'w') as fobj: - assert hasattr(fobj.fobj, 'compress') - with Opener('test.glrph', 'w') as fobj: - assert not hasattr(fobj.fobj, 'compress') - - class MyOpener(Opener): - compress_ext_map = Opener.compress_ext_map.copy() - compress_ext_map['.glrph'] = Opener.gz_def - - with MyOpener('test.glrph', 'w') as fobj: - assert hasattr(fobj.fobj, 'compress') - - -def test_close_if_mine(): - # Test that we close the file iff we opened it - with InTemporaryDirectory(): - sobj = BytesIO() - lunk = Lunk('') - for input in ('test.txt', 'test.txt.gz', 'test.txt.bz2', sobj, lunk): - fobj = Opener(input, 'wb') - # gzip objects have no 'closed' attribute - has_closed = hasattr(fobj.fobj, 'closed') - if has_closed: - assert not fobj.closed - fobj.close_if_mine() - is_str = type(input) is str - if has_closed: - assert fobj.closed == is_str - - -def test_iter(): - # Check we can iterate over lines, if the underlying file object allows it - lines = """On the -blue ridged mountains -of -virginia -""".splitlines() - with InTemporaryDirectory(): - sobj = BytesIO() - files_to_test = [ - ('test.txt', True), - ('test.txt.gz', False), - ('test.txt.bz2', False), - (sobj, True), - ] - if HAVE_ZSTD: - files_to_test += [('test.txt.zst', False)] - for input, does_t in files_to_test: - with Opener(input, 'wb') as fobj: - for line in lines: - fobj.write(str.encode(line + os.linesep)) - with Opener(input, 'rb') as fobj: - for back_line, line in zip(fobj, lines): - assert back_line.decode().rstrip() == line - if not does_t: - continue - with Opener(input, 'rt') as fobj: - for back_line, line in zip(fobj, lines): - assert back_line.rstrip() == line - lobj = Opener(Lunk('')) - with pytest.raises(TypeError): - list(lobj) - - -def md5sum(fname): - with open(fname, 'rb') as fobj: - return hashlib.md5(fobj.read()).hexdigest() - - -def test_DeterministicGzipFile(): - with InTemporaryDirectory(): - msg = b"Hello, I'd like to have an argument." - - # No filename, no mtime - with open('ref.gz', 'wb') as fobj: - with GzipFile(filename='', mode='wb', fileobj=fobj, mtime=0) as gzobj: - gzobj.write(msg) - anon_chksum = md5sum('ref.gz') - - with DeterministicGzipFile('default.gz', 'wb') as fobj: - internal_fobj = fobj.myfileobj - fobj.write(msg) - # Check that myfileobj is being closed by GzipFile.close() - # This is in case GzipFile changes its internal implementation - assert internal_fobj.closed - - assert md5sum('default.gz') == anon_chksum - - # No filename, current mtime - now = time.time() - with open('ref.gz', 'wb') as fobj: - with GzipFile(filename='', mode='wb', fileobj=fobj, mtime=now) as gzobj: - gzobj.write(msg) - now_chksum = md5sum('ref.gz') - - with DeterministicGzipFile('now.gz', 'wb', mtime=now) as fobj: - fobj.write(msg) - - assert md5sum('now.gz') == now_chksum - - # Change in default behavior - with mock.patch('time.time') as t: - t.return_value = now - - # GzipFile will use time.time() - with open('ref.gz', 'wb') as fobj: - with GzipFile(filename='', mode='wb', fileobj=fobj) as gzobj: - gzobj.write(msg) - assert md5sum('ref.gz') == now_chksum - - # DeterministicGzipFile will use 0 - with DeterministicGzipFile('now.gz', 'wb') as fobj: - fobj.write(msg) - assert md5sum('now.gz') == anon_chksum - - # GzipFile is filename dependent, DeterministicGzipFile is independent - with GzipFile('filenameA.gz', mode='wb', mtime=0) as gzobj: - gzobj.write(msg) - fnameA_chksum = md5sum('filenameA.gz') - assert fnameA_chksum != anon_chksum - - with DeterministicGzipFile('filenameA.gz', 'wb') as fobj: - fobj.write(msg) - - # But the contents are the same with different filenames - assert md5sum('filenameA.gz') == anon_chksum - - -def test_DeterministicGzipFile_fileobj(): - with InTemporaryDirectory(): - msg = b"Hello, I'd like to have an argument." - with open('ref.gz', 'wb') as fobj: - with GzipFile(filename='', mode='wb', fileobj=fobj, mtime=0) as gzobj: - gzobj.write(msg) - ref_chksum = md5sum('ref.gz') - - with open('test.gz', 'wb') as fobj: - with DeterministicGzipFile(filename='', mode='wb', fileobj=fobj) as gzobj: - gzobj.write(msg) - assert md5sum('test.gz') == ref_chksum - - with open('test.gz', 'wb') as fobj: - with DeterministicGzipFile(fileobj=fobj, mode='wb') as gzobj: - gzobj.write(msg) - assert md5sum('test.gz') == ref_chksum - - with open('test.gz', 'wb') as fobj: - with DeterministicGzipFile(filename='test.gz', mode='wb', fileobj=fobj) as gzobj: - gzobj.write(msg) - assert md5sum('test.gz') == ref_chksum - - -def test_bitwise_determinism(): - with InTemporaryDirectory(): - msg = b"Hello, I'd like to have an argument." - # Canonical reference: No filename, no mtime - # Use default compresslevel - with open('ref.gz', 'wb') as fobj: - with GzipFile(filename='', mode='wb', compresslevel=1, fileobj=fobj, mtime=0) as gzobj: - gzobj.write(msg) - anon_chksum = md5sum('ref.gz') - - # Different times, different filenames - now = time.time() - with mock.patch('time.time') as t: - t.return_value = now - with Opener('a.gz', 'wb') as fobj: - fobj.write(msg) - t.return_value = now + 1 - with Opener('b.gz', 'wb') as fobj: - fobj.write(msg) - - assert md5sum('a.gz') == anon_chksum - assert md5sum('b.gz') == anon_chksum - - # Users can still set mtime, but filenames will not be embedded - with Opener('filenameA.gz', 'wb', mtime=0xCAFE10C0) as fobj: - fobj.write(msg) - with Opener('filenameB.gz', 'wb', mtime=0xCAFE10C0) as fobj: - fobj.write(msg) - fnameA_chksum = md5sum('filenameA.gz') - fnameB_chksum = md5sum('filenameB.gz') - assert fnameA_chksum == fnameB_chksum != anon_chksum diff --git a/nibabel/tests/test_optpkg.py b/nibabel/tests/test_optpkg.py deleted file mode 100644 index c243633a07..0000000000 --- a/nibabel/tests/test_optpkg.py +++ /dev/null @@ -1,83 +0,0 @@ -"""Testing optpkg module""" - -import builtins -import sys -import types -from unittest import SkipTest, mock - -import pytest -from packaging.version import Version - -from nibabel.optpkg import optional_package -from nibabel.tripwire import TripWire, TripWireError - - -def assert_good(pkg_name, min_version=None): - pkg, have_pkg, setup = optional_package(pkg_name, min_version=min_version) - assert have_pkg - assert sys.modules[pkg_name] == pkg - assert setup() is None - - -def assert_bad(pkg_name, min_version=None): - pkg, have_pkg, setup = optional_package(pkg_name, min_version=min_version) - assert not have_pkg - assert isinstance(pkg, TripWire) - with pytest.raises(TripWireError): - pkg.a_method - with pytest.raises(SkipTest): - setup() - - -def test_basic(): - # We always have os - assert_good('os') - # Subpackage - assert_good('os.path') - # We never have package _not_a_package - assert_bad('_not_a_package') - - # Only disrupt imports for "nottriedbefore" package - orig_import = builtins.__import__ - - def raise_Exception(*args, **kwargs): - if args[0] == 'nottriedbefore': - raise Exception( - 'non ImportError could be thrown by some malfunctioning module ' - 'upon import, and optional_package should catch it too' - ) - return orig_import(*args, **kwargs) - - with mock.patch.object(builtins, '__import__', side_effect=raise_Exception): - assert_bad('nottriedbefore') - - -def test_versions(): - fake_name = '_a_fake_package' - fake_pkg = types.ModuleType(fake_name) - assert 'fake_pkg' not in sys.modules - # Not inserted yet - assert_bad(fake_name) - try: - sys.modules[fake_name] = fake_pkg - # No __version__ yet - assert_good(fake_name) # With no version check - assert_bad(fake_name, '1.0') - # We can make an arbitrary callable to check version - assert_good(fake_name, lambda pkg: True) - # Now add a version - fake_pkg.__version__ = '2.0' - # We have fake_pkg > 1.0 - for min_ver in (None, '1.0', Version('1.0'), lambda pkg: True): - assert_good(fake_name, min_ver) - # We never have fake_pkg > 100.0 - for min_ver in ('100.0', Version('100.0'), lambda pkg: False): - assert_bad(fake_name, min_ver) - # Check error string for bad version - pkg, _, _ = optional_package(fake_name, min_version='3.0') - try: - pkg.some_method - except TripWireError as err: - assert str(err) == 'These functions need _a_fake_package version >= 3.0' - finally: - del sys.modules[fake_name] diff --git a/nibabel/tests/test_orientations.py b/nibabel/tests/test_orientations.py deleted file mode 100644 index e7c32d7867..0000000000 --- a/nibabel/tests/test_orientations.py +++ /dev/null @@ -1,409 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Testing for orientations module""" - -import numpy as np -import pytest -from numpy.testing import assert_array_equal - -from ..affines import from_matvec, to_matvec -from ..orientations import ( - OrientationError, - aff2axcodes, - apply_orientation, - axcodes2ornt, - flip_axis, - inv_ornt_aff, - io_orientation, - ornt2axcodes, - ornt_transform, -) -from ..testing import deprecated_to, expires - -IN_ARRS = [ - np.eye(4), - [ - [0, 0, 1, 0], - [0, 1, 0, 0], - [1, 0, 0, 0], - [0, 0, 0, 1], - ], - [ - [0, 1, 0, 0], - [0, 0, 1, 0], - [1, 0, 0, 0], - [0, 0, 0, 1], - ], - [ - [3, 1, 0, 0], - [1, 3, 0, 0], - [0, 0, 1, 0], - [0, 0, 0, 1], - ], - [ - [1, 3, 0, 0], - [3, 1, 0, 0], - [0, 0, 1, 0], - [0, 0, 0, 1], - ], -] - -OUT_ORNTS = [ - [ - [0, 1], - [1, 1], - [2, 1], - ], - [ - [2, 1], - [1, 1], - [0, 1], - ], - [ - [2, 1], - [0, 1], - [1, 1], - ], - [ - [0, 1], - [1, 1], - [2, 1], - ], - [ - [1, 1], - [0, 1], - [2, 1], - ], -] - -IN_ARRS.extend( - [ - [np.cos(np.pi / 6 + i * np.pi / 2), np.sin(np.pi / 6 + i * np.pi / 2), 0, 0], - [-np.sin(np.pi / 6 + i * np.pi / 2), np.cos(np.pi / 6 + i * np.pi / 2), 0, 0], - [0, 0, 1, 0], - [0, 0, 0, 1], - ] - for i in range(4) -) - -OUT_ORNTS += [ - [ - [0, 1], - [1, 1], - [2, 1], - ], - [ - [1, -1], - [0, 1], - [2, 1], - ], - [ - [0, -1], - [1, -1], - [2, 1], - ], - [ - [1, 1], - [0, -1], - [2, 1], - ], -] - - -IN_ARRS = [np.array(arr) for arr in IN_ARRS] -OUT_ORNTS = [np.array(ornt) for ornt in OUT_ORNTS] - - -_LABELS = ['RL', 'AP', 'SI'] -ALL_AXCODES = [ - (_LABELS[i0][j0], _LABELS[i1][j1], _LABELS[i2][j2]) - for i0 in range(3) - for i1 in range(3) - for i2 in range(3) - if i0 != i1 != i2 != i0 - for j0 in range(2) - for j1 in range(2) - for j2 in range(2) -] - -ALL_ORNTS = [ - [[i0, j0], [i1, j1], [i2, j2]] - for i0 in range(3) - for i1 in range(3) - for i2 in range(3) - if i0 != i1 != i2 != i0 - for j0 in [1, -1] - for j1 in [1, -1] - for j2 in [1, -1] -] - - -def same_transform(taff, ornt, shape): - # Applying transformations implied by `ornt` to a made-up array - # ``arr`` of shape `shape`, results in ``t_arr``. When the point - # indices from ``arr`` are transformed by (the inverse of) `taff`, - # and we index into ``t_arr`` with these transformed points, then we - # should get the same values as we would from indexing into arr with - # the untransformed points. - shape = np.array(shape) - size = np.prod(shape) - arr = np.arange(size).reshape(shape) - # apply ornt transformations - t_arr = apply_orientation(arr, ornt) - # get all point indices in arr - i, j, k = shape - arr_pts = np.mgrid[:i, :j, :k].reshape((3, -1)) - # inverse of taff takes us from point index in arr to point index in - # t_arr - itaff = np.linalg.inv(taff) - # apply itaff so that points indexed in t_arr should correspond - o2t_pts = np.dot(itaff[:3, :3], arr_pts) + itaff[:3, 3][:, None] - assert np.allclose(np.round(o2t_pts), o2t_pts) - # fancy index out the t_arr values - vals = t_arr[tuple(o2t_pts.astype('i'))] - return np.all(vals == arr.ravel()) - - -def test_apply(): - # most tests are in ``same_transform`` above, via the - # test_io_orientations - a = np.arange(24).reshape((2, 3, 4)) - # Test 4D with an example orientation - ornt = OUT_ORNTS[-1] - t_arr = apply_orientation(a[:, :, :, None], ornt) - assert t_arr.ndim == 4 - # Orientation errors - with pytest.raises(OrientationError): - apply_orientation(a[:, :, 1], ornt) - with pytest.raises(OrientationError): - apply_orientation(a, [[0, 1], [np.nan, np.nan], [2, 1]]) - for ornt in ALL_ORNTS: - t_arr = apply_orientation(a, ornt) - assert_array_equal(a.shape, np.array(t_arr.shape)[np.array(ornt)[:, 0]]) - - -def test_io_orientation(): - for shape in ((2, 3, 4), (20, 15, 7)): - for in_arr, out_ornt in zip(IN_ARRS, OUT_ORNTS): - ornt = io_orientation(in_arr) - assert_array_equal(ornt, out_ornt) - taff = inv_ornt_aff(ornt, shape) - assert same_transform(taff, ornt, shape) - for axno in range(3): - arr = in_arr.copy() - ex_ornt = out_ornt.copy() - # flip the input axis in affine - arr[:, axno] *= -1 - # check that result shows flip - ex_ornt[axno, 1] *= -1 - ornt = io_orientation(arr) - assert_array_equal(ornt, ex_ornt) - taff = inv_ornt_aff(ornt, shape) - assert same_transform(taff, ornt, shape) - # Test nasty hang for zero columns - rzs = np.c_[np.diag([2, 3, 4, 5]), np.zeros((4, 3))] - arr = from_matvec(rzs, [15, 16, 17, 18]) - ornt = io_orientation(arr) - assert_array_equal( - ornt, - [ - [0, 1], - [1, 1], - [2, 1], - [3, 1], - [np.nan, np.nan], - [np.nan, np.nan], - [np.nan, np.nan], - ], - ) - # Test behavior of thresholding - def_aff = np.array( - [ - [1.0, 1, 0, 0], - [0, 0, 0, 0], - [0, 0, 1, 0], - [0, 0, 0, 1], - ] - ) - fail_tol = np.array( - [ - [0, 1], - [np.nan, np.nan], - [2, 1], - ] - ) - pass_tol = np.array( - [ - [0, 1], - [1, 1], - [2, 1], - ] - ) - eps = np.finfo(float).eps - # Test that a Y axis appears as we increase the difference between the - # first two columns - for y_val, has_y in ( - (0, False), - (eps, False), - (eps * 5, False), - (eps * 10, True), - ): - def_aff[1, 1] = y_val - res = pass_tol if has_y else fail_tol - assert_array_equal(io_orientation(def_aff), res) - # Test tol input argument - def_aff[1, 1] = eps - assert_array_equal(io_orientation(def_aff, tol=0), pass_tol) - def_aff[1, 1] = eps * 10 - assert_array_equal(io_orientation(def_aff, tol=1e-5), fail_tol) - # Test drop of rows, columns - mat, vec = to_matvec(def_aff) - aff_extra_col = np.zeros((4, 5)) - aff_extra_col[-1, -1] = 1 # Not strictly necessary, but for completeness - aff_extra_col[:3, :3] = mat - aff_extra_col[:3, -1] = vec - assert_array_equal( - io_orientation(aff_extra_col, tol=1e-5), - [ - [0, 1], - [np.nan, np.nan], - [2, 1], - [np.nan, np.nan], - ], - ) - aff_extra_row = np.zeros((5, 4)) - aff_extra_row[-1, -1] = 1 # Not strictly necessary, but for completeness - aff_extra_row[:3, :3] = mat - aff_extra_row[:3, -1] = vec - assert_array_equal( - io_orientation(aff_extra_row, tol=1e-5), - [ - [0, 1], - [np.nan, np.nan], - [2, 1], - ], - ) - - -def test_ornt_transform(): - assert_array_equal( - ornt_transform( - [[0, 1], [1, 1], [2, -1]], - [[1, 1], [0, 1], [2, 1]], - ), - [[1, 1], [0, 1], [2, -1]], - ) - assert_array_equal( - ornt_transform( - [[0, 1], [1, 1], [2, 1]], - [[2, 1], [0, -1], [1, 1]], - ), - [[1, -1], [2, 1], [0, 1]], - ) - # Must have same shape - with pytest.raises(ValueError): - ornt_transform([[0, 1], [1, 1]], [[0, 1], [1, 1], [2, 1]]) - - # Must be (N,2) in shape - with pytest.raises(ValueError): - ornt_transform( - [[0, 1, 1], [1, 1, 1]], - [[0, 1, 1], [1, 1, 1]], - ) - - # Target axes must exist in source - with pytest.raises(ValueError): - ornt_transform( - [[0, 1], [1, 1], [1, 1]], - [[0, 1], [1, 1], [2, 1]], - ) - - -def test_ornt2axcodes(): - # Recoding orientation to axis codes - labels = (('left', 'right'), ('back', 'front'), ('down', 'up')) - assert ornt2axcodes([[0, 1], [1, 1], [2, 1]], labels) == ('right', 'front', 'up') - assert ornt2axcodes([[0, -1], [1, -1], [2, -1]], labels) == ('left', 'back', 'down') - assert ornt2axcodes([[2, -1], [1, -1], [0, -1]], labels) == ('down', 'back', 'left') - assert ornt2axcodes([[1, 1], [2, -1], [0, 1]], labels) == ('front', 'down', 'right') - # default is RAS output directions - assert ornt2axcodes([[0, 1], [1, 1], [2, 1]]) == ('R', 'A', 'S') - # dropped axes produce None - assert ornt2axcodes([[0, 1], [np.nan, np.nan], [2, 1]]) == ('R', None, 'S') - # Non integer axes raises error - with pytest.raises(ValueError): - ornt2axcodes([[0.1, 1]]) - # As do directions not in range - with pytest.raises(ValueError): - ornt2axcodes([[0, 0]]) - - for axcodes, ornt in zip(ALL_AXCODES, ALL_ORNTS): - assert ornt2axcodes(ornt) == axcodes - - -def test_axcodes2ornt(): - # Go from axcodes back to orientations - labels = (('left', 'right'), ('back', 'front'), ('down', 'up')) - assert_array_equal(axcodes2ornt(('right', 'front', 'up'), labels), [[0, 1], [1, 1], [2, 1]]) - assert_array_equal(axcodes2ornt(('left', 'back', 'down'), labels), [[0, -1], [1, -1], [2, -1]]) - assert_array_equal(axcodes2ornt(('down', 'back', 'left'), labels), [[2, -1], [1, -1], [0, -1]]) - assert_array_equal(axcodes2ornt(('front', 'down', 'right'), labels), [[1, 1], [2, -1], [0, 1]]) - - # default is RAS output directions - default = np.c_[range(3), [1] * 3] - assert_array_equal(axcodes2ornt(('R', 'A', 'S')), default) - - # dropped axes produce None - assert_array_equal(axcodes2ornt(('R', None, 'S')), [[0, 1], [np.nan, np.nan], [2, 1]]) - - # Missing axcodes raise an error - assert_array_equal(axcodes2ornt('RAS'), default) - with pytest.raises(ValueError): - axcodes2ornt('rAS') - # None is OK as axis code - assert_array_equal(axcodes2ornt(('R', None, 'S')), [[0, 1], [np.nan, np.nan], [2, 1]]) - # Bad axis code with None also raises error. - with pytest.raises(ValueError): - axcodes2ornt(('R', None, 's')) - # Axis codes checked with custom labels - labels = ('SD', 'BF', 'lh') - assert_array_equal(axcodes2ornt('BlD', labels), [[1, -1], [2, -1], [0, 1]]) - with pytest.raises(ValueError): - axcodes2ornt('blD', labels) - - # Duplicate labels - for labels in [('SD', 'BF', 'lD'), ('SD', 'SF', 'lD')]: - with pytest.raises(ValueError): - axcodes2ornt('blD', labels) - - for axcodes, ornt in zip(ALL_AXCODES, ALL_ORNTS): - assert_array_equal(axcodes2ornt(axcodes), ornt) - - -def test_aff2axcodes(): - assert aff2axcodes(np.eye(4)) == tuple('RAS') - aff = [[0, 1, 0, 10], [-1, 0, 0, 20], [0, 0, 1, 30], [0, 0, 0, 1]] - assert aff2axcodes(aff, (('L', 'R'), ('B', 'F'), ('D', 'U'))) == ('B', 'R', 'U') - assert aff2axcodes(aff, (('L', 'R'), ('B', 'F'), ('D', 'U'))) == ('B', 'R', 'U') - - -def test_inv_ornt_aff(): - # Extra tests for inv_ornt_aff routines (also tested in - # io_orientations test) - with pytest.raises(OrientationError): - inv_ornt_aff([[0, 1], [1, -1], [np.nan, np.nan]], (3, 4, 5)) - - -@expires('5.0.0') -def test_flip_axis_deprecation(): - a = np.arange(24).reshape((2, 3, 4)) - axis = 1 - with deprecated_to('5.0.0'): - a_flipped = flip_axis(a, axis) - assert_array_equal(a_flipped, np.flip(a, axis)) diff --git a/nibabel/tests/test_parrec.py b/nibabel/tests/test_parrec.py deleted file mode 100644 index a312c558a8..0000000000 --- a/nibabel/tests/test_parrec.py +++ /dev/null @@ -1,910 +0,0 @@ -"""Testing parrec module""" - -from glob import glob -from os.path import basename, dirname -from os.path import join as pjoin -from warnings import simplefilter - -import numpy as np -import pytest -from numpy import array as npa -from numpy.testing import assert_almost_equal, assert_array_equal - -from .. import load as top_load -from .. import parrec -from ..fileholders import FileHolder -from ..nifti1 import Nifti1Extension, Nifti1Header, Nifti1Image -from ..openers import ImageOpener -from ..parrec import ( - PARRECArrayProxy, - PARRECError, - PARRECHeader, - PARRECImage, - exts2pars, - parse_PAR_header, - vol_is_full, - vol_numbers, -) -from ..testing import assert_arr_dict_equal, clear_and_catch_warnings, suppress_warnings -from ..volumeutils import array_from_file -from . import test_spatialimages as tsi -from .test_arrayproxy import check_mmap - -DATA_PATH = pjoin(dirname(__file__), 'data') -EG_PAR = pjoin(DATA_PATH, 'phantom_EPI_asc_CLEAR_2_1.PAR') -EG_REC = pjoin(DATA_PATH, 'phantom_EPI_asc_CLEAR_2_1.REC') -with ImageOpener(EG_PAR, 'rt') as _fobj: - HDR_INFO, HDR_DEFS = parse_PAR_header(_fobj) -# Fake truncated -TRUNC_PAR = pjoin(DATA_PATH, 'phantom_truncated.PAR') -TRUNC_REC = pjoin(DATA_PATH, 'phantom_truncated.REC') -# Post-processed diffusion: ADC Map -ADC_PAR = pjoin(DATA_PATH, 'ADC_Map.PAR') -# Fake V4 -V4_PAR = pjoin(DATA_PATH, 'phantom_fake_v4.PAR') -# Fake V4.1 -V41_PAR = pjoin(DATA_PATH, 'phantom_fake_v4_1.PAR') -# Fake V4.1 with dual TRs -DUAL_TR_PAR = pjoin(DATA_PATH, 'phantom_fake_dualTR.PAR') -# Anonymized PAR -ANON_PAR = pjoin(DATA_PATH, 'umass_anonymized.PAR') -# Fake varying scaling -VARY_PAR = pjoin(DATA_PATH, 'phantom_varscale.PAR') -VARY_REC = pjoin(DATA_PATH, 'phantom_varscale.REC') -# V4.2 PAR with variant field names in the header -VARIANT_PAR = pjoin(DATA_PATH, 'variant_v4_2_header.PAR') -# Affine as we determined it mid-2014 -AN_OLD_AFFINE = np.array( - [ - [-3.64994708, 0.0, 1.83564171, 123.66276611], - [0.0, -3.75, 0.0, 115.617], - [0.86045705, 0.0, 7.78655376, -27.91161211], - [0.0, 0.0, 0.0, 1.0], - ] -) -# Affine from Philips-created NIfTI -PHILIPS_AFFINE = np.array( - [ - [-3.65, -0.0016, 1.8356, 125.4881], - [0.0016, -3.75, -0.0004, 117.4916], - [0.8604, 0.0002, 7.7866, -28.3411], - [0.0, 0.0, 0.0, 1.0], - ] -) - -# Affines generated by parrec.py from test data in many orientations -# Data from http://psydata.ovgu.de/philips_achieva_testfiles/conversion2 -PREVIOUS_AFFINES = { - 'Phantom_EPI_3mm_cor_20APtrans_15RLrot_SENSE_15_1': npa( - [ - [-3.0, 0.0, 0.0, 118.5], - [0.0, -0.77645714, -3.18755523, 72.82738377], - [0.0, -2.89777748, 0.85410285, 97.80720486], - [0.0, 0.0, 0.0, 1.0], - ] - ), - 'Phantom_EPI_3mm_cor_SENSE_8_1': npa( - [ - [-3.0, 0.0, 0.0, 118.5], - [0.0, 0.0, -3.3, 64.35], - [0.0, -3.0, 0.0, 118.5], - [0.0, 0.0, 0.0, 1.0], - ] - ), - 'Phantom_EPI_3mm_sag_15AP_SENSE_13_1': npa( - [ - [0.0, 0.77645714, 3.18755523, -92.82738377], - [-3.0, 0.0, 0.0, 118.5], - [0.0, -2.89777748, 0.85410285, 97.80720486], - [0.0, 0.0, 0.0, 1.0], - ] - ), - 'Phantom_EPI_3mm_sag_15FH_SENSE_12_1': npa( - [ - [0.77645714, 0.0, 3.18755523, -92.82738377], - [-2.89777748, 0.0, 0.85410285, 97.80720486], - [0.0, -3.0, 0.0, 118.5], - [0.0, 0.0, 0.0, 1.0], - ] - ), - 'Phantom_EPI_3mm_sag_15RL_SENSE_11_1': npa( - [ - [0.0, 0.0, 3.3, -64.35], - [-2.89777748, -0.77645714, 0.0, 145.13226726], - [0.77645714, -2.89777748, 0.0, 83.79215357], - [0.0, 0.0, 0.0, 1.0], - ] - ), - 'Phantom_EPI_3mm_sag_SENSE_7_1': npa( - [ - [0.0, 0.0, 3.3, -64.35], - [-3.0, 0.0, 0.0, 118.5], - [0.0, -3.0, 0.0, 118.5], - [0.0, 0.0, 0.0, 1.0], - ] - ), - 'Phantom_EPI_3mm_tra_-30AP_10RL_20FH_SENSE_14_1': npa( - [ - [0.0, 0.0, 3.3, -74.35], - [-3.0, 0.0, 0.0, 148.5], - [0.0, -3.0, 0.0, 138.5], - [0.0, 0.0, 0.0, 1.0], - ] - ), - 'Phantom_EPI_3mm_tra_15FH_SENSE_9_1': npa( - [ - [0.77645714, 0.0, 3.18755523, -92.82738377], - [-2.89777748, 0.0, 0.85410285, 97.80720486], - [0.0, -3.0, 0.0, 118.5], - [0.0, 0.0, 0.0, 1.0], - ] - ), - 'Phantom_EPI_3mm_tra_15RL_SENSE_10_1': npa( - [ - [0.0, 0.0, 3.3, -64.35], - [-2.89777748, -0.77645714, 0.0, 145.13226726], - [0.77645714, -2.89777748, 0.0, 83.79215357], - [0.0, 0.0, 0.0, 1.0], - ] - ), - 'Phantom_EPI_3mm_tra_SENSE_6_1': npa( - [ - [-3.0, 0.0, 0.0, 118.5], - [0.0, -3.0, 0.0, 118.5], - [0.0, 0.0, 3.3, -64.35], - [0.0, 0.0, 0.0, 1.0], - ] - ), -} -# Original values for b values in DTI.PAR, still in PSL orientation -DTI_PAR_BVECS = np.array( - [ - [-0.667, -0.667, -0.333], - [-0.333, 0.667, -0.667], - [-0.667, 0.333, 0.667], - [-0.707, -0.000, -0.707], - [-0.707, 0.707, 0.000], - [-0.000, 0.707, 0.707], - [0.000, 0.000, 0.000], - [0.000, 0.000, 0.000], - ] -) - -# DTI.PAR values for bvecs -DTI_PAR_BVALS = [1000] * 6 + [0, 1000] - -EXAMPLE_IMAGES = [ - # Parameters come from load of Philips' conversion to NIfTI - # Loaded image was ``phantom_EPI_asc_CLEAR_2_1.nii`` from - # http://psydata.ovgu.de/philips_achieva_testfiles/conversion - dict( - fname=EG_PAR, - shape=(64, 64, 9, 3), - dtype=np.uint16, - # We disagree with Philips about the right affine, for the moment, so - # use our own affine as determined from a previous load in nibabel - affine=AN_OLD_AFFINE, - zooms=(3.75, 3.75, 8.0, 2.0), - data_summary=dict(min=0.0, max=2299.4110643863678, mean=194.95876256117265), - is_proxy=True, - ) -] - - -def test_top_level_load(): - # Test PARREC images can be loaded from nib.load - img = top_load(EG_PAR) - assert_almost_equal(img.affine, AN_OLD_AFFINE) - - -def test_header(): - v42_hdr = PARRECHeader(HDR_INFO, HDR_DEFS) - for strict_sort in [False, True]: - with open(V4_PAR) as fobj: - v4_hdr = PARRECHeader.from_fileobj(fobj, strict_sort=strict_sort) - with open(V41_PAR) as fobj: - v41_hdr = PARRECHeader.from_fileobj(fobj, strict_sort=strict_sort) - for hdr in (v42_hdr, v41_hdr, v4_hdr): - hdr = PARRECHeader(HDR_INFO, HDR_DEFS) - assert hdr.get_data_shape() == (64, 64, 9, 3) - assert hdr.get_data_dtype() == np.dtype('' - - gi_arr = np.asanyarray(gi) - assert gi_arr.dtype == np.dtype('u1') - assert gi_arr.shape == (6, 2) - # Tractable to write out - assert np.array_equal(gi_arr, [[0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2]]) - - shape = (2, 3, 4) - gi = ps.GridIndices(shape) - - assert gi.dtype == np.dtype('u1') - assert gi.shape == (24, 3) - assert repr(gi) == '' - - gi_arr = np.asanyarray(gi) - assert gi_arr.dtype == np.dtype('u1') - assert gi_arr.shape == (24, 3) - # Separate implementation - assert np.array_equal(gi_arr, np.mgrid[:2, :3, :4].reshape(3, -1).T) - - -class TestGrids(TestPointsets): - @pytest.mark.parametrize('shape', [(5, 5, 5), (5, 5, 5, 5), (5, 5, 5, 5, 5)]) - def test_from_image(self, shape): - # Check image is generates voxel coordinates - affine = np.diag([2, 3, 4, 1]) - img = SpatialImage(strided_scalar(shape), affine) - grid = ps.Grid.from_image(img) - grid_coords = grid.get_coords() - - assert grid.n_coords == prod(shape[:3]) - assert grid.dim == 3 - assert np.allclose(grid.affine, affine) - - assert np.allclose(grid_coords[0], [0, 0, 0]) - # Final index is [4, 4, 4], scaled by affine - assert np.allclose(grid_coords[-1], [8, 12, 16]) - - def test_from_mask(self): - affine = np.diag([2, 3, 4, 1]) - mask = np.zeros((3, 3, 3)) - mask[1, 1, 1] = 1 - img = SpatialImage(mask, affine) - - grid = ps.Grid.from_mask(img) - grid_coords = grid.get_coords() - - assert grid.n_coords == 1 - assert grid.dim == 3 - assert np.array_equal(grid_coords, [[2, 3, 4]]) - - def test_to_mask(self): - coords = np.array([[1, 1, 1]]) - - grid = ps.Grid(coords) - - mask_img = grid.to_mask() - assert mask_img.shape == (2, 2, 2) - assert np.array_equal(mask_img.get_fdata(), [[[0, 0], [0, 0]], [[0, 0], [0, 1]]]) - assert np.array_equal(mask_img.affine, np.eye(4)) - - mask_img = grid.to_mask(shape=(3, 3, 3)) - assert mask_img.shape == (3, 3, 3) - assert np.array_equal( - mask_img.get_fdata(), - [ - [[0, 0, 0], [0, 0, 0], [0, 0, 0]], - [[0, 0, 0], [0, 1, 0], [0, 0, 0]], - [[0, 0, 0], [0, 0, 0], [0, 0, 0]], - ], - ) - assert np.array_equal(mask_img.affine, np.eye(4)) diff --git a/nibabel/tests/test_processing.py b/nibabel/tests/test_processing.py deleted file mode 100644 index 7e2cc4b16d..0000000000 --- a/nibabel/tests/test_processing.py +++ /dev/null @@ -1,494 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Testing processing module""" - -import logging -import warnings -from os.path import dirname -from os.path import join as pjoin - -import numpy as np -import numpy.linalg as npl - -from nibabel.optpkg import optional_package - -spnd, have_scipy, _ = optional_package('scipy.ndimage') - -import unittest - -import pytest -from numpy.testing import assert_almost_equal, assert_array_equal - -import nibabel as nib -from nibabel.affines import AffineError, apply_affine, from_matvec, to_matvec, voxel_sizes -from nibabel.eulerangles import euler2mat -from nibabel.nifti1 import Nifti1Image -from nibabel.nifti2 import Nifti2Image -from nibabel.orientations import aff2axcodes, inv_ornt_aff -from nibabel.processing import ( - adapt_affine, - conform, - fwhm2sigma, - resample_from_to, - resample_to_output, - sigma2fwhm, - smooth_image, -) -from nibabel.testing import assert_allclose_safely -from nibabel.tests.test_spaces import assert_all_in, get_outspace_params - -needs_scipy = unittest.skipUnless(have_scipy, 'These tests need scipy') - -DATA_DIR = pjoin(dirname(__file__), 'data') - -# 3D MINC work correctly with processing, but not 4D MINC -from .test_imageclasses import MINC_3DS, MINC_4DS - -# Filenames of other images that should work correctly with processing -OTHER_IMGS = ( - 'anatomical.nii', - 'functional.nii', - 'example4d.nii.gz', - 'example_nifti2.nii.gz', - 'phantom_EPI_asc_CLEAR_2_1.PAR', -) - - -def test_sigma2fwhm(): - # Test from constant - assert_almost_equal(sigma2fwhm(1), 2.3548200) - assert_almost_equal(sigma2fwhm([1, 2, 3]), np.arange(1, 4) * 2.3548200) - assert_almost_equal(fwhm2sigma(2.3548200), 1) - assert_almost_equal(fwhm2sigma(np.arange(1, 4) * 2.3548200), [1, 2, 3]) - # direct test fwhm2sigma and sigma2fwhm are inverses of each other - fwhm = np.arange(1.0, 5.0, 0.1) - sigma = np.arange(1.0, 5.0, 0.1) - assert np.allclose(sigma2fwhm(fwhm2sigma(fwhm)), fwhm) - assert np.allclose(fwhm2sigma(sigma2fwhm(sigma)), sigma) - - -def test_adapt_affine(): - # Adapt affine to missing or extra input dimensions - aff_3d = from_matvec(np.arange(9).reshape((3, 3)), [11, 12, 13]) - # For 4x4 affine, 3D image, no-op - assert_array_equal(adapt_affine(aff_3d, 3), aff_3d) - # For 4x4 affine, 4D image, add extra identity dimension - assert_array_equal( - adapt_affine(aff_3d, 4), - [ - [0, 1, 2, 0, 11], - [3, 4, 5, 0, 12], - [6, 7, 8, 0, 13], - [0, 0, 0, 1, 0], - [0, 0, 0, 0, 1], - ], - ) - # For 5x5 affine, 4D image, identity - aff_4d = from_matvec(np.arange(16).reshape((4, 4)), [11, 12, 13, 14]) - assert_array_equal(adapt_affine(aff_4d, 4), aff_4d) - # For 4x4 affine, 2D image, dropped column - assert_array_equal( - adapt_affine(aff_3d, 2), - [ - [0, 1, 11], - [3, 4, 12], - [6, 7, 13], - [0, 0, 1], - ], - ) - # For 4x4 affine, 1D image, 2 dropped columns - assert_array_equal( - adapt_affine(aff_3d, 1), - [ - [0, 11], - [3, 12], - [6, 13], - [0, 1], - ], - ) - # For 3x3 affine, 2D image, identity - aff_2d = from_matvec(np.arange(4).reshape((2, 2)), [11, 12]) - assert_array_equal(adapt_affine(aff_2d, 2), aff_2d) - - -@needs_scipy -def test_resample_from_to(caplog): - # Test resampling from image to image / image space - data = np.arange(24, dtype='int32').reshape((2, 3, 4)) - affine = np.diag([-4, 5, 6, 1]) - img = Nifti1Image(data, affine) - img.header['descrip'] = 'red shirt image' - out = resample_from_to(img, img) - assert_almost_equal(img.dataobj, out.dataobj) - assert_array_equal(img.affine, out.affine) - # Check resampling reverses effect of flipping axes - # This will also test translations - flip_ornt = np.array([[0, 1], [1, 1], [2, 1]]) - for axis in (0, 1, 2): - ax_flip_ornt = flip_ornt.copy() - ax_flip_ornt[axis, 1] = -1 - aff_flip_i = inv_ornt_aff(ax_flip_ornt, (2, 3, 4)) - flipped_img = Nifti1Image(np.flip(data, axis), np.dot(affine, aff_flip_i)) - out = resample_from_to(flipped_img, ((2, 3, 4), affine)) - assert_almost_equal(img.dataobj, out.dataobj) - assert_array_equal(img.affine, out.affine) - # A translation of one voxel on each axis - trans_aff = from_matvec(np.diag([-4, 5, 6]), [4, -5, -6]) - trans_img = Nifti1Image(data, trans_aff) - out = resample_from_to(trans_img, img) - exp_out = np.zeros_like(data) - exp_out[:-1, :-1, :-1] = data[1:, 1:, 1:] - assert_almost_equal(out.dataobj, exp_out) - out = resample_from_to(img, trans_img) - trans_exp_out = np.zeros_like(data) - trans_exp_out[1:, 1:, 1:] = data[:-1, :-1, :-1] - assert_almost_equal(out.dataobj, trans_exp_out) - # Test mode with translation of first axis only - # Default 'constant' mode first - trans1_aff = from_matvec(np.diag([-4, 5, 6]), [4, 0, 0]) - trans1_img = Nifti1Image(data, trans1_aff) - out = resample_from_to(img, trans1_img) - exp_out = np.zeros_like(data) - exp_out[1:, :, :] = data[:-1, :, :] - assert_almost_equal(out.dataobj, exp_out) - # Then 'nearest' mode - out = resample_from_to(img, trans1_img, mode='nearest') - exp_out[0, :, :] = exp_out[1, :, :] - assert_almost_equal(out.dataobj, exp_out) - # Test order - trans_p_25_aff = from_matvec(np.diag([-4, 5, 6]), [1, 0, 0]) - trans_p_25_img = Nifti1Image(data, trans_p_25_aff) - # Surprising to me, but all points outside are set to 0, even with NN - out = resample_from_to(img, trans_p_25_img, order=0) - exp_out = np.zeros_like(data) - exp_out[1:, :, :] = data[1, :, :] - assert_almost_equal(out.dataobj, exp_out) - out = resample_from_to(img, trans_p_25_img) - with warnings.catch_warnings(): - warnings.simplefilter('ignore', UserWarning) - exp_out = spnd.affine_transform(data, [1, 1, 1], [-0.25, 0, 0], order=3) - assert_almost_equal(out.dataobj, exp_out) - # Test cval - out = resample_from_to(img, trans_img, cval=99) - exp_out = np.zeros_like(data) + 99 - exp_out[1:, 1:, 1:] = data[:-1, :-1, :-1] - assert_almost_equal(out.dataobj, exp_out) - # Out class - out = resample_from_to(img, trans_img) - assert out.__class__ == Nifti1Image - # By default, type of from_img makes no difference - n1_img = Nifti2Image(data, affine) - with caplog.at_level(logging.CRITICAL): # Here and below, suppress logs when changing classes - out = resample_from_to(n1_img, trans_img) - assert out.__class__ == Nifti1Image - # Passed as keyword arg - with caplog.at_level(logging.CRITICAL): - out = resample_from_to(img, trans_img, out_class=Nifti2Image) - assert out.__class__ == Nifti2Image - # If keyword arg is None, use type of from_img - out = resample_from_to(n1_img, trans_img, out_class=None) - assert out.__class__ == Nifti2Image - # to_img type irrelevant in all cases - n1_trans_img = Nifti2Image(data, trans_aff) - out = resample_from_to(img, n1_trans_img, out_class=None) - assert out.__class__ == Nifti1Image - # From 2D to 3D, error, the fixed affine is not invertible - img_2d = Nifti1Image(data[:, :, 0], affine) - with pytest.raises(AffineError): - resample_from_to(img_2d, img) - # 3D to 2D, we don't need to invert the fixed matrix - out = resample_from_to(img, img_2d) - assert_array_equal(out.dataobj, data[:, :, 0]) - # Same for tuple as to_img input - out = resample_from_to(img, (img_2d.shape, img_2d.affine)) - assert_array_equal(out.dataobj, data[:, :, 0]) - # 4D input and output also OK - data_4d = np.arange(24 * 5, dtype='int32').reshape((2, 3, 4, 5)) - img_4d = Nifti1Image(data_4d, affine) - out = resample_from_to(img_4d, img_4d) - assert_almost_equal(data_4d, out.dataobj) - assert_array_equal(img_4d.affine, out.affine) - # Errors trying to match 3D to 4D - with pytest.raises(ValueError): - resample_from_to(img_4d, img) - with pytest.raises(ValueError): - resample_from_to(img, img_4d) - - -@needs_scipy -def test_resample_to_output(caplog): - # Test routine to sample images to output space - # Image aligned to output axes - no-op - data = np.arange(24, dtype='int32').reshape((2, 3, 4)) - img = Nifti1Image(data, np.eye(4)) - # Check default resampling - img2 = resample_to_output(img) - assert_array_equal(img2.shape, (2, 3, 4)) - assert_array_equal(img2.affine, np.eye(4)) - assert_array_equal(img2.dataobj, data) - # Check resampling with different voxel size specifications - for vox_sizes in (None, 1, [1, 1, 1]): - img2 = resample_to_output(img, vox_sizes) - assert_array_equal(img2.shape, (2, 3, 4)) - assert_array_equal(img2.affine, np.eye(4)) - assert_array_equal(img2.dataobj, data) - img2 = resample_to_output(img, vox_sizes) - # Check 2D works - img_2d = Nifti1Image(data[0], np.eye(4)) - for vox_sizes in (None, 1, (1, 1), (1, 1, 1)): - img3 = resample_to_output(img_2d, vox_sizes) - assert_array_equal(img3.shape, (3, 4, 1)) - assert_array_equal(img3.affine, np.eye(4)) - assert_array_equal(img3.dataobj, data[0][..., None]) - # Even 1D - img_1d = Nifti1Image(data[0, 0], np.eye(4)) - img3 = resample_to_output(img_1d) - assert_array_equal(img3.shape, (4, 1, 1)) - assert_array_equal(img3.affine, np.eye(4)) - assert_array_equal(img3.dataobj, data[0, 0][..., None, None]) - # But 4D does not - img_4d = Nifti1Image(data.reshape(2, 3, 2, 2), np.eye(4)) - with pytest.raises(ValueError): - resample_to_output(img_4d) - # Run vox2vox_out tests, checking output shape, coordinate transform - for in_shape, in_aff, vox, out_shape, out_aff in get_outspace_params(): - # Allow for expansion of image shape from < 3D - in_n_dim = len(in_shape) - if in_n_dim < 3: - in_shape = in_shape + (1,) * (3 - in_n_dim) - if not vox is None: - vox = vox + (1,) * (3 - in_n_dim) - assert len(out_shape) == in_n_dim - out_shape = out_shape + (1,) * (3 - in_n_dim) - img = Nifti1Image(np.ones(in_shape), in_aff) - out_img = resample_to_output(img, vox) - assert_all_in(in_shape, in_aff, out_img.shape, out_img.affine) - assert out_img.shape == out_shape - assert_almost_equal(out_img.affine, out_aff) - # Check data is as expected with some transforms - # Flip first axis - out_img = resample_to_output(Nifti1Image(data, np.diag([-1, 1, 1, 1]))) - assert_array_equal(out_img.dataobj, np.flipud(data)) - # Subsample voxels - out_img = resample_to_output(Nifti1Image(data, np.diag([4, 5, 6, 1]))) - with warnings.catch_warnings(): - warnings.simplefilter('ignore', UserWarning) - exp_out = spnd.affine_transform(data, [1 / 4, 1 / 5, 1 / 6], output_shape=(5, 11, 19)) - assert_array_equal(out_img.dataobj, exp_out) - # Unsubsample with voxel sizes - out_img = resample_to_output(Nifti1Image(data, np.diag([4, 5, 6, 1])), [4, 5, 6]) - assert_array_equal(out_img.dataobj, data) - # A rotation to test nearest, order, cval - rot_3 = from_matvec(euler2mat(np.pi / 4), [0, 0, 0]) - rot_3_img = Nifti1Image(data, rot_3) - out_img = resample_to_output(rot_3_img) - exp_shape = (4, 4, 4) - assert out_img.shape == exp_shape - exp_aff = np.array( - [ - [1, 0, 0, -2 * np.cos(np.pi / 4)], - [0, 1, 0, 0], - [0, 0, 1, 0], - [0, 0, 0, 1], - ] - ) - assert_almost_equal(out_img.affine, exp_aff) - rzs, trans = to_matvec(np.dot(npl.inv(rot_3), exp_aff)) - exp_out = spnd.affine_transform(data, rzs, trans, exp_shape) - assert_almost_equal(out_img.dataobj, exp_out) - # Order - assert_almost_equal( - resample_to_output(rot_3_img, order=0).dataobj, - spnd.affine_transform(data, rzs, trans, exp_shape, order=0), - ) - # Cval - assert_almost_equal( - resample_to_output(rot_3_img, cval=99).dataobj, - spnd.affine_transform(data, rzs, trans, exp_shape, cval=99), - ) - # Mode - assert_almost_equal( - resample_to_output(rot_3_img, mode='nearest').dataobj, - spnd.affine_transform(data, rzs, trans, exp_shape, mode='nearest'), - ) - # out_class - img_ni1 = Nifti2Image(data, np.eye(4)) - img_ni2 = Nifti2Image(data, np.eye(4)) - # Default is Nifti1Image - with caplog.at_level(logging.CRITICAL): # Here and below, suppress logs when changing classes - assert resample_to_output(img_ni2).__class__ == Nifti1Image - # Can be overridden - with caplog.at_level(logging.CRITICAL): - assert resample_to_output(img_ni1, out_class=Nifti2Image).__class__ == Nifti2Image - # None specifies out_class from input - assert resample_to_output(img_ni2, out_class=None).__class__ == Nifti2Image - - -@needs_scipy -def test_smooth_image(caplog): - # Test image smoothing - data = np.arange(24, dtype='int32').reshape((2, 3, 4)) - aff = np.diag([-4, 5, 6, 1]) - img = Nifti1Image(data, aff) - # Zero smoothing is no-op - out_img = smooth_image(img, 0) - assert_array_equal(out_img.affine, img.affine) - assert_array_equal(out_img.shape, img.shape) - assert_array_equal(out_img.dataobj, data) - # Isotropic smoothing - sd = fwhm2sigma(np.true_divide(8, [4, 5, 6])) - exp_out = spnd.gaussian_filter(data, sd, mode='nearest') - assert_array_equal(smooth_image(img, 8).dataobj, exp_out) - assert_array_equal(smooth_image(img, [8, 8, 8]).dataobj, exp_out) - with pytest.raises(ValueError): - smooth_image(img, [8, 8]) - # Not isotropic - mixed_sd = fwhm2sigma(np.true_divide([8, 7, 6], [4, 5, 6])) - exp_out = spnd.gaussian_filter(data, mixed_sd, mode='nearest') - assert_array_equal(smooth_image(img, [8, 7, 6]).dataobj, exp_out) - # In 2D - img_2d = Nifti1Image(data[0], aff) - exp_out = spnd.gaussian_filter(data[0], sd[:2], mode='nearest') - assert_array_equal(smooth_image(img_2d, 8).dataobj, exp_out) - assert_array_equal(smooth_image(img_2d, [8, 8]).dataobj, exp_out) - with pytest.raises(ValueError): - smooth_image(img_2d, [8, 8, 8]) - # Isotropic in 4D has zero for last dimension in scalar case - data_4d = np.arange(24 * 5, dtype='int32').reshape((2, 3, 4, 5)) - img_4d = Nifti1Image(data_4d, aff) - exp_out = spnd.gaussian_filter(data_4d, list(sd) + [0], mode='nearest') - assert_array_equal(smooth_image(img_4d, 8).dataobj, exp_out) - # But raises error for vector case - with pytest.raises(ValueError): - smooth_image(img_4d, [8, 8, 8]) - # mode, cval - exp_out = spnd.gaussian_filter(data, sd, mode='constant') - assert_array_equal(smooth_image(img, 8, mode='constant').dataobj, exp_out) - exp_out = spnd.gaussian_filter(data, sd, mode='constant', cval=99) - assert_array_equal(smooth_image(img, 8, mode='constant', cval=99).dataobj, exp_out) - # out_class - img_ni1 = Nifti1Image(data, np.eye(4)) - img_ni2 = Nifti2Image(data, np.eye(4)) - # Default is Nifti1Image - with caplog.at_level(logging.CRITICAL): # Here and below, suppress logs when changing classes - assert smooth_image(img_ni2, 0).__class__ == Nifti1Image - # Can be overridden - with caplog.at_level(logging.CRITICAL): - assert smooth_image(img_ni1, 0, out_class=Nifti2Image).__class__ == Nifti2Image - # None specifies out_class from input - assert smooth_image(img_ni2, 0, out_class=None).__class__ == Nifti2Image - - -@needs_scipy -def test_spatial_axes_check(caplog): - for fname in MINC_3DS + OTHER_IMGS: - img = nib.load(pjoin(DATA_DIR, fname)) - with caplog.at_level(logging.CRITICAL): # Suppress logs when changing classes - s_img = smooth_image(img, 0) - assert_array_equal(img.dataobj, s_img.dataobj) - with caplog.at_level(logging.CRITICAL): - out = resample_from_to(img, img, mode='nearest') - assert_almost_equal(img.dataobj, out.dataobj) - if len(img.shape) > 3: - continue - # Resample to output does not raise an error - out = resample_to_output(img, voxel_sizes(img.affine)) - for fname in MINC_4DS: - img = nib.load(pjoin(DATA_DIR, fname)) - with pytest.raises(ValueError): - smooth_image(img, 0) - with pytest.raises(ValueError): - resample_from_to(img, img, mode='nearest') - with pytest.raises(ValueError): - resample_to_output(img, voxel_sizes(img.affine)) - - -def assert_spm_resampling_close(from_img, our_resampled, spm_resampled): - """Assert our resampling is close to SPM's, allowing for edge effects""" - # To allow for differences in the way SPM and scipy.ndimage handle off-edge - # interpolation, mask out voxels off edge - to_img_shape = spm_resampled.shape - to_img_affine = spm_resampled.affine - to_vox_coords = np.indices(to_img_shape).transpose((1, 2, 3, 0)) - # Coordinates of to_img mapped to from_img - to_to_from = npl.inv(from_img.affine).dot(to_img_affine) - resamp_coords = apply_affine(to_to_from, to_vox_coords) - # Places where SPM may not return default value but scipy.ndimage will (SPM - # does not return zeros <0.05 from image edges). - # See: https://github.com/nipy/nibabel/pull/255#issuecomment-186774173 - outside_vol = np.any( - (resamp_coords < 0) | (np.subtract(resamp_coords, from_img.shape) > -1), axis=-1 - ) - spm_res = np.where(outside_vol, np.nan, np.array(spm_resampled.dataobj)) - assert_allclose_safely(our_resampled.dataobj, spm_res) - assert_almost_equal(our_resampled.affine, spm_resampled.affine, 5) - - -@needs_scipy -def test_against_spm_resample(): - # Test resampling against images resampled with SPM12 - # anatomical.nii has a diagonal -2, 2 2 affine; - # functional.nii has a diagonal -4, 4 4 affine; - # These are a bit boring, so first add some rotations and translations to - # the anatomical image affine, and then resample to the first volume in the - # functional, and compare to the same thing in SPM. - # See ``make_moved_anat.py`` script in this directory for input to SPM. - anat = nib.load(pjoin(DATA_DIR, 'anatomical.nii')) - func = nib.load(pjoin(DATA_DIR, 'functional.nii')) - some_rotations = euler2mat(0.1, 0.2, 0.3) - extra_affine = from_matvec(some_rotations, [3, 4, 5]) - moved_anat = nib.Nifti1Image(anat.get_fdata(), extra_affine.dot(anat.affine), anat.header) - one_func = nib.Nifti1Image(func.dataobj[..., 0], func.affine, func.header) - moved2func = resample_from_to(moved_anat, one_func, order=1, cval=np.nan) - spm_moved = nib.load(pjoin(DATA_DIR, 'resampled_anat_moved.nii')) - assert_spm_resampling_close(moved_anat, moved2func, spm_moved) - # Next we resample the rotated anatomical image to output space, and compare - # to the same operation done with SPM (our own version of 'reorient.m' by - # John Ashburner). - moved2output = resample_to_output(moved_anat, 4, order=1, cval=np.nan) - spm2output = nib.load(pjoin(DATA_DIR, 'reoriented_anat_moved.nii')) - assert_spm_resampling_close(moved_anat, moved2output, spm2output) - - -@needs_scipy -def test_conform(caplog): - anat = nib.load(pjoin(DATA_DIR, 'anatomical.nii')) - - # Test with default arguments. - c = conform(anat) - assert c.shape == (256, 256, 256) - assert c.header.get_zooms() == (1, 1, 1) - assert c.dataobj.dtype.type == anat.dataobj.dtype.type - assert aff2axcodes(c.affine) == ('R', 'A', 'S') - assert isinstance(c, Nifti1Image) - - # Test with non-default arguments. - with caplog.at_level(logging.CRITICAL): # Suppress logs when changing classes - c = conform( - anat, - out_shape=(100, 100, 200), - voxel_size=(2, 2, 1.5), - orientation='LPI', - out_class=Nifti2Image, - ) - assert c.shape == (100, 100, 200) - assert c.header.get_zooms() == (2, 2, 1.5) - assert c.dataobj.dtype.type == anat.dataobj.dtype.type - assert aff2axcodes(c.affine) == ('L', 'P', 'I') - assert isinstance(c, Nifti2Image) - - # TODO: support nD images in `conform` in the future, but for now, test that we get - # errors on non-3D images. - func = nib.load(pjoin(DATA_DIR, 'functional.nii')) - with pytest.raises(ValueError): - conform(func) - with pytest.raises(ValueError): - conform(anat, out_shape=(100, 100)) - with pytest.raises(ValueError): - conform(anat, voxel_size=(2, 2)) diff --git a/nibabel/tests/test_proxy_api.py b/nibabel/tests/test_proxy_api.py deleted file mode 100644 index c5f7ab42ae..0000000000 --- a/nibabel/tests/test_proxy_api.py +++ /dev/null @@ -1,460 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Validate image proxy API - -Minimum array proxy API is: - -* read only ``shape`` property -* read only ``is_proxy`` property set to True -* returns array from ``np.asarray(prox)`` -* returns array slice from ``prox[]`` where ```` is any - non-fancy slice specification. - -And: - -* that modifying no object outside ``prox`` will affect the result of - ``np.asarray(obj)``. Specifically: - * Changes in position (``obj.tell()``) of any passed file-like objects - will not affect the output of from ``np.asarray(proxy)``. - * if you pass a header into the __init__, then modifying the original - header will not affect the result of the array return. - -These last are to allow the proxy to be reused with different images. -""" - -import unittest -import warnings -from io import BytesIO -from itertools import product -from os.path import join as pjoin - -import numpy as np -import pytest -from numpy.testing import assert_allclose, assert_array_equal - -from .. import ecat, minc1, minc2, parrec -from ..analyze import AnalyzeHeader -from ..arrayproxy import ArrayProxy, is_proxy -from ..casting import have_binary128, sctypes -from ..externals.netcdf import netcdf_file -from ..freesurfer.mghformat import MGHHeader -from ..nifti1 import Nifti1Header -from ..optpkg import optional_package -from ..spm2analyze import Spm2AnalyzeHeader -from ..spm99analyze import Spm99AnalyzeHeader -from ..testing import assert_dt_equal, clear_and_catch_warnings -from ..testing import data_path as DATA_PATH -from ..tmpdirs import InTemporaryDirectory -from ..volumeutils import apply_read_scaling -from .test_api_validators import ValidateAPI -from .test_parrec import EG_REC, VARY_REC - -h5py, have_h5py, _ = optional_package('h5py') - -try: - from numpy.exceptions import ComplexWarning -except ModuleNotFoundError: # NumPy < 1.25 - from numpy import ComplexWarning - - -def _some_slicers(shape): - ndim = len(shape) - slicers = np.eye(ndim, dtype=int).astype(object) - slicers[slicers == 0] = slice(None) - for i in range(ndim): - if i % 2: - slicers[i, i] = -1 - elif shape[i] < 2: # some proxy examples have length 1 axes - slicers[i, i] = 0 - # Add a newaxis to keep us on our toes - no_pos = ndim // 2 - slicers = np.hstack( - ( - slicers[:, :no_pos], - np.empty((ndim, 1)), - slicers[:, no_pos:], - ) - ) - slicers[:, no_pos] = None - return [tuple(s) for s in slicers] - - -class _TestProxyAPI(ValidateAPI): - """Base class for testing proxy APIs - - Assumes that real classes will provide an `obj_params` method which is a - generator returning 2 tuples of (, ). - is a function returning a 3 tuple of (, , -
    ). is a dictionary containing at least keys - ``arr_out`` (expected output array from proxy), ``dtype_out`` (expected - output dtype for array) and ``shape`` (shape of array). - - The
    above should support at least "get_data_dtype", - "set_data_dtype", "get_data_shape", "set_data_shape" - """ - - # Flag True if offset can be set into header of image - settable_offset = False - - def validate_shape(self, pmaker, params): - # Check shape - prox, fio, hdr = pmaker() - assert_array_equal(prox.shape, params['shape']) - # Read only - with pytest.raises(AttributeError): - prox.shape = params['shape'] - - def validate_ndim(self, pmaker, params): - # Check shape - prox, fio, hdr = pmaker() - assert prox.ndim == len(params['shape']) - # Read only - with pytest.raises(AttributeError): - prox.ndim = len(params['shape']) - - def validate_is_proxy(self, pmaker, params): - # Check shape - prox, fio, hdr = pmaker() - assert prox.is_proxy - assert is_proxy(prox) - assert not is_proxy(np.arange(10)) - # Read only - with pytest.raises(AttributeError): - prox.is_proxy = False - - def validate_asarray(self, pmaker, params): - # Check proxy returns expected array from asarray - prox, fio, hdr = pmaker() - out = np.asarray(prox) - assert_array_equal(out, params['arr_out']) - assert_dt_equal(out.dtype, params['dtype_out']) - # Shape matches expected shape - assert out.shape == params['shape'] - - def validate_array_interface_with_dtype(self, pmaker, params): - # Check proxy returns expected array from asarray - prox, fio, hdr = pmaker() - orig = np.array(prox, dtype=None) - assert_array_equal(orig, params['arr_out']) - assert_dt_equal(orig.dtype, params['dtype_out']) - - context = None - if np.issubdtype(orig.dtype, np.complexfloating): - context = clear_and_catch_warnings() - context.__enter__() - warnings.simplefilter('ignore', ComplexWarning) - - for dtype in sctypes['float'] + sctypes['int'] + sctypes['uint']: - # Directly coerce with a dtype - direct = dtype(prox) - # Half-precision is imprecise. Obviously. It's a bad idea, but don't break - # the test over it. - rtol = 1e-03 if dtype == np.float16 else 1e-05 - assert_allclose(direct, orig.astype(dtype), rtol=rtol, atol=1e-08) - assert_dt_equal(direct.dtype, np.dtype(dtype)) - assert direct.shape == params['shape'] - # All three methods should produce equivalent results - for arrmethod in (np.array, np.asarray, np.asanyarray): - out = arrmethod(prox, dtype=dtype) - assert_array_equal(out, direct) - assert_dt_equal(out.dtype, np.dtype(dtype)) - # Shape matches expected shape - assert out.shape == params['shape'] - del out - del direct - - del orig - - if context is not None: - context.__exit__() - - def validate_header_isolated(self, pmaker, params): - # Confirm altering input header has no effect - # Depends on header providing 'get_data_dtype', 'set_data_dtype', - # 'get_data_shape', 'set_data_shape', 'set_data_offset' - prox, fio, hdr = pmaker() - assert_array_equal(prox, params['arr_out']) - # Mess up header badly and hope for same correct result - if hdr.get_data_dtype() == np.uint8: - hdr.set_data_dtype(np.int16) - else: - hdr.set_data_dtype(np.uint8) - hdr.set_data_shape(np.array(hdr.get_data_shape()) + 1) - if self.settable_offset: - hdr.set_data_offset(32) - assert_array_equal(prox, params['arr_out']) - - def validate_fileobj_isolated(self, pmaker, params): - # Check file position of read independent of file-like object - prox, fio, hdr = pmaker() - if isinstance(fio, str): - return - assert_array_equal(prox, params['arr_out']) - fio.read() # move to end of file - assert_array_equal(prox, params['arr_out']) - - def validate_proxy_slicing(self, pmaker, params): - # Confirm that proxy object can be sliced correctly - arr = params['arr_out'] - shape = arr.shape - prox, fio, hdr = pmaker() - for sliceobj in _some_slicers(shape): - assert_array_equal(arr[sliceobj], prox[sliceobj]) - - -class TestAnalyzeProxyAPI(_TestProxyAPI): - """Specific Analyze-type array proxy API test - - The analyze proxy extends the general API by adding read-only attributes - ``slope, inter, offset`` - """ - - proxy_class = ArrayProxy - header_class = AnalyzeHeader - shapes = ((2,), (2, 3), (2, 3, 4), (2, 3, 4, 5)) - has_slope = False - has_inter = False - data_dtypes = (np.uint8, np.int16, np.int32, np.float32, np.complex64, np.float64) - array_order = 'F' - # Cannot set offset for Freesurfer - settable_offset = True - # Freesurfer enforces big-endian. '=' means use native - data_endian = '=' - - def obj_params(self): - """Iterator returning (``proxy_creator``, ``proxy_params``) pairs - - Each pair will be tested separately. - - ``proxy_creator`` is a function taking no arguments and returning (fresh - proxy object, fileobj, header). We need to pass this function rather - than a proxy instance so we can recreate the proxy objects fresh for - each of multiple tests run from the ``validate_xxx`` autogenerated test - methods. This allows the tests to modify the proxy instance without - having an effect on the later tests in the same function. - """ - # Analyze and up wrap binary arrays, Fortran ordered, with given offset - # and dtype and shape. - if not self.settable_offset: - offsets = (self.header_class().get_data_offset(),) - else: - offsets = (0, 16) - # For non-integral parameters, cast to float32 value can be losslessly cast - # later, enabling exact checks, then back to float for consistency - slopes = (1.0, 2.0, float(np.float32(3.1416))) if self.has_slope else (1.0,) - inters = (0.0, 10.0, float(np.float32(2.7183))) if self.has_inter else (0.0,) - for shape, dtype, offset, slope, inter in product( - self.shapes, - self.data_dtypes, - offsets, - slopes, - inters, - ): - n_els = np.prod(shape) - dtype = np.dtype(dtype).newbyteorder(self.data_endian) - arr = np.arange(n_els, dtype=dtype).reshape(shape) - data = arr.tobytes(order=self.array_order) - hdr = self.header_class() - hdr.set_data_dtype(dtype) - hdr.set_data_shape(shape) - if self.settable_offset: - hdr.set_data_offset(offset) - if (slope, inter) == (1, 0): # No scaling applied - # dtype from array - dtype_out = dtype - else: # scaling or offset applied - # out dtype predictable from apply_read_scaling - # and datatypes of slope, inter - hdr.set_slope_inter(slope, inter) - s, i = hdr.get_slope_inter() - tmp = apply_read_scaling(arr, 1.0 if s is None else s, 0.0 if i is None else i) - dtype_out = tmp.dtype.type - - def sio_func(): - fio = BytesIO() - fio.truncate(0) - fio.seek(offset) - fio.write(data) - # Use a copy of the header to avoid changing - # global header in test functions. - new_hdr = hdr.copy() - return (self.proxy_class(fio, new_hdr), fio, new_hdr) - - params = dict( - dtype=dtype, - dtype_out=dtype_out, - arr=arr.copy(), - arr_out=arr.astype(dtype_out) * slope + inter, - shape=shape, - offset=offset, - slope=slope, - inter=inter, - ) - yield sio_func, params - # Same with filenames - with InTemporaryDirectory(): - fname = 'data.bin' - - def fname_func(): - with open(fname, 'wb') as fio: - fio.seek(offset) - fio.write(data) - # Use a copy of the header to avoid changing - # global header in test functions. - new_hdr = hdr.copy() - return (self.proxy_class(fname, new_hdr), fname, new_hdr) - - params = params.copy() - yield fname_func, params - - def validate_dtype(self, pmaker, params): - # Read-only dtype attribute - prox, fio, hdr = pmaker() - assert_dt_equal(prox.dtype, params['dtype']) - with pytest.raises(AttributeError): - prox.dtype = np.dtype(prox.dtype) - - def validate_slope_inter_offset(self, pmaker, params): - # Check slope, inter, offset - prox, fio, hdr = pmaker() - for attr_name in ('slope', 'inter', 'offset'): - expected = params[attr_name] - assert_array_equal(getattr(prox, attr_name), expected) - # Read only - with pytest.raises(AttributeError): - setattr(prox, attr_name, expected) - - -class TestSpm99AnalyzeProxyAPI(TestAnalyzeProxyAPI): - # SPM-type analyze has slope scaling but not intercept - header_class = Spm99AnalyzeHeader - has_slope = True - - -class TestSpm2AnalyzeProxyAPI(TestSpm99AnalyzeProxyAPI): - header_class = Spm2AnalyzeHeader - - -class TestNifti1ProxyAPI(TestSpm99AnalyzeProxyAPI): - header_class = Nifti1Header - has_inter = True - data_dtypes = ( - np.uint8, - np.int16, - np.int32, - np.float32, - np.complex64, - np.float64, - np.int8, - np.uint16, - np.uint32, - np.int64, - np.uint64, - np.complex128, - ) - if have_binary128(): - data_dtypes += (np.float128, np.complex256) - - -class TestMGHAPI(TestAnalyzeProxyAPI): - header_class = MGHHeader - shapes = ((2, 3, 4), (2, 3, 4, 5)) # MGH can only do >= 3D - has_slope = False - has_inter = False - settable_offset = False - data_endian = '>' - data_dtypes = (np.uint8, np.int16, np.int32, np.float32) - - -class TestMinc1API(_TestProxyAPI): - module = minc1 - file_class = minc1.Minc1File - eg_fname = 'tiny.mnc' - eg_shape = (10, 20, 20) - - @staticmethod - def opener(f): - return netcdf_file(f, mode='r') - - def obj_params(self): - """Iterator returning (``proxy_creator``, ``proxy_params``) pairs - - Each pair will be tested separately. - - ``proxy_creator`` is a function taking no arguments and returning (fresh - proxy object, fileobj, header). We need to pass this function rather - than a proxy instance so we can recreate the proxy objects fresh for - each of multiple tests run from the ``validate_xxx`` autogenerated test - methods. This allows the tests to modify the proxy instance without - having an effect on the later tests in the same function. - """ - eg_path = pjoin(DATA_PATH, self.eg_fname) - arr_out = self.file_class(self.opener(eg_path)).get_scaled_data() - - def eg_func(): - mf = self.file_class(self.opener(eg_path)) - prox = minc1.MincImageArrayProxy(mf) - img = self.module.load(eg_path) - fobj = open(eg_path, 'rb') - return prox, fobj, img.header - - yield (eg_func, dict(shape=self.eg_shape, dtype_out=np.float64, arr_out=arr_out)) - - -if have_h5py: - - class TestMinc2API(TestMinc1API): - module = minc2 - file_class = minc2.Minc2File - eg_fname = 'small.mnc' - eg_shape = (18, 28, 29) - - @staticmethod - def opener(f): - return h5py.File(f, mode='r') - - -class TestEcatAPI(_TestProxyAPI): - eg_fname = 'tinypet.v' - eg_shape = (10, 10, 3, 1) - - def obj_params(self): - eg_path = pjoin(DATA_PATH, self.eg_fname) - img = ecat.load(eg_path) - arr_out = img.get_fdata() - - def eg_func(): - img = ecat.load(eg_path) - sh = img.get_subheaders() - prox = ecat.EcatImageArrayProxy(sh) - fobj = open(eg_path, 'rb') - return prox, fobj, sh - - yield (eg_func, dict(shape=self.eg_shape, dtype_out=np.float64, arr_out=arr_out)) - - def validate_header_isolated(self, pmaker, params): - raise unittest.SkipTest('ECAT header does not support dtype get') - - -class TestPARRECAPI(_TestProxyAPI): - def _func_dict(self, rec_name): - img = parrec.load(rec_name) - arr_out = img.get_fdata() - - def eg_func(): - img = parrec.load(rec_name) - prox = parrec.PARRECArrayProxy(rec_name, img.header, scaling='dv') - fobj = open(rec_name, 'rb') - return prox, fobj, img.header - - return (eg_func, dict(shape=img.shape, dtype_out=np.float64, arr_out=arr_out)) - - def obj_params(self): - yield self._func_dict(EG_REC) - yield self._func_dict(VARY_REC) diff --git a/nibabel/tests/test_quaternions.py b/nibabel/tests/test_quaternions.py deleted file mode 100644 index a5ec89d948..0000000000 --- a/nibabel/tests/test_quaternions.py +++ /dev/null @@ -1,227 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Test quaternion calculations""" - -import numpy as np -import pytest -from numpy import pi -from numpy.testing import assert_array_almost_equal, assert_array_equal - -from .. import eulerangles as nea -from .. import quaternions as nq - - -def norm(vec): - # Return unit vector with same orientation as input vector - return vec / np.sqrt(vec @ vec) - - -def gen_vec(dtype): - # Generate random 3-vector in [-1, 1]^3 - rand = np.random.default_rng() - return rand.uniform(low=-1.0, high=1.0, size=(3,)).astype(dtype) - - -# Example rotations -eg_rots = [ - nea.euler2mat(z, y, x) - for z in np.arange(-pi, pi, pi / 2) - for y in np.arange(-pi, pi, pi / 2) - for x in np.arange(-pi, pi, pi / 2) -] - -# Example quaternions (from rotations) -eg_quats = [nq.mat2quat(M) for M in eg_rots] -# M, quaternion pairs -eg_pairs = list(zip(eg_rots, eg_quats)) - -# Set of arbitrary unit quaternions -unit_quats = set( - tuple(norm(np.r_[w, x, y, z])) - for w in range(-2, 3) - for x in range(-2, 3) - for y in range(-2, 3) - for z in range(-2, 3) - if (w, x, y, z) != (0, 0, 0, 0) -) - - -def test_fillpos(): - # Takes np array - xyz = np.zeros((3,)) - w, x, y, z = nq.fillpositive(xyz) - assert w == 1 - # Or lists - xyz = [0] * 3 - w, x, y, z = nq.fillpositive(xyz) - assert w == 1 - # Errors with wrong number of values - with pytest.raises(ValueError): - nq.fillpositive([0, 0]) - with pytest.raises(ValueError): - nq.fillpositive([0] * 4) - # Errors with negative w2 - with pytest.raises(ValueError): - nq.fillpositive([1.0] * 3) - # Test corner case where w is near zero - wxyz = nq.fillpositive([1, 0, 0]) - assert wxyz[0] == 0.0 - - -@pytest.mark.parametrize('dtype', ('f4', 'f8')) -def test_fillpositive_plus_minus_epsilon(dtype): - # Deterministic test for fillpositive threshold - # We are trying to fill (x, y, z) with a w such that |(w, x, y, z)| == 1 - # If |(x, y, z)| is slightly off one, w should still be 0 - nptype = np.dtype(dtype).type - - # Obviously, |(x, y, z)| == 1 - baseline = np.array([0, 0, 1], dtype=dtype) - - # Obviously, |(x, y, z)| ~ 1 - plus = baseline * nptype(1 + np.finfo(dtype).eps) - minus = baseline * nptype(1 - np.finfo(dtype).eps) - - assert nq.fillpositive(plus)[0] == 0.0 - assert nq.fillpositive(minus)[0] == 0.0 - - # |(x, y, z)| > 1, no real solutions - plus = baseline * nptype(1 + 2 * np.finfo(dtype).eps) - with pytest.raises(ValueError): - nq.fillpositive(plus) - - # |(x, y, z)| < 1, two real solutions, we choose positive - minus = baseline * nptype(1 - 2 * np.finfo(dtype).eps) - assert nq.fillpositive(minus)[0] > 0.0 - - -@pytest.mark.parametrize('dtype', ('f4', 'f8')) -def test_fillpositive_simulated_error(dtype): - # Nondeterministic test for fillpositive threshold - # Create random vectors, normalize to unit length, and count on floating point - # error to result in magnitudes larger/smaller than one - # This is to simulate cases where a unit quaternion with w == 0 would be encoded - # as xyz with small error, and we want to recover the w of 0 - - # Permit 1 epsilon per value (default, but make explicit here) - w2_thresh = 3 * np.finfo(dtype).eps - - for _ in range(50): - xyz = norm(gen_vec(dtype)) - - assert nq.fillpositive(xyz, w2_thresh)[0] == 0.0 - - -def test_conjugate(): - # Takes sequence - cq = nq.conjugate((1, 0, 0, 0)) - # Returns float type - assert cq.dtype.kind == 'f' - - -def test_quat2mat(): - # also tested in roundtrip case below - M = nq.quat2mat([1, 0, 0, 0]) - assert_array_almost_equal, M, np.eye(3) - M = nq.quat2mat([3, 0, 0, 0]) - assert_array_almost_equal, M, np.eye(3) - M = nq.quat2mat([0, 1, 0, 0]) - assert_array_almost_equal, M, np.diag([1, -1, -1]) - M = nq.quat2mat([0, 2, 0, 0]) - assert_array_almost_equal, M, np.diag([1, -1, -1]) - M = nq.quat2mat([0, 0, 0, 0]) - assert_array_almost_equal, M, np.eye(3) - - -def test_inverse_0(): - # Takes sequence - iq = nq.inverse((1, 0, 0, 0)) - # Returns float type - assert iq.dtype.kind == 'f' - - -@pytest.mark.parametrize(('M', 'q'), eg_pairs) -def test_inverse_1(M, q): - iq = nq.inverse(q) - iqM = nq.quat2mat(iq) - iM = np.linalg.inv(M) - assert np.allclose(iM, iqM) - - -def test_eye(): - qi = nq.eye() - assert qi.dtype.kind == 'f' - assert np.all([1, 0, 0, 0] == qi) - assert np.allclose(nq.quat2mat(qi), np.eye(3)) - - -def test_norm(): - qi = nq.eye() - assert nq.norm(qi) == 1 - assert nq.isunit(qi) - qi[1] = 0.2 - assert not nq.isunit(qi) - - -@pytest.mark.parametrize(('M1', 'q1'), eg_pairs[0::4]) -@pytest.mark.parametrize(('M2', 'q2'), eg_pairs[1::4]) -def test_mult(M1, q1, M2, q2): - # Test that quaternion * same as matrix * - q21 = nq.mult(q2, q1) - assert_array_almost_equal, M2 @ M1, nq.quat2mat(q21) - - -@pytest.mark.parametrize(('M', 'q'), eg_pairs) -def test_inverse(M, q): - iq = nq.inverse(q) - iqM = nq.quat2mat(iq) - iM = np.linalg.inv(M) - assert np.allclose(iM, iqM) - - -@pytest.mark.parametrize('vec', np.eye(3)) -@pytest.mark.parametrize(('M', 'q'), eg_pairs) -def test_qrotate(vec, M, q): - vdash = nq.rotate_vector(vec, q) - vM = M @ vec - assert_array_almost_equal(vdash, vM) - - -@pytest.mark.parametrize('q', unit_quats) -def test_quaternion_reconstruction(q): - # Test reconstruction of arbitrary unit quaternions - M = nq.quat2mat(q) - qt = nq.mat2quat(M) - # Accept positive or negative match - posm = np.allclose(q, qt) - negm = np.allclose(q, -qt) - assert posm or negm - - -def test_angle_axis2quat(): - q = nq.angle_axis2quat(0, [1, 0, 0]) - assert_array_equal(q, [1, 0, 0, 0]) - q = nq.angle_axis2quat(np.pi, [1, 0, 0]) - assert_array_almost_equal(q, [0, 1, 0, 0]) - q = nq.angle_axis2quat(np.pi, [1, 0, 0], True) - assert_array_almost_equal(q, [0, 1, 0, 0]) - q = nq.angle_axis2quat(np.pi, [2, 0, 0], False) - assert_array_almost_equal(q, [0, 1, 0, 0]) - - -def test_angle_axis(): - for M, q in eg_pairs: - theta, vec = nq.quat2angle_axis(q) - q2 = nq.angle_axis2quat(theta, vec) - nq.nearly_equivalent(q, q2) - aa_mat = nq.angle_axis2mat(theta, vec) - assert_array_almost_equal(aa_mat, M) - unit_vec = norm(vec) - aa_mat2 = nq.angle_axis2mat(theta, unit_vec, is_normalized=True) - assert_array_almost_equal(aa_mat2, M) diff --git a/nibabel/tests/test_recoder.py b/nibabel/tests/test_recoder.py deleted file mode 100644 index f5a77158ec..0000000000 --- a/nibabel/tests/test_recoder.py +++ /dev/null @@ -1,189 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Tests recoder class""" - -import numpy as np -import pytest - -from ..volumeutils import DtypeMapper, Recoder, native_code, swapped_code - - -def test_recoder_1(): - # simplest case, no aliases - codes = ((1,), (2,)) - rc = Recoder(codes) - assert rc.code[1] == 1 - assert rc.code[2] == 2 - with pytest.raises(KeyError): - rc.code[3] - - -def test_recoder_2(): - # with explicit name for code - codes = ((1,), (2,)) - rc = Recoder(codes, ['code1']) - with pytest.raises(AttributeError): - rc.code - assert rc.code1[1] == 1 - assert rc.code1[2] == 2 - - -def test_recoder_3(): - # code and label - codes = ((1, 'one'), (2, 'two')) - rc = Recoder(codes) # just with implicit alias - assert rc.code[1] == 1 - assert rc.code[2] == 2 - with pytest.raises(KeyError): - rc.code[3] - assert rc.code['one'] == 1 - assert rc.code['two'] == 2 - with pytest.raises(KeyError): - rc.code['three'] - with pytest.raises(AttributeError): - rc.label - - -def test_recoder_4(): - # with explicit column names - codes = ((1, 'one'), (2, 'two')) - rc = Recoder(codes, ['code1', 'label']) - with pytest.raises(AttributeError): - rc.code - assert rc.code1[1] == 1 - assert rc.code1['one'] == 1 - assert rc.label[1] == 'one' - assert rc.label['one'] == 'one' - - -def test_recoder_5(): - # code, label, aliases - codes = ((1, 'one', '1', 'first'), (2, 'two')) - rc = Recoder(codes) # just with implicit alias - assert rc.code[1] == 1 - assert rc.code['one'] == 1 - assert rc.code['first'] == 1 - - -def test_recoder_6(): - # with explicit column names - codes = ((1, 'one', '1', 'first'), (2, 'two')) - rc = Recoder(codes, ['code1', 'label']) - assert rc.code1[1] == 1 - assert rc.code1['first'] == 1 - assert rc.label[1] == 'one' - assert rc.label['first'] == 'one' - # Don't allow funny names - with pytest.raises(KeyError): - Recoder(codes, ['field1']) - - -def test_custom_dicter(): - # Allow custom dict-like object in constructor - class MyDict: - def __init__(self): - self._keys = [] - - def __setitem__(self, key, value): - self._keys.append(key) - - def __getitem__(self, key): - if key in self._keys: - return 'spam' - return 'eggs' - - def keys(self): - return ['some', 'keys'] - - def values(self): - return ['funny', 'list'] - - # code, label, aliases - codes = ((1, 'one', '1', 'first'), (2, 'two')) - rc = Recoder(codes, map_maker=MyDict) - assert rc.code[1] == 'spam' - assert rc.code['one'] == 'spam' - assert rc.code['first'] == 'spam' - assert rc.code['bizarre'] == 'eggs' - assert rc.value_set() == {'funny', 'list'} - assert list(rc.keys()) == ['some', 'keys'] - - -def test_add_codes(): - codes = ((1, 'one', '1', 'first'), (2, 'two')) - rc = Recoder(codes) - assert rc.code['two'] == 2 - with pytest.raises(KeyError): - rc.code['three'] - rc.add_codes(((3, 'three'), (1, 'number 1'))) - assert rc.code['three'] == 3 - assert rc.code['number 1'] == 1 - - -def test_sugar(): - # Syntactic sugar for recoder class - codes = ((1, 'one', '1', 'first'), (2, 'two')) - rc = Recoder(codes) - # Field1 is synonym for first named dict - assert rc.code == rc.field1 - rc = Recoder(codes, fields=('code1', 'label')) - assert rc.code1 == rc.field1 - # Direct key access identical to key access for first named - assert rc[1] == rc.field1[1] - assert rc['two'] == rc.field1['two'] - # keys gets all keys - assert set(rc.keys()) == {1, 'one', '1', 'first', 2, 'two'} - # value_set gets set of values from first column - assert rc.value_set() == {1, 2} - # or named column if given - assert rc.value_set('label') == {'one', 'two'} - # "in" works for values in and outside the set - assert 'one' in rc - assert 'three' not in rc - - -def test_dtmapper(): - # dict-like that will lookup on dtypes, even if they don't hash properly - d = DtypeMapper() - with pytest.raises(KeyError): - d[1] - d[1] = 'something' - assert d[1] == 'something' - assert list(d.keys()) == [1] - assert list(d.values()) == ['something'] - intp_dt = np.dtype('intp') - if intp_dt == np.dtype('int32'): - canonical_dt = np.dtype('int32') - elif intp_dt == np.dtype('int64'): - canonical_dt = np.dtype('int64') - else: - raise RuntimeError('Can I borrow your computer?') - native_dt = canonical_dt.newbyteorder('=') - explicit_dt = canonical_dt.newbyteorder(native_code) - d[canonical_dt] = 'spam' - assert d[canonical_dt] == 'spam' - assert d[native_dt] == 'spam' - assert d[explicit_dt] == 'spam' - - # Test keys, values - d = DtypeMapper() - assert list(d.keys()) == [] - assert list(d.keys()) == [] - d[canonical_dt] = 'spam' - assert list(d.keys()) == [canonical_dt] - assert list(d.values()) == ['spam'] - # With other byte order - d = DtypeMapper() - sw_dt = canonical_dt.newbyteorder(swapped_code) - d[sw_dt] = 'spam' - with pytest.raises(KeyError): - d[canonical_dt] - assert d[sw_dt] == 'spam' - sw_intp_dt = intp_dt.newbyteorder(swapped_code) - assert d[sw_intp_dt] == 'spam' diff --git a/nibabel/tests/test_removalschedule.py b/nibabel/tests/test_removalschedule.py deleted file mode 100644 index d2bc7da2fc..0000000000 --- a/nibabel/tests/test_removalschedule.py +++ /dev/null @@ -1,175 +0,0 @@ -from unittest import mock - -import pytest - -from ..pkg_info import cmp_pkg_version - -MODULE_SCHEDULE = [ - ('7.0.0', ['nibabel.pydicom_compat']), - ('5.0.0', ['nibabel.keywordonly', 'nibabel.py3k']), - ('4.0.0', ['nibabel.trackvis']), - ('3.0.0', ['nibabel.minc', 'nibabel.checkwarns']), - # Verify that the test will be quiet if the schedule outlives the modules - ('1.0.0', ['nibabel.nosuchmod']), -] - -OBJECT_SCHEDULE = [ - ( - '8.0.0', - [ - ('nibabel.casting', 'as_int'), - ('nibabel.casting', 'int_to_float'), - ('nibabel.tmpdirs', 'TemporaryDirectory'), - ], - ), - ( - '7.0.0', - [ - ('nibabel.gifti.gifti', 'GiftiNVPairs'), - ], - ), - ( - '6.0.0', - [ - ('nibabel.loadsave', 'guessed_image_type'), - ('nibabel.loadsave', 'read_img_data'), - ('nibabel.orientations', 'flip_axis'), - ('nibabel.pydicom_compat', 'dicom_test'), - ('nibabel.onetime', 'setattr_on_read'), - ], - ), - ( - '5.0.0', - [ - ('nibabel.gifti.gifti', 'data_tag'), - ('nibabel.gifti.giftiio', 'read'), - ('nibabel.gifti.giftiio', 'write'), - ('nibabel.gifti.parse_gifti_fast', 'Outputter'), - ('nibabel.gifti.parse_gifti_fast', 'parse_gifti_file'), - ('nibabel.imageclasses', 'ext_map'), - ('nibabel.imageclasses', 'class_map'), - ('nibabel.loadsave', 'which_analyze_type'), - ('nibabel.volumeutils', 'BinOpener'), - ('nibabel.volumeutils', 'allopen'), - ('nibabel.orientations', 'orientation_affine'), - ('nibabel.spatialimages', 'Header'), - ], - ), - ('4.0.0', [('nibabel.minc1', 'MincFile'), ('nibabel.minc1', 'MincImage')]), - ('3.0.0', [('nibabel.testing', 'catch_warn_reset')]), - # Verify that the test will be quiet if the schedule outlives the modules - ('1.0.0', [('nibabel.nosuchmod', 'anyobj'), ('nibabel.nifti1', 'nosuchobj')]), -] - -ATTRIBUTE_SCHEDULE = [ - ( - '7.0.0', - [ - ('nibabel.gifti.gifti', 'GiftiMetaData', 'from_dict'), - ('nibabel.gifti.gifti', 'GiftiMetaData', 'metadata'), - ('nibabel.gifti.gifti', 'GiftiMetaData', 'data'), - ], - ), - ( - '5.0.0', - [ - ('nibabel.dataobj_images', 'DataobjImage', 'get_data'), - ('nibabel.freesurfer.mghformat', 'MGHHeader', '_header_data'), - ('nibabel.gifti.gifti', 'GiftiDataArray', 'from_array'), - ('nibabel.gifti.gifti', 'GiftiDataArray', 'to_xml_open'), - ('nibabel.gifti.gifti', 'GiftiDataArray', 'to_xml_close'), - ('nibabel.gifti.gifti', 'GiftiDataArray', 'get_metadata'), - ('nibabel.gifti.gifti', 'GiftiImage', 'get_labeltable'), - ('nibabel.gifti.gifti', 'GiftiImage', 'set_labeltable'), - ('nibabel.gifti.gifti', 'GiftiImage', 'get_metadata'), - ('nibabel.gifti.gifti', 'GiftiImage', 'set_metadata'), - ('nibabel.gifti.gifti', 'GiftiImage', 'getArraysFromIntent'), - ('nibabel.gifti.gifti', 'GiftiMetaData', 'get_metadata'), - ('nibabel.gifti.gifti', 'GiftiLabel', 'get_rgba'), - ('nibabel.nicom.dicomwrappers', 'Wrapper', 'get_affine'), - ('nibabel.streamlines.array_sequence', 'ArraySequence', 'data'), - ('nibabel.ecat', 'EcatImage', 'from_filespec'), - ('nibabel.filebasedimages', 'FileBasedImage', 'get_header'), - ('nibabel.spatialimages', 'SpatialImage', 'get_affine'), - ('nibabel.arraywriters', 'ArrayWriter', '_check_nan2zero'), - ], - ), - ( - '4.0.0', - [ - ('nibabel.dataobj_images', 'DataobjImage', 'get_shape'), - ('nibabel.filebasedimages', 'FileBasedImage', 'filespec_to_files'), - ('nibabel.filebasedimages', 'FileBasedImage', 'to_filespec'), - ('nibabel.filebasedimages', 'FileBasedImage', 'to_files'), - ('nibabel.filebasedimages', 'FileBasedImage', 'from_files'), - ('nibabel.arrayproxy', 'ArrayProxy', 'header'), - ], - ), - # Verify that the test will be quiet if the schedule outlives the modules - ( - '1.0.0', - [ - ('nibabel.nosuchmod', 'anyobj', 'anyattr'), - ('nibabel.nifti1', 'nosuchobj', 'anyattr'), - ('nibabel.nifti1', 'Nifti1Image', 'nosuchattr'), - ], - ), -] - - -def _filter(schedule): - return [entry for ver, entries in schedule if cmp_pkg_version(ver) < 1 for entry in entries] - - -def test_module_removal(): - for module in _filter(MODULE_SCHEDULE): - with pytest.raises(ImportError): - __import__(module) - raise AssertionError(f'Time to remove {module}') - - -def test_object_removal(): - for module_name, obj in _filter(OBJECT_SCHEDULE): - try: - module = __import__(module_name) - except ImportError: - continue - assert not hasattr(module, obj), f'Time to remove {module_name}.{obj}' - - -def test_attribute_removal(): - for module_name, cls, attr in _filter(ATTRIBUTE_SCHEDULE): - try: - module = __import__(module_name) - except ImportError: - continue - try: - klass = getattr(module, cls) - except AttributeError: - continue - assert not hasattr(klass, attr), f'Time to remove {module_name}.{cls}.{attr}' - - -# -# Test the tests, making sure that we will get errors when the time comes -# - -_sched = 'nibabel.tests.test_removalschedule.{}_SCHEDULE'.format - - -@mock.patch(_sched('MODULE'), [('3.0.0', ['nibabel.nifti1'])]) -def test_unremoved_module(): - with pytest.raises(AssertionError): - test_module_removal() - - -@mock.patch(_sched('OBJECT'), [('3.0.0', [('nibabel.nifti1', 'Nifti1Image')])]) -def test_unremoved_object(): - with pytest.raises(AssertionError): - test_object_removal() - - -@mock.patch(_sched('ATTRIBUTE'), [('3.0.0', [('nibabel.nifti1', 'Nifti1Image', 'affine')])]) -def test_unremoved_attr(): - with pytest.raises(AssertionError): - test_attribute_removal() diff --git a/nibabel/tests/test_round_trip.py b/nibabel/tests/test_round_trip.py deleted file mode 100644 index 6daf960aa4..0000000000 --- a/nibabel/tests/test_round_trip.py +++ /dev/null @@ -1,206 +0,0 @@ -"""Test numerical errors introduced by writing then reading images - -Test arrays with a range of numerical values, integer and floating point. -""" - -from io import BytesIO - -import numpy as np -from numpy.testing import assert_array_equal - -from .. import Nifti1Header, Nifti1Image -from ..arraywriters import ScalingError -from ..casting import best_float, sctypes, type_info, ulp -from ..spatialimages import HeaderDataError, supported_np_types - -DEBUG = False - - -def round_trip(arr, out_dtype): - img = Nifti1Image(arr, np.eye(4), dtype=out_dtype) - img.file_map['image'].fileobj = BytesIO() - img.to_file_map() - back = Nifti1Image.from_file_map(img.file_map) - # Recover array and calculated scaling from array proxy object - return back.get_fdata(), back.dataobj.slope, back.dataobj.inter - - -def check_params(in_arr, in_type, out_type): - arr = in_arr.astype(in_type) - # clip infs that can arise from downcasting - if arr.dtype.kind == 'f': - info = np.finfo(in_type) - arr = np.clip(arr, info.min, info.max) - try: - arr_dash, slope, inter = round_trip(arr, out_type) - except (ScalingError, HeaderDataError): - return arr, None, None, None - return arr, arr_dash, slope, inter - - -BFT = best_float() -LOGe2 = np.log(BFT(2)) - - -def big_bad_ulp(arr): - """Return array of ulp values for values in `arr` - - I haven't thought about whether the vectorized log2 here could lead to - incorrect rounding; this only needs to be ballpark - - This function might be used in nipy/io/tests/test_image_io.py - - Parameters - ---------- - arr : array - floating point array - - Returns - ------- - ulps : array - ulp values for each element of arr - """ - # Assumes array is floating point - arr = np.asarray(arr) - info = type_info(arr.dtype) - working_arr = np.abs(arr.astype(BFT)) - # Log2 for numpy < 1.3 - fl2 = np.zeros_like(working_arr) + info['minexp'] - # Avoid divide by zero error for log of 0 - nzs = working_arr > 0 - fl2[nzs] = np.floor(np.log(working_arr[nzs]) / LOGe2) - fl2 = np.clip(fl2, info['minexp'], np.inf) - return 2 ** (fl2 - info['nmant']) - - -def test_big_bad_ulp(): - for ftype in (np.float32, np.float64): - ti = type_info(ftype) - fi = np.finfo(ftype) - min_ulp = 2 ** (ti['minexp'] - ti['nmant']) - in_arr = np.zeros((10,), dtype=ftype) - in_arr = np.array([0, 0, 1, 2, 4, 5, -5, -np.inf, np.inf], dtype=ftype) - out_arr = [ - min_ulp, - min_ulp, - fi.eps, - fi.eps * 2, - fi.eps * 4, - fi.eps * 4, - fi.eps * 4, - np.inf, - np.inf, - ] - assert_array_equal(big_bad_ulp(in_arr).astype(ftype), out_arr) - - -BIG_FLOAT = np.float64 - - -def test_round_trip(): - scaling_type = np.float32 - rng = np.random.RandomState(20111121) - N = 10000 - sd_10s = range(-20, 51, 5) - iuint_types = sctypes['int'] + sctypes['uint'] - # Remove types which cannot be set into nifti header datatype - nifti_supported = supported_np_types(Nifti1Header()) - iuint_types = [t for t in iuint_types if t in nifti_supported] - f_types = [np.float32, np.float64] - # Expanding standard deviations - for sd_10 in sd_10s: - sd = 10.0**sd_10 - V_in = rng.normal(0, sd, size=(N, 1)) - for in_type in f_types: - for out_type in iuint_types: - check_arr(sd_10, V_in, in_type, out_type, scaling_type) - # Spread integers across range - for sd in np.linspace(0.05, 0.5, 5): - for in_type in iuint_types: - info = np.iinfo(in_type) - mn, mx = info.min, info.max - type_range = mx - mn - center = type_range / 2.0 + mn - # float(sd) because type_range can be type 'long' - width = type_range * float(sd) - V_in = rng.normal(center, width, size=(N, 1)) - for out_type in iuint_types: - check_arr(sd, V_in, in_type, out_type, scaling_type) - - -def check_arr(test_id, V_in, in_type, out_type, scaling_type): - arr, arr_dash, slope, inter = check_params(V_in, in_type, out_type) - if arr_dash is None: - # Scaling causes a header or writer error - return - nzs = arr != 0 # avoid divide by zero error - if not np.any(nzs): - if DEBUG: - raise ValueError('Array all zero') - return - arr = arr[nzs] - arr_dash_L = arr_dash.astype(BIG_FLOAT)[nzs] - top = arr - arr_dash_L - if not np.any(top != 0): - return - rel_err = np.abs(top / arr) - abs_err = np.abs(top) - if slope == 1: # integers output, offset only scaling - if {in_type, out_type} == {np.int64, np.uint64}: - # Scaling to or from 64 bit ints can go outside range of continuous - # integers for float64 and thus lose precision; take this into - # account - A = arr.astype(float) - Ai = A - inter - ulps = [big_bad_ulp(A), big_bad_ulp(Ai)] - exp_abs_err = np.max(ulps, axis=0) - else: # floats can give full precision - no error! - exp_abs_err = np.zeros_like(abs_err) - rel_thresh = 0 - else: - # Error from integer rounding - inting_err = np.abs(scaling_type(slope) / 2) - inting_err = inting_err + ulp(inting_err) - # Error from calculation of inter - inter_err = ulp(scaling_type(inter)) - # Max abs error from floating point - with np.errstate(over='ignore'): - Ai = arr - scaling_type(inter) - Ais = Ai / scaling_type(slope) - exp_abs_err = inting_err + inter_err + (big_bad_ulp(Ai) + big_bad_ulp(Ais)) - # Relative scaling error from calculation of slope - # This threshold needs to be 2 x larger on windows 32 bit and PPC for - # some reason - rel_thresh = ulp(scaling_type(1)) - test_vals = (abs_err <= exp_abs_err) | (rel_err <= rel_thresh) - this_test = np.all(test_vals) - if DEBUG: - abs_fails = abs_err > exp_abs_err - rel_fails = rel_err > rel_thresh - all_fails = abs_fails & rel_fails - if np.any(rel_fails): - abs_mx_e = abs_err[rel_fails].max() - exp_abs_mx_e = exp_abs_err[rel_fails].max() - else: - abs_mx_e = None - exp_abs_mx_e = None - if np.any(abs_fails): - rel_mx_e = rel_err[abs_fails].max() - else: - rel_mx_e = None - print( - ( - test_id, - np.dtype(in_type).str, - np.dtype(out_type).str, - exp_abs_mx_e, - abs_mx_e, - rel_thresh, - rel_mx_e, - slope, - inter, - ) - ) - # To help debugging failures with --pdb-failure - np.nonzero(all_fails) - assert this_test diff --git a/nibabel/tests/test_rstutils.py b/nibabel/tests/test_rstutils.py deleted file mode 100644 index eab1969857..0000000000 --- a/nibabel/tests/test_rstutils.py +++ /dev/null @@ -1,106 +0,0 @@ -"""Test printable table""" - -import numpy as np -import pytest - -from ..rstutils import rst_table - - -def test_rst_table(): - # Tests for printable table function - R, C = 3, 4 - cell_values = np.arange(R * C).reshape((R, C)) - assert ( - rst_table(cell_values) - == """+--------+--------+--------+--------+--------+ -| | col[0] | col[1] | col[2] | col[3] | -+========+========+========+========+========+ -| row[0] | 0.00 | 1.00 | 2.00 | 3.00 | -| row[1] | 4.00 | 5.00 | 6.00 | 7.00 | -| row[2] | 8.00 | 9.00 | 10.00 | 11.00 | -+--------+--------+--------+--------+--------+""" - ) - assert ( - rst_table(cell_values, ['a', 'b', 'c']) - == """+---+--------+--------+--------+--------+ -| | col[0] | col[1] | col[2] | col[3] | -+===+========+========+========+========+ -| a | 0.00 | 1.00 | 2.00 | 3.00 | -| b | 4.00 | 5.00 | 6.00 | 7.00 | -| c | 8.00 | 9.00 | 10.00 | 11.00 | -+---+--------+--------+--------+--------+""" - ) - with pytest.raises(ValueError): - rst_table(cell_values, ['a', 'b']) - with pytest.raises(ValueError): - rst_table(cell_values, ['a', 'b', 'c', 'd']) - assert ( - rst_table(cell_values, None, ['1', '2', '3', '4']) - == """+--------+-------+-------+-------+-------+ -| | 1 | 2 | 3 | 4 | -+========+=======+=======+=======+=======+ -| row[0] | 0.00 | 1.00 | 2.00 | 3.00 | -| row[1] | 4.00 | 5.00 | 6.00 | 7.00 | -| row[2] | 8.00 | 9.00 | 10.00 | 11.00 | -+--------+-------+-------+-------+-------+""" - ) - with pytest.raises(ValueError): - rst_table(cell_values, None, ['1', '2', '3']) - with pytest.raises(ValueError): - rst_table(cell_values, None, list('12345')) - assert ( - rst_table(cell_values, title='A title') - == """******* -A title -******* - -+--------+--------+--------+--------+--------+ -| | col[0] | col[1] | col[2] | col[3] | -+========+========+========+========+========+ -| row[0] | 0.00 | 1.00 | 2.00 | 3.00 | -| row[1] | 4.00 | 5.00 | 6.00 | 7.00 | -| row[2] | 8.00 | 9.00 | 10.00 | 11.00 | -+--------+--------+--------+--------+--------+""" - ) - assert ( - rst_table(cell_values, val_fmt='{0}') - == """+--------+--------+--------+--------+--------+ -| | col[0] | col[1] | col[2] | col[3] | -+========+========+========+========+========+ -| row[0] | 0 | 1 | 2 | 3 | -| row[1] | 4 | 5 | 6 | 7 | -| row[2] | 8 | 9 | 10 | 11 | -+--------+--------+--------+--------+--------+""" - ) - # Doing a fancy cell format - cell_values_back = np.arange(R * C)[::-1].reshape((R, C)) - cell_3d = np.dstack((cell_values, cell_values_back)) - assert ( - rst_table(cell_3d, val_fmt='{0[0]}-{0[1]}') - == """+--------+--------+--------+--------+--------+ -| | col[0] | col[1] | col[2] | col[3] | -+========+========+========+========+========+ -| row[0] | 0-11 | 1-10 | 2-9 | 3-8 | -| row[1] | 4-7 | 5-6 | 6-5 | 7-4 | -| row[2] | 8-3 | 9-2 | 10-1 | 11-0 | -+--------+--------+--------+--------+--------+""" - ) - # Test formatting characters - formats = dict(down='!', along='_', thick_long='~', cross='%', title_heading='#') - assert ( - rst_table(cell_values, title='A title', format_chars=formats) - == """####### -A title -####### - -%________%________%________%________%________% -! ! col[0] ! col[1] ! col[2] ! col[3] ! -%~~~~~~~~%~~~~~~~~%~~~~~~~~%~~~~~~~~%~~~~~~~~% -! row[0] ! 0.00 ! 1.00 ! 2.00 ! 3.00 ! -! row[1] ! 4.00 ! 5.00 ! 6.00 ! 7.00 ! -! row[2] ! 8.00 ! 9.00 ! 10.00 ! 11.00 ! -%________%________%________%________%________%""" - ) - formats['funny_value'] = '!' - with pytest.raises(ValueError): - rst_table(cell_values, title='A title', format_chars=formats) diff --git a/nibabel/tests/test_scaling.py b/nibabel/tests/test_scaling.py deleted file mode 100644 index ccc379c256..0000000000 --- a/nibabel/tests/test_scaling.py +++ /dev/null @@ -1,225 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Test for scaling / rounding in volumeutils module""" - -import warnings -from io import BytesIO - -import numpy as np -import pytest -from numpy.testing import assert_array_equal - -from ..casting import sctypes, type_info -from ..testing import suppress_warnings -from ..volumeutils import apply_read_scaling, array_from_file, array_to_file, finite_range -from .test_volumeutils import _calculate_scale - -# Debug print statements -DEBUG = True - - -@pytest.mark.parametrize( - ('in_arr', 'res'), - [ - ([[-1, 0, 1], [np.inf, np.nan, -np.inf]], (-1, 1)), - (np.array([[-1, 0, 1], [np.inf, np.nan, -np.inf]]), (-1, 1)), - ([[np.nan], [np.nan]], (np.inf, -np.inf)), # all nans slices - (np.zeros((3, 4, 5)) + np.nan, (np.inf, -np.inf)), - ([[-np.inf], [np.inf]], (np.inf, -np.inf)), # all infs slices - (np.zeros((3, 4, 5)) + np.inf, (np.inf, -np.inf)), - ([[np.nan, -1, 2], [-2, np.nan, 1]], (-2, 2)), - ([[np.nan, -np.inf, 2], [-2, np.nan, np.inf]], (-2, 2)), - ([[-np.inf, 2], [np.nan, 1]], (1, 2)), # good max case - ([np.nan], (np.inf, -np.inf)), - ([np.inf], (np.inf, -np.inf)), - ([-np.inf], (np.inf, -np.inf)), - ([np.inf, 1], (1, 1)), # only look at finite values - ([-np.inf, 1], (1, 1)), - ([[], []], (np.inf, -np.inf)), # empty array - (np.array([[-3, 0, 1], [2, -1, 4]], dtype=int), (-3, 4)), - (np.array([[1, 0, 1], [2, 3, 4]], dtype=np.uint), (0, 4)), - ([0.0, 1, 2, 3], (0, 3)), - # Complex comparison works as if they are floats - ([[np.nan, -1 - 100j, 2], [-2, np.nan, 1 + 100j]], (-2, 2)), - ([[np.nan, -1, 2 - 100j], [-2 + 100j, np.nan, 1]], (-2 + 100j, 2 - 100j)), - ], -) -def test_finite_range(in_arr, res): - # Finite range utility function - assert finite_range(in_arr) == res - assert finite_range(in_arr, False) == res - assert finite_range(in_arr, check_nan=False) == res - has_nan = np.any(np.isnan(in_arr)) - assert finite_range(in_arr, True) == res + (has_nan,) - assert finite_range(in_arr, check_nan=True) == res + (has_nan,) - in_arr = np.array(in_arr) - flat_arr = in_arr.ravel() - assert finite_range(flat_arr) == res - assert finite_range(flat_arr, True) == res + (has_nan,) - # Check float types work as complex - if in_arr.dtype.kind == 'f': - c_arr = in_arr.astype(np.complex128) - assert finite_range(c_arr) == res - assert finite_range(c_arr, True) == res + (has_nan,) - - -def test_finite_range_err(): - # Test error cases - a = np.array([[1.0, 0, 1], [2, 3, 4]]).view([('f1', 'f')]) - with pytest.raises(TypeError): - finite_range(a) - - -@pytest.mark.parametrize('out_type', [np.int16, np.float32]) -def test_a2f_mn_mx(out_type): - # Test array to file mn, mx handling - str_io = BytesIO() - arr = np.arange(6, dtype=out_type) - arr_orig = arr.copy() # safe backup for testing against - # Basic round trip to warm up - array_to_file(arr, str_io) - data_back = array_from_file(arr.shape, out_type, str_io) - assert_array_equal(arr, data_back) - # Clip low - array_to_file(arr, str_io, mn=2) - data_back = array_from_file(arr.shape, out_type, str_io) - # arr unchanged - assert_array_equal(arr, arr_orig) - # returned value clipped low - assert_array_equal(data_back, [2, 2, 2, 3, 4, 5]) - # Clip high - array_to_file(arr, str_io, mx=4) - data_back = array_from_file(arr.shape, out_type, str_io) - # arr unchanged - assert_array_equal(arr, arr_orig) - # returned value clipped high - assert_array_equal(data_back, [0, 1, 2, 3, 4, 4]) - # Clip both - array_to_file(arr, str_io, mn=2, mx=4) - data_back = array_from_file(arr.shape, out_type, str_io) - # arr unchanged - assert_array_equal(arr, arr_orig) - # returned value clipped high - assert_array_equal(data_back, [2, 2, 2, 3, 4, 4]) - - -def test_a2f_nan2zero(): - # Test conditions under which nans written to zero - arr = np.array([np.nan, 99.0], dtype=np.float32) - str_io = BytesIO() - array_to_file(arr, str_io) - data_back = array_from_file(arr.shape, np.float32, str_io) - assert_array_equal(np.isnan(data_back), [True, False]) - # nan2zero ignored for floats - array_to_file(arr, str_io, nan2zero=True) - data_back = array_from_file(arr.shape, np.float32, str_io) - assert_array_equal(np.isnan(data_back), [True, False]) - # Integer output with nan2zero gives zero - with np.errstate(invalid='ignore'): - array_to_file(arr, str_io, np.int32, nan2zero=True) - data_back = array_from_file(arr.shape, np.int32, str_io) - assert_array_equal(data_back, [0, 99]) - # Integer output with nan2zero=False gives whatever astype gives - with np.errstate(invalid='ignore'): - array_to_file(arr, str_io, np.int32, nan2zero=False) - data_back = array_from_file(arr.shape, np.int32, str_io) - assert_array_equal(data_back, [np.array(np.nan).astype(np.int32), 99]) - - -@pytest.mark.parametrize( - ('in_type', 'out_type'), - [ - (np.int16, np.int16), - (np.int16, np.int8), - (np.uint16, np.uint8), - (np.int32, np.int8), - (np.float32, np.uint8), - (np.float32, np.int16), - ], -) -def test_array_file_scales(in_type, out_type): - # Test scaling works for max, min when going from larger to smaller type, - # and from float to integer. - bio = BytesIO() - out_dtype = np.dtype(out_type) - arr = np.zeros((3,), dtype=in_type) - info = type_info(in_type) - arr[0], arr[1] = info['min'], info['max'] - slope, inter, mn, mx = _calculate_scale(arr, out_dtype, True) - array_to_file(arr, bio, out_type, 0, inter, slope, mn, mx) - bio.seek(0) - arr2 = array_from_file(arr.shape, out_dtype, bio) - arr3 = apply_read_scaling(arr2, slope, inter) - # Max rounding error for integer type - max_miss = slope / 2.0 - assert np.all(np.abs(arr - arr3) <= max_miss) - - -@pytest.mark.parametrize( - ('category0', 'category1', 'overflow'), - [ - # Confirm that, for all ints and uints as input, and all possible outputs, - # for any simple way of doing the calculation, the result is near enough - ('int', 'int', False), - ('uint', 'int', False), - # Converting floats to integer - ('float', 'int', True), - ('float', 'uint', True), - ('complex', 'int', True), - ('complex', 'uint', True), - ], -) -def test_scaling_in_abstract(category0, category1, overflow): - for in_type in sctypes[category0]: - for out_type in sctypes[category1]: - if overflow: - with suppress_warnings(): - check_int_a2f(in_type, out_type) - else: - check_int_a2f(in_type, out_type) - - -def check_int_a2f(in_type, out_type): - # Check that array to / from file returns roughly the same as input - big_floater = sctypes['float'][-1] - info = type_info(in_type) - this_min, this_max = info['min'], info['max'] - if not in_type in sctypes['complex']: - data = np.array([this_min, this_max], in_type) - # Bug in numpy 1.6.2 on PPC leading to infs - abort - if not np.all(np.isfinite(data)): - if DEBUG: - print(f'Hit PPC max -> inf bug; skip in_type {in_type}') - return - else: # Funny behavior with complex256 - data = np.zeros((2,), in_type) - data[0] = this_min + 0j - data[1] = this_max + 0j - str_io = BytesIO() - try: - scale, inter, mn, mx = _calculate_scale(data, out_type, True) - except ValueError as e: - if DEBUG: - warnings.warn(str((in_type, out_type, e))) - return - array_to_file(data, str_io, out_type, 0, inter, scale, mn, mx) - data_back = array_from_file(data.shape, out_type, str_io) - data_back = apply_read_scaling(data_back, scale, inter) - assert np.allclose(big_floater(data), big_floater(data_back)) - # Try with analyze-size scale and inter - scale32 = np.float32(scale) - inter32 = np.float32(inter) - if scale32 == np.inf or inter32 == np.inf: - return - data_back = array_from_file(data.shape, out_type, str_io) - data_back = apply_read_scaling(data_back, scale32, inter32) - # Clip at extremes to remove inf - info = type_info(in_type) - out_min, out_max = info['min'], info['max'] - assert np.allclose(big_floater(data), big_floater(np.clip(data_back, out_min, out_max))) diff --git a/nibabel/tests/test_scripts.py b/nibabel/tests/test_scripts.py deleted file mode 100644 index 0ff4ce1984..0000000000 --- a/nibabel/tests/test_scripts.py +++ /dev/null @@ -1,523 +0,0 @@ -# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -"""Test scripts - -Test running scripts -""" - -import csv -import os -import shutil -import sys -import unittest -from glob import glob -from os.path import abspath, basename, dirname, exists, splitext -from os.path import join as pjoin - -import numpy as np -import pytest -from numpy.testing import assert_almost_equal - -import nibabel as nib - -from ..loadsave import load -from ..orientations import aff2axcodes, inv_ornt_aff -from ..testing import assert_data_similar, assert_dt_equal, assert_re_in -from ..tmpdirs import InTemporaryDirectory -from .nibabel_data import needs_nibabel_data -from .scriptrunner import ScriptRunner -from .test_parrec import DTI_PAR_BVALS, DTI_PAR_BVECS -from .test_parrec import EXAMPLE_IMAGES as PARREC_EXAMPLES -from .test_parrec_data import AFF_OFF, BALLS - - -def _proc_stdout(stdout): - stdout_str = stdout.decode('latin1').strip() - return stdout_str.replace(os.linesep, '\n') - - -runner = ScriptRunner( - script_sdir='bin', debug_print_var='NIPY_DEBUG_PRINT', output_processor=_proc_stdout -) -run_command = runner.run_command - - -def script_test(func): - # Decorator to label test as a script_test - func.script_test = True - return func - - -script_test.__test__ = False # It's not a test - -DATA_PATH = abspath(pjoin(dirname(__file__), 'data')) - - -def load_small_file(): - try: - load(pjoin(DATA_PATH, 'small.mnc')) - return True - except: - return False - - -def check_nib_ls_example4d(opts=[], hdrs_str='', other_str=''): - # test nib-ls script - fname = pjoin(DATA_PATH, 'example4d.nii.gz') - expected_re = ( - ' (int16|[<>]i2) \\[128, 96, 24, 2\\] 2.00x2.00x2.20x2000.00 ' - f'#exts: 2{hdrs_str} sform{other_str}$' - ) - cmd = ['nib-ls'] + opts + [fname] - code, stdout, stderr = run_command(cmd) - assert fname == stdout[: len(fname)] - assert_re_in(expected_re, stdout[len(fname) :]) - - -def check_nib_diff_examples(): - fnames = [pjoin(DATA_PATH, f) for f in ('standard.nii.gz', 'example4d.nii.gz')] - code, stdout, stderr = run_command(['nib-diff'] + fnames, check_code=False) - checked_fields = [ - 'Field/File', - 'regular', - 'dim_info', - 'dim', - 'datatype', - 'bitpix', - 'pixdim', - 'slice_end', - 'xyzt_units', - 'cal_max', - 'descrip', - 'qform_code', - 'sform_code', - 'quatern_b', - 'quatern_c', - 'quatern_d', - 'qoffset_x', - 'qoffset_y', - 'qoffset_z', - 'srow_x', - 'srow_y', - 'srow_z', - 'DATA(md5)', - 'DATA(diff 1:)', - ] - for item in checked_fields: - assert item in stdout - - fnames2 = [pjoin(DATA_PATH, f) for f in ('example4d.nii.gz', 'example4d.nii.gz')] - code, stdout, stderr = run_command(['nib-diff'] + fnames2, check_code=False) - assert stdout == 'These files are identical.' - - fnames3 = [ - pjoin(DATA_PATH, f) - for f in ('standard.nii.gz', 'example4d.nii.gz', 'example_nifti2.nii.gz') - ] - code, stdout, stderr = run_command(['nib-diff'] + fnames3, check_code=False) - for item in checked_fields: - assert item in stdout - - fnames4 = [ - pjoin(DATA_PATH, f) for f in ('standard.nii.gz', 'standard.nii.gz', 'standard.nii.gz') - ] - code, stdout, stderr = run_command(['nib-diff'] + fnames4, check_code=False) - assert stdout == 'These files are identical.' - - code, stdout, stderr = run_command(['nib-diff', '--dt', 'float64'] + fnames, check_code=False) - for item in checked_fields: - assert item in stdout - - -@pytest.mark.parametrize( - 'args', - [ - [], - [['-H', 'dim,bitpix'], r' \[ 4 128 96 24 2 1 1 1\] 16'], - [['-c'], '', ' !1030 uniques. Use --all-counts'], - [['-c', '--all-counts'], '', ' 2:3 3:2 4:1 5:1.*'], - # both stats and counts - [['-c', '-s', '--all-counts'], '', r' \[229725\] \[2, 1.2e\+03\] 2:3 3:2 4:1 5:1.*'], - # and must not error out if we allow for zeros - [ - ['-c', '-s', '-z', '--all-counts'], - '', - r' \[589824\] \[0, 1.2e\+03\] 0:360099 2:3 3:2 4:1 5:1.*', - ], - ], -) -@script_test -def test_nib_ls(args): - check_nib_ls_example4d(*args) - - -@unittest.skipUnless(load_small_file(), "Can't load the small.mnc file") -@script_test -def test_nib_ls_multiple(): - # verify that correctly lists/formats for multiple files - fnames = [ - pjoin(DATA_PATH, f) - for f in ('example4d.nii.gz', 'example_nifti2.nii.gz', 'small.mnc', 'nifti2.hdr') - ] - code, stdout, stderr = run_command(['nib-ls'] + fnames) - stdout_lines = stdout.split('\n') - assert len(stdout_lines) == 4 - - # they should be indented correctly. Since all files are int type - - ln = max(len(f) for f in fnames) - i_str = ' i' if sys.byteorder == 'little' else ' -TINY) - assert np.all(out_grid < np.array(out_shape) + TINY) - - -def get_outspace_params(): - # Return in_shape, in_aff, vox, out_shape, out_aff for output space tests - # Put in function to use also for resample_to_output tests - # Some affines as input to the tests - trans_123 = [[1, 0, 0, 1], [0, 1, 0, 2], [0, 0, 1, 3], [0, 0, 0, 1]] - trans_m123 = [[1, 0, 0, -1], [0, 1, 0, -2], [0, 0, 1, -3], [0, 0, 0, 1]] - rot_3 = from_matvec(euler2mat(np.pi / 4), [0, 0, 0]) - return ( # in_shape, in_aff, vox, out_shape, out_aff - # Identity - ((2, 3, 4), np.eye(4), None, (2, 3, 4), np.eye(4)), - # Flip first axis - ( - (2, 3, 4), - np.diag([-1, 1, 1, 1]), - None, - (2, 3, 4), - [ - [1, 0, 0, -1], # axis reversed -> -ve offset - [0, 1, 0, 0], - [0, 0, 1, 0], - [0, 0, 0, 1], - ], - ), - # zooms for affine > 1 -> larger grid with default 1mm output voxels - ((2, 3, 4), np.diag([4, 5, 6, 1]), None, (5, 11, 19), np.eye(4)), - # set output voxels to be same size as input. back to original shape - ((2, 3, 4), np.diag([4, 5, 6, 1]), (4, 5, 6), (2, 3, 4), np.diag([4, 5, 6, 1])), - # Translation preserved in output - ((2, 3, 4), trans_123, None, (2, 3, 4), trans_123), - ((2, 3, 4), trans_m123, None, (2, 3, 4), trans_m123), - # rotation around 3rd axis - ( - (2, 3, 4), - rot_3, - None, - # x diff, y diff now 3 cos pi / 4 == 2.12, ceil to 3, add 1 - # most negative x now 2 cos pi / 4 - (4, 4, 4), - [ - [1, 0, 0, -2 * np.cos(np.pi / 4)], - [0, 1, 0, 0], - [0, 0, 1, 0], - [0, 0, 0, 1], - ], - ), - # Less than 3 axes - ((2, 3), np.eye(4), None, (2, 3), np.eye(4)), - ((2,), np.eye(4), None, (2,), np.eye(4)), - # Number of voxel sizes matches length - ((2, 3), np.diag([4, 5, 6, 1]), (4, 5), (2, 3), np.diag([4, 5, 1, 1])), - ) - - -def test_vox2out_vox(): - # Test world space bounding box - # Test basic case, identity, no voxel sizes passed - shape, aff = vox2out_vox(((2, 3, 4), np.eye(4))) - assert shape == (2, 3, 4) - assert (aff == np.eye(4)).all() - for in_shape, in_aff, vox, out_shape, out_aff in get_outspace_params(): - img = Nifti1Image(np.ones(in_shape), in_aff) - for input in ((in_shape, in_aff), img): - shape, aff = vox2out_vox(input, vox) - assert_all_in(in_shape, in_aff, shape, aff) - assert shape == out_shape - assert_almost_equal(aff, out_aff) - assert isinstance(shape, tuple) - assert isinstance(shape[0], int) - # Enforce number of axes - with pytest.raises(ValueError): - vox2out_vox(((2, 3, 4, 5), np.eye(4))) - with pytest.raises(ValueError): - vox2out_vox(((2, 3, 4, 5, 6), np.eye(4))) - # Voxel sizes must be positive - with pytest.raises(ValueError): - vox2out_vox(((2, 3, 4), np.eye(4), [-1, 1, 1])) - with pytest.raises(ValueError): - vox2out_vox(((2, 3, 4), np.eye(4), [1, 0, 1])) - - -def test_slice2volume(): - # Get affine expressing selection of single slice from volume - for axis, def_aff in zip( - (0, 1, 2), - ( - [[0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1]], - [[1, 0, 0], [0, 0, 0], [0, 1, 0], [0, 0, 1]], - [[1, 0, 0], [0, 1, 0], [0, 0, 0], [0, 0, 1]], - ), - ): - for val in (0, 5, 10): - exp_aff = np.array(def_aff) - exp_aff[axis, -1] = val - assert (slice2volume(val, axis) == exp_aff).all() - - -@pytest.mark.parametrize( - ('index', 'axis'), - [ - [-1, 0], - [0, -1], - [0, 3], - ], -) -def test_slice2volume_exception(index, axis): - with pytest.raises(ValueError): - slice2volume(index, axis) diff --git a/nibabel/tests/test_spatialimages.py b/nibabel/tests/test_spatialimages.py deleted file mode 100644 index 7890ba48e4..0000000000 --- a/nibabel/tests/test_spatialimages.py +++ /dev/null @@ -1,628 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Testing spatialimages""" - -from io import BytesIO - -import numpy as np -import pytest -from numpy.testing import assert_array_almost_equal - -from .. import load as top_load -from ..imageclasses import spatial_axes_first -from ..spatialimages import HeaderDataError, SpatialHeader, SpatialImage -from ..testing import bytesio_round_trip, deprecated_to, expires, memmap_after_ufunc -from ..tmpdirs import InTemporaryDirectory - - -def test_header_init(): - # test the basic header - hdr = SpatialHeader() - assert hdr.get_data_dtype() == np.dtype(np.float32) - assert hdr.get_data_shape() == (0,) - assert hdr.get_zooms() == (1.0,) - hdr = SpatialHeader(np.float64) - assert hdr.get_data_dtype() == np.dtype(np.float64) - assert hdr.get_data_shape() == (0,) - assert hdr.get_zooms() == (1.0,) - hdr = SpatialHeader(np.float64, shape=(1, 2, 3)) - assert hdr.get_data_dtype() == np.dtype(np.float64) - assert hdr.get_data_shape() == (1, 2, 3) - assert hdr.get_zooms() == (1.0, 1.0, 1.0) - hdr = SpatialHeader(np.float64, shape=(1, 2, 3), zooms=None) - assert hdr.get_data_dtype() == np.dtype(np.float64) - assert hdr.get_data_shape() == (1, 2, 3) - assert hdr.get_zooms() == (1.0, 1.0, 1.0) - hdr = SpatialHeader(np.float64, shape=(1, 2, 3), zooms=(3.0, 2.0, 1.0)) - assert hdr.get_data_dtype() == np.dtype(np.float64) - assert hdr.get_data_shape() == (1, 2, 3) - assert hdr.get_zooms() == (3.0, 2.0, 1.0) - - -def test_from_header(): - # check from header class method. Note equality checks below, - # equality methods used here too. - empty = SpatialHeader.from_header() - assert SpatialHeader() == empty - empty = SpatialHeader.from_header(None) - assert SpatialHeader() == empty - hdr = SpatialHeader(np.float64, shape=(1, 2, 3), zooms=(3.0, 2.0, 1.0)) - copy = SpatialHeader.from_header(hdr) - assert hdr == copy - assert hdr is not copy - - class C: - def get_data_dtype(self): - return np.dtype('u2') - - def get_data_shape(self): - return (5, 4, 3) - - def get_zooms(self): - return (10.0, 9.0, 8.0) - - converted = SpatialHeader.from_header(C()) - assert isinstance(converted, SpatialHeader) - assert converted.get_data_dtype() == np.dtype('u2') - assert converted.get_data_shape() == (5, 4, 3) - assert converted.get_zooms() == (10.0, 9.0, 8.0) - - -def test_eq(): - hdr = SpatialHeader() - other = SpatialHeader() - assert hdr == other - other = SpatialHeader('u2') - assert hdr != other - other = SpatialHeader(shape=(1, 2, 3)) - assert hdr != other - hdr = SpatialHeader(shape=(1, 2)) - other = SpatialHeader(shape=(1, 2)) - assert hdr == other - other = SpatialHeader(shape=(1, 2), zooms=(2.0, 3.0)) - assert hdr != other - - -def test_copy(): - # test that copy makes independent copy - hdr = SpatialHeader(np.float64, shape=(1, 2, 3), zooms=(3.0, 2.0, 1.0)) - hdr_copy = hdr.copy() - hdr.set_data_shape((4, 5, 6)) - assert hdr.get_data_shape() == (4, 5, 6) - assert hdr_copy.get_data_shape() == (1, 2, 3) - hdr.set_zooms((4, 5, 6)) - assert hdr.get_zooms() == (4, 5, 6) - assert hdr_copy.get_zooms() == (3, 2, 1) - hdr.set_data_dtype(np.uint8) - assert hdr.get_data_dtype() == np.dtype(np.uint8) - assert hdr_copy.get_data_dtype() == np.dtype(np.float64) - - -def test_shape_zooms(): - hdr = SpatialHeader() - hdr.set_data_shape((1, 2, 3)) - assert hdr.get_data_shape() == (1, 2, 3) - assert hdr.get_zooms() == (1.0, 1.0, 1.0) - hdr.set_zooms((4, 3, 2)) - assert hdr.get_zooms() == (4.0, 3.0, 2.0) - hdr.set_data_shape((1, 2)) - assert hdr.get_data_shape() == (1, 2) - assert hdr.get_zooms() == (4.0, 3.0) - hdr.set_data_shape((1, 2, 3)) - assert hdr.get_data_shape() == (1, 2, 3) - assert hdr.get_zooms() == (4.0, 3.0, 1.0) - # null shape is (0,) - hdr.set_data_shape(()) - assert hdr.get_data_shape() == (0,) - assert hdr.get_zooms() == (1.0,) - # zooms of wrong lengths raise error - with pytest.raises(HeaderDataError): - hdr.set_zooms((4.0, 3.0)) - with pytest.raises(HeaderDataError): - hdr.set_zooms((4.0, 3.0, 2.0, 1.0)) - # as do negative zooms - with pytest.raises(HeaderDataError): - hdr.set_zooms((4.0, 3.0, -2.0)) - - -def test_data_dtype(): - hdr = SpatialHeader() - assert hdr.get_data_dtype() == np.dtype(np.float32) - hdr.set_data_dtype(np.float64) - assert hdr.get_data_dtype() == np.dtype(np.float64) - hdr.set_data_dtype('u2') - assert hdr.get_data_dtype() == np.dtype(np.uint16) - - -def test_affine(): - hdr = SpatialHeader(np.float64, shape=(1, 2, 3), zooms=(3.0, 2.0, 1.0)) - assert_array_almost_equal( - hdr.get_best_affine(), - [ - [-3.0, 0, 0, 0], - [0, 2, 0, -1], - [0, 0, 1, -1], - [0, 0, 0, 1], - ], - ) - hdr.default_x_flip = False - assert_array_almost_equal( - hdr.get_best_affine(), - [ - [3.0, 0, 0, 0], - [0, 2, 0, -1], - [0, 0, 1, -1], - [0, 0, 0, 1], - ], - ) - assert np.array_equal(hdr.get_base_affine(), hdr.get_best_affine()) - - -def test_read_data(): - class CHeader(SpatialHeader): - data_layout = 'C' - - for klass, order in ((SpatialHeader, 'F'), (CHeader, 'C')): - hdr = klass(np.int32, shape=(1, 2, 3), zooms=(3.0, 2.0, 1.0)) - fobj = BytesIO() - data = np.arange(6).reshape((1, 2, 3)) - hdr.data_to_fileobj(data, fobj) - assert fobj.getvalue() == data.astype(np.int32).tobytes(order=order) - # data_to_fileobj accepts kwarg 'rescale', but no effect in this case - fobj.seek(0) - hdr.data_to_fileobj(data, fobj, rescale=True) - assert fobj.getvalue() == data.astype(np.int32).tobytes(order=order) - # data_to_fileobj can be a list - fobj.seek(0) - hdr.data_to_fileobj(data.tolist(), fobj, rescale=True) - assert fobj.getvalue() == data.astype(np.int32).tobytes(order=order) - # Read data back again - fobj.seek(0) - data2 = hdr.data_from_fileobj(fobj) - assert (data == data2).all() - - -class DataLike: - # Minimal class implementing 'data' API - shape = (3,) - - def __array__(self, dtype='int16'): - return np.arange(3, dtype=dtype) - - -class TestSpatialImage: - # class for testing images - image_class = SpatialImage - can_save = False - - def test_isolation(self): - # Test image isolated from external changes to header and affine - img_klass = self.image_class - arr = np.arange(24, dtype=np.int16).reshape((2, 3, 4)) - aff = np.eye(4) - img = img_klass(arr, aff) - assert (img.affine == aff).all() - aff[0, 0] = 99 - assert not np.all(img.affine == aff) - # header, created by image creation - ihdr = img.header - # Pass it back in - img = img_klass(arr, aff, ihdr) - # Check modifying header outside does not modify image - ihdr.set_zooms((4, 5, 6)) - assert img.header != ihdr - - def test_float_affine(self): - # Check affines get converted to float - img_klass = self.image_class - arr = np.arange(3, dtype=np.int16) - img = img_klass(arr, np.eye(4, dtype=np.float32)) - assert img.affine.dtype == np.dtype(np.float64) - img = img_klass(arr, np.eye(4, dtype=np.int16)) - assert img.affine.dtype == np.dtype(np.float64) - - def test_images(self): - # Assumes all possible images support int16 - # See https://github.com/nipy/nibabel/issues/58 - arr = np.arange(24, dtype=np.int16).reshape((2, 3, 4)) - img = self.image_class(arr, None) - assert (img.get_fdata() == arr).all() - assert img.affine is None - - def test_default_header(self): - # Check default header is as expected - arr = np.arange(24, dtype=np.int16).reshape((2, 3, 4)) - img = self.image_class(arr, None) - hdr = self.image_class.header_class() - hdr.set_data_shape(arr.shape) - hdr.set_data_dtype(arr.dtype) - assert img.header == hdr - - def test_data_api(self): - # Test minimal api data object can initialize - img = self.image_class(DataLike(), None) - # Shape may be promoted to higher dimension, but may not reorder or - # change size - assert (img.get_fdata().flatten() == np.arange(3)).all() - assert img.shape[:1] == (3,) - assert np.prod(img.shape) == 3 - - def check_dtypes(self, expected, actual): - # Some images will want dtypes to be equal including endianness, - # others may only require the same type - assert expected == actual - - def test_data_default(self): - # check that the default dtype comes from the data if the header - # is None, and that unsupported dtypes raise an error - img_klass = self.image_class - hdr_klass = self.image_class.header_class - data = np.arange(24, dtype=np.int32).reshape((2, 3, 4)) - affine = np.eye(4) - img = img_klass(data, affine) - self.check_dtypes(data.dtype, img.get_data_dtype()) - header = hdr_klass() - header.set_data_dtype(np.float32) - img = img_klass(data, affine, header) - self.check_dtypes(np.dtype(np.float32), img.get_data_dtype()) - - def test_data_shape(self): - # Check shape correctly read - img_klass = self.image_class - # Assumes all possible images support int16 - # See https://github.com/nipy/nibabel/issues/58 - arr = np.arange(4, dtype=np.int16) - img = img_klass(arr, np.eye(4)) - # Shape may be promoted to higher dimension, but may not reorder or - # change size - assert img.shape[:1] == (4,) - assert np.prod(img.shape) == 4 - img = img_klass(np.zeros((2, 3, 4), dtype=np.float32), np.eye(4)) - assert img.shape == (2, 3, 4) - - def test_str(self): - # Check something comes back from string representation - img_klass = self.image_class - # Assumes all possible images support int16 - # See https://github.com/nipy/nibabel/issues/58 - arr = np.arange(5, dtype=np.int16) - img = img_klass(arr, np.eye(4)) - assert len(str(img)) > 0 - # Shape may be promoted to higher dimension, but may not reorder or - # change size - assert img.shape[:1] == (5,) - assert np.prod(img.shape) == 5 - img = img_klass(np.zeros((2, 3, 4), dtype=np.int16), np.eye(4)) - assert len(str(img)) > 0 - - def test_get_fdata(self): - # Test array image and proxy image interface for floating point data - img_klass = self.image_class - in_data_template = np.arange(24, dtype=np.int16).reshape((2, 3, 4)) - in_data = in_data_template.copy() - img = img_klass(in_data, None) - assert in_data is img.dataobj - # The get_fdata method changes the array to floating point type - assert img.get_fdata(dtype='f4').dtype == np.dtype(np.float32) - fdata_32 = img.get_fdata(dtype=np.float32) - assert fdata_32.dtype == np.dtype(np.float32) - # Caching is specific to data dtype. If we reload with default data - # type, the cache gets reset - fdata_32[:] = 99 - # Cache has been modified, we pick up the modifications, but only for - # the cached data type - assert (img.get_fdata(dtype='f4') == 99).all() - fdata_64 = img.get_fdata() - assert fdata_64.dtype == np.dtype(np.float64) - assert (fdata_64 == in_data).all() - fdata_64[:] = 101 - assert (img.get_fdata(dtype='f8') == 101).all() - assert (img.get_fdata() == 101).all() - # Reloading with new data type blew away the float32 cache - assert (img.get_fdata(dtype='f4') == in_data).all() - img.uncache() - # Now recaching, is float64 - out_data = img.get_fdata() - assert out_data.dtype == np.dtype(np.float64) - # Input dtype needs to be floating point - with pytest.raises(ValueError): - img.get_fdata(dtype=np.int16) - with pytest.raises(ValueError): - img.get_fdata(dtype=np.int32) - # The cache is filled - out_data[:] = 42 - assert img.get_fdata() is out_data - img.uncache() - assert img.get_fdata() is not out_data - # The 42 has gone now. - assert (img.get_fdata() == in_data_template).all() - # If we can save, we can create a proxy image - if not self.can_save: - return - rt_img = bytesio_round_trip(img) - assert in_data is not rt_img.dataobj - assert (rt_img.dataobj == in_data).all() - out_data = rt_img.get_fdata() - assert (out_data == in_data).all() - assert rt_img.dataobj is not out_data - assert out_data.dtype == np.dtype(np.float64) - # cache - assert rt_img.get_fdata() is out_data - out_data[:] = 42 - rt_img.uncache() - assert rt_img.get_fdata() is not out_data - assert (rt_img.get_fdata() == in_data).all() - - @expires('5.0.0') - def test_get_data(self): - # Test array image and proxy image interface - img_klass = self.image_class - in_data_template = np.arange(24, dtype=np.int16).reshape((2, 3, 4)) - in_data = in_data_template.copy() - img = img_klass(in_data, None) - assert in_data is img.dataobj - with deprecated_to('5.0.0'): - out_data = img.get_data() - assert in_data is out_data - # and that uncache has no effect - img.uncache() - assert in_data is out_data - assert (out_data == in_data_template).all() - # If we can save, we can create a proxy image - if not self.can_save: - return - rt_img = bytesio_round_trip(img) - assert in_data is not rt_img.dataobj - assert (rt_img.dataobj == in_data).all() - with deprecated_to('5.0.0'): - out_data = rt_img.get_data() - assert (out_data == in_data).all() - assert rt_img.dataobj is not out_data - # cache - with deprecated_to('5.0.0'): - assert rt_img.get_data() is out_data - out_data[:] = 42 - rt_img.uncache() - with deprecated_to('5.0.0'): - assert rt_img.get_data() is not out_data - with deprecated_to('5.0.0'): - assert (rt_img.get_data() == in_data).all() - - def test_slicer(self): - img_klass = self.image_class - in_data_template = np.arange(240, dtype=np.int16) - base_affine = np.eye(4) - for dshape in ( - (4, 5, 6, 2), # Time series - (8, 5, 6), # Volume - ): - in_data = in_data_template.copy().reshape(dshape) - img = img_klass(in_data, base_affine.copy()) - - # Can't slice into the image object: - with pytest.raises(TypeError) as exception_manager: - img[0, 0, 0] - # Make sure the right message gets raised: - assert ( - str(exception_manager.value) == 'Cannot slice image objects; consider using ' - '`img.slicer[slice]` to generate a sliced image (see ' - 'documentation for caveats) or slicing image array data ' - 'with `img.dataobj[slice]` or `img.get_fdata()[slice]`' - ) - - if not spatial_axes_first(img): - with pytest.raises(ValueError): - img.slicer - continue - - assert hasattr(img.slicer, '__getitem__') - - # Note spatial zooms are always first 3, even when - spatial_zooms = img.header.get_zooms()[:3] - - # Down-sample with [::2, ::2, ::2] along spatial dimensions - sliceobj = [slice(None, None, 2)] * 3 + [slice(None)] * (len(dshape) - 3) - downsampled_img = img.slicer[tuple(sliceobj)] - assert (downsampled_img.header.get_zooms()[:3] == np.array(spatial_zooms) * 2).all() - - max4d = ( - hasattr(img.header, '_structarr') - and 'dims' in img.header._structarr.dtype.fields - and img.header._structarr['dims'].shape == (4,) - ) - # Check newaxis and single-slice errors - with pytest.raises(IndexError): - img.slicer[None] - with pytest.raises(IndexError): - img.slicer[0] - # Axes 1 and 2 are always spatial - with pytest.raises(IndexError): - img.slicer[:, None] - with pytest.raises(IndexError): - img.slicer[:, 0] - with pytest.raises(IndexError): - img.slicer[:, :, None] - with pytest.raises(IndexError): - img.slicer[:, :, 0] - if len(img.shape) == 4: - if max4d: - with pytest.raises(ValueError): - img.slicer[:, :, :, None] - else: - # Reorder non-spatial axes - assert img.slicer[:, :, :, None].shape == img.shape[:3] + (1,) + img.shape[3:] - # 4D to 3D using ellipsis or slices - assert img.slicer[..., 0].shape == img.shape[:-1] - assert img.slicer[:, :, :, 0].shape == img.shape[:-1] - else: - # 3D Analyze/NIfTI/MGH to 4D - assert img.slicer[:, :, :, None].shape == img.shape + (1,) - if len(img.shape) == 3: - # Slices exceed dimensions - with pytest.raises(IndexError): - img.slicer[:, :, :, :, None] - elif max4d: - with pytest.raises(ValueError): - img.slicer[:, :, :, :, None] - else: - assert img.slicer[:, :, :, :, None].shape == img.shape + (1,) - - # Crop by one voxel in each dimension - sliced_i = img.slicer[1:] - sliced_j = img.slicer[:, 1:] - sliced_k = img.slicer[:, :, 1:] - sliced_ijk = img.slicer[1:, 1:, 1:] - - # No scaling change - assert (sliced_i.affine[:3, :3] == img.affine[:3, :3]).all() - assert (sliced_j.affine[:3, :3] == img.affine[:3, :3]).all() - assert (sliced_k.affine[:3, :3] == img.affine[:3, :3]).all() - assert (sliced_ijk.affine[:3, :3] == img.affine[:3, :3]).all() - # Translation - assert (sliced_i.affine[:, 3] == [1, 0, 0, 1]).all() - assert (sliced_j.affine[:, 3] == [0, 1, 0, 1]).all() - assert (sliced_k.affine[:, 3] == [0, 0, 1, 1]).all() - assert (sliced_ijk.affine[:, 3] == [1, 1, 1, 1]).all() - - # No change to affines with upper-bound slices - assert (img.slicer[:1, :1, :1].affine == img.affine).all() - - # Yell about step = 0 - with pytest.raises(ValueError): - img.slicer[:, ::0] - with pytest.raises(ValueError): - img.slicer.slice_affine((slice(None), slice(None, None, 0))) - - # Don't permit zero-length slices - with pytest.raises(IndexError): - img.slicer[:0] - - # No fancy indexing - with pytest.raises(IndexError): - img.slicer[[0]] - with pytest.raises(IndexError): - img.slicer[[-1]] - with pytest.raises(IndexError): - img.slicer[[0], [-1]] - - # Check data is consistent with slicing numpy arrays - slice_elems = np.array( - ( - None, - Ellipsis, - 0, - 1, - -1, - [0], - [1], - [-1], - slice(None), - slice(1), - slice(-1), - slice(1, -1), - ), - dtype=object, - ) - for n_elems in range(6): - for _ in range(1 if n_elems == 0 else 10): - sliceobj = tuple(np.random.choice(slice_elems, n_elems)) - try: - sliced_img = img.slicer[sliceobj] - except (IndexError, ValueError, HeaderDataError): - # Skip invalid slices or images that can't be created - continue - - sliced_data = in_data[sliceobj] - assert np.array_equal(sliced_data, sliced_img.get_fdata()) - assert np.array_equal(sliced_data, sliced_img.dataobj) - assert np.array_equal(sliced_data, img.dataobj[sliceobj]) - assert np.array_equal(sliced_data, img.get_fdata()[sliceobj]) - - -class MmapImageMixin: - """Mixin for testing images that may return memory maps""" - - #: whether to test mode of returned memory map - check_mmap_mode = True - - def get_disk_image(self): - """Return image, image filename, and flag for required scaling - - Subclasses can do anything to return an image, including loading a - pre-existing image from disk. - - Returns - ------- - img : class:`SpatialImage` instance - fname : str - Image filename. - has_scaling : bool - True if the image array has scaling to apply to the raw image array - data, False otherwise. - """ - img_klass = self.image_class - shape = (3, 4, 2) - data = np.arange(np.prod(shape), dtype=np.int16).reshape(shape) - img = img_klass(data, None) - fname = 'test' + img_klass.files_types[0][1] - img.to_filename(fname) - return img, fname, False - - def test_load_mmap(self): - # Test memory mapping when loading images - img_klass = self.image_class - viral_memmap = memmap_after_ufunc() - with InTemporaryDirectory(): - img, fname, has_scaling = self.get_disk_image() - file_map = img.file_map.copy() - for func, param1 in ( - (img_klass.from_filename, fname), - (img_klass.load, fname), - (top_load, fname), - (img_klass.from_file_map, file_map), - ): - for mmap, expected_mode in ( - # mmap value, expected memmap mode - # mmap=None -> no mmap value - # expected mode=None -> no memmap returned - (None, 'c'), - (True, 'c'), - ('c', 'c'), - ('r', 'r'), - (False, None), - ): - # If the image has scaling, then numpy 1.12 will not return - # a memmap, regardless of the input flags. Previous - # numpies returned a memmap object, even though the array - # has no mmap memory backing. See: - # https://github.com/numpy/numpy/pull/7406 - if has_scaling and not viral_memmap: - expected_mode = None - kwargs = {} - if mmap is not None: - kwargs['mmap'] = mmap - back_img = func(param1, **kwargs) - back_data = np.asanyarray(back_img.dataobj) - if expected_mode is None: - assert not isinstance(back_data, np.memmap), ( - f'Should not be a {img_klass.__name__}' - ) - else: - assert isinstance(back_data, np.memmap), f'Not a {img_klass.__name__}' - if self.check_mmap_mode: - assert back_data.mode == expected_mode - del back_img, back_data - # Check that mmap is keyword-only - with pytest.raises(TypeError): - func(param1, True) - # Check invalid values raise error - with pytest.raises(ValueError): - func(param1, mmap='rw') - with pytest.raises(ValueError): - func(param1, mmap='r+') diff --git a/nibabel/tests/test_spm2analyze.py b/nibabel/tests/test_spm2analyze.py deleted file mode 100644 index 7e3d048de5..0000000000 --- a/nibabel/tests/test_spm2analyze.py +++ /dev/null @@ -1,66 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Tests for SPM2 header stuff""" - -import numpy as np -import pytest -from numpy.testing import assert_array_equal - -from ..spatialimages import HeaderDataError, HeaderTypeError -from ..spm2analyze import Spm2AnalyzeHeader, Spm2AnalyzeImage -from . import test_spm99analyze - - -class TestSpm2AnalyzeHeader(test_spm99analyze.TestSpm99AnalyzeHeader): - header_class = Spm2AnalyzeHeader - - def test_slope_inter(self): - hdr = self.header_class() - assert hdr.get_slope_inter() == (1.0, 0.0) - for in_tup, exp_err, out_tup, raw_slope in ( - ((2.0,), None, (2.0, 0.0), 2.0), - ((None,), None, (None, None), np.nan), - ((1.0, None), None, (1.0, 0.0), 1.0), - # non zero intercept causes error - ((None, 1.1), HeaderTypeError, (None, None), np.nan), - ((2.0, 1.1), HeaderTypeError, (None, None), 2.0), - # null scalings - ((0.0, None), HeaderDataError, (None, None), 0.0), - ((np.nan, np.nan), None, (None, None), np.nan), - ((np.nan, None), None, (None, None), np.nan), - ((None, np.nan), None, (None, None), np.nan), - ((np.inf, None), HeaderDataError, (None, None), np.inf), - ((-np.inf, None), HeaderDataError, (None, None), -np.inf), - ((None, 0.0), None, (None, None), np.nan), - ): - hdr = self.header_class() - if not exp_err is None: - with pytest.raises(exp_err): - hdr.set_slope_inter(*in_tup) - # raw set - if not in_tup[0] is None: - hdr['scl_slope'] = in_tup[0] - else: - hdr.set_slope_inter(*in_tup) - assert hdr.get_slope_inter() == out_tup - # Check set survives through checking - hdr = Spm2AnalyzeHeader.from_header(hdr, check=True) - assert hdr.get_slope_inter() == out_tup - assert_array_equal(hdr['scl_slope'], raw_slope) - - -class TestSpm2AnalyzeImage(test_spm99analyze.TestSpm99AnalyzeImage): - # class for testing images - image_class = Spm2AnalyzeImage - - -def test_origin_affine(): - # check that origin affine works, only - hdr = Spm2AnalyzeHeader() - hdr.get_origin_affine() diff --git a/nibabel/tests/test_spm99analyze.py b/nibabel/tests/test_spm99analyze.py deleted file mode 100644 index 26098d8ede..0000000000 --- a/nibabel/tests/test_spm99analyze.py +++ /dev/null @@ -1,531 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## - -import itertools -import unittest -from io import BytesIO - -import numpy as np -import pytest -from numpy.testing import assert_array_almost_equal, assert_array_equal - -from ..optpkg import optional_package - -_, have_scipy, _ = optional_package('scipy') - -# Decorator to skip tests requiring save / load if scipy not available for mat -# files -needs_scipy = unittest.skipUnless(have_scipy, 'scipy not available') - -from ..casting import sctypes_aliases, shared_range, type_info -from ..spatialimages import HeaderDataError -from ..spm99analyze import HeaderTypeError, Spm99AnalyzeHeader, Spm99AnalyzeImage -from ..testing import ( - assert_allclose_safely, - bytesio_filemap, - bytesio_round_trip, - suppress_warnings, -) -from ..volumeutils import _dt_min_max, apply_read_scaling -from . import test_analyze - -# np.core.sctypes values are lists of types with unique sizes -# For testing, we want all concrete classes of a type -# Key on kind, rather than abstract base classes, since timedelta64 is a signedinteger -sctypes = {} -for sctype in sctypes_aliases: - sctypes.setdefault(np.dtype(sctype).kind, []).append(sctype) - -# Sort types to ensure that xdist doesn't complain about test order when we parametrize -FLOAT_TYPES = sorted(sctypes['f'], key=lambda x: x.__name__) -COMPLEX_TYPES = sorted(sctypes['c'], key=lambda x: x.__name__) -INT_TYPES = sorted(sctypes['i'], key=lambda x: x.__name__) -UINT_TYPES = sorted(sctypes['u'], key=lambda x: x.__name__) - -# Create combined type lists -CFLOAT_TYPES = FLOAT_TYPES + COMPLEX_TYPES -IUINT_TYPES = INT_TYPES + UINT_TYPES -NUMERIC_TYPES = CFLOAT_TYPES + IUINT_TYPES - - -class HeaderScalingMixin: - """Mixin to add scaling tests to header tests - - Needs to be a mixin so nifti tests can use this method without inheriting - directly from the SPM header tests - """ - - def test_data_scaling(self): - hdr = self.header_class() - hdr.set_data_shape((1, 2, 3)) - hdr.set_data_dtype(np.int16) - S3 = BytesIO() - data = np.arange(6, dtype=np.float64).reshape((1, 2, 3)) - # This uses scaling - hdr.data_to_fileobj(data, S3) - data_back = hdr.data_from_fileobj(S3) - # almost equal - assert_array_almost_equal(data, data_back, 4) - # But not quite - assert not np.all(data == data_back) - # This is exactly the same call, just testing it works twice - data_back2 = hdr.data_from_fileobj(S3) - assert_array_equal(data_back, data_back2, 4) - # Rescaling is the default - hdr.data_to_fileobj(data, S3, rescale=True) - data_back = hdr.data_from_fileobj(S3) - assert_array_almost_equal(data, data_back, 4) - assert not np.all(data == data_back) - # This doesn't use scaling, and so gets perfect precision - with np.errstate(invalid='ignore'): - hdr.data_to_fileobj(data, S3, rescale=False) - data_back = hdr.data_from_fileobj(S3) - assert np.all(data == data_back) - - -class TestSpm99AnalyzeHeader(test_analyze.TestAnalyzeHeader, HeaderScalingMixin): - header_class = Spm99AnalyzeHeader - - def test_empty(self): - super().test_empty() - hdr = self.header_class() - assert hdr['scl_slope'] == 1 - - def test_big_scaling(self): - # Test that upcasting works for huge scalefactors - # See tests for apply_read_scaling in test_volumeutils - hdr = self.header_class() - hdr.set_data_shape((1, 1, 1)) - hdr.set_data_dtype(np.int16) - sio = BytesIO() - dtt = np.float32 - # This will generate a huge scalefactor - data = np.array([type_info(dtt)['max']], dtype=dtt)[:, None, None] - hdr.data_to_fileobj(data, sio) - data_back = hdr.data_from_fileobj(sio) - assert np.allclose(data, data_back) - - def test_slope_inter(self): - hdr = self.header_class() - assert hdr.get_slope_inter() == (1.0, None) - for in_tup, exp_err, out_tup, raw_slope in ( - ((2.0,), None, (2.0, None), 2.0), - ((None,), None, (None, None), np.nan), - ((1.0, None), None, (1.0, None), 1.0), - # non zero intercept causes error - ((None, 1.1), HeaderTypeError, (None, None), np.nan), - ((2.0, 1.1), HeaderTypeError, (None, None), 2.0), - # null scalings - ((0.0, None), HeaderDataError, (None, None), 0.0), - ((np.nan, np.nan), None, (None, None), np.nan), - ((np.nan, None), None, (None, None), np.nan), - ((None, np.nan), None, (None, None), np.nan), - ((np.inf, None), HeaderDataError, (None, None), np.inf), - ((-np.inf, None), HeaderDataError, (None, None), -np.inf), - ((None, 0.0), None, (None, None), np.nan), - ): - hdr = self.header_class() - if not exp_err is None: - with pytest.raises(exp_err): - hdr.set_slope_inter(*in_tup) - # raw set - if not in_tup[0] is None: - hdr['scl_slope'] = in_tup[0] - else: - hdr.set_slope_inter(*in_tup) - assert hdr.get_slope_inter() == out_tup - # Check set survives through checking - hdr = Spm99AnalyzeHeader.from_header(hdr, check=True) - assert hdr.get_slope_inter() == out_tup - assert_array_equal(hdr['scl_slope'], raw_slope) - - def test_origin_checks(self): - HC = self.header_class - # origin - hdr = HC() - hdr.data_shape = [1, 1, 1] - hdr['origin'][0] = 101 # severity 20 - fhdr, message, raiser = self.log_chk(hdr, 20) - assert fhdr == hdr - assert ( - message == 'very large origin values ' - 'relative to dims; leaving as set, ' - 'ignoring for affine' - ) - pytest.raises(*raiser) - # diagnose binary block - dxer = self.header_class.diagnose_binaryblock - assert dxer(hdr.binaryblock) == 'very large origin values relative to dims' - - -class ImageScalingMixin: - # Mixin to add scaling checks to image test class - # Nifti tests inherits from Analyze tests not Spm Analyze tests. We need - # these tests for Nifti scaling, hence the mixin. - - def assert_scaling_equal(self, hdr, slope, inter): - h_slope, h_inter = self._get_raw_scaling(hdr) - assert_array_equal(h_slope, slope) - assert_array_equal(h_inter, inter) - - def assert_scale_me_scaling(self, hdr): - # Assert that header `hdr` has "scale-me" scaling - slope, inter = self._get_raw_scaling(hdr) - if not slope is None: - assert np.isnan(slope) - if not inter is None: - assert np.isnan(inter) - - def _get_raw_scaling(self, hdr): - return hdr['scl_slope'], None - - def _set_raw_scaling(self, hdr, slope, inter): - # Brutal set of slope and inter - hdr['scl_slope'] = slope - if not inter is None: - raise ValueError('inter should be None') - - def assert_null_scaling(self, arr, slope, inter): - # Assert scaling makes no difference to img, load, save - img_class = self.image_class - input_hdr = img_class.header_class() - # Scaling makes no difference to array returned from get_data - self._set_raw_scaling(input_hdr, slope, inter) - img = img_class(arr, np.eye(4), input_hdr) - img_hdr = img.header - self._set_raw_scaling(input_hdr, slope, inter) - assert_array_equal(img.get_fdata(), arr) - # Scaling has no effect on image as written via header (with rescaling - # turned off). - fm = bytesio_filemap(img) - img_fobj = fm['image'].fileobj - hdr_fobj = img_fobj if not 'header' in fm else fm['header'].fileobj - img_hdr.write_to(hdr_fobj) - img_hdr.data_to_fileobj(arr, img_fobj, rescale=False) - raw_rt_img = img_class.from_file_map(fm) - assert_array_equal(raw_rt_img.get_fdata(), arr) - # Scaling makes no difference for image round trip - fm = bytesio_filemap(img) - img.to_file_map(fm) - rt_img = img_class.from_file_map(fm) - assert_array_equal(rt_img.get_fdata(), arr) - - def test_header_scaling(self): - # For images that implement scaling, test effect of scaling - # - # This tests the affect of creating an image with a header containing - # the scaling, then writing the image and reading again. So the - # scaling can be affected by the processing of the header when creating - # the image, or by interpretation of the scaling when creating the - # array. - # - # Analyze does not implement any scaling, but this test class is the - # base class for all Analyze-derived classes, such as NIfTI - img_class = self.image_class - hdr_class = img_class.header_class - if not hdr_class.has_data_slope: - return - arr = np.arange(24, dtype=np.int16).reshape((2, 3, 4)) - invalid_slopes = (0, np.nan, np.inf, -np.inf) - for slope in (1,) + invalid_slopes: - self.assert_null_scaling(arr, slope, None) - if not hdr_class.has_data_intercept: - return - invalid_inters = (np.nan, np.inf, -np.inf) - invalid_pairs = tuple(itertools.product(invalid_slopes, invalid_inters)) - bad_slopes_good_inter = tuple(itertools.product(invalid_slopes, (0, 1))) - good_slope_bad_inters = tuple(itertools.product((1, 2), invalid_inters)) - for slope, inter in invalid_pairs + bad_slopes_good_inter + good_slope_bad_inters: - self.assert_null_scaling(arr, slope, inter) - - def _check_write_scaling(self, slope, inter, effective_slope, effective_inter): - # Test that explicit set of slope / inter forces write of data using - # this slope, inter. We use this helper function for children of the - # Analyze header - img_class = self.image_class - arr = np.arange(24, dtype=np.float32).reshape((2, 3, 4)) - # We're going to test rounding later - arr[0, 0, 0] = 0.4 - arr[1, 0, 0] = 0.6 - aff = np.eye(4) - # Implicit header gives scale-me scaling - img = img_class(arr, aff) - self.assert_scale_me_scaling(img.header) - # Input header scaling reset when creating image - hdr = img.header - self._set_raw_scaling(hdr, slope, inter) - img = img_class(arr, aff) - self.assert_scale_me_scaling(img.header) - # Array from image unchanged by scaling - assert_array_equal(img.get_fdata(), arr) - # As does round trip - img_rt = bytesio_round_trip(img) - self.assert_scale_me_scaling(img_rt.header) - # Round trip array is not scaled - assert_array_equal(img_rt.get_fdata(), arr) - # Explicit scaling causes scaling after round trip - self._set_raw_scaling(img.header, slope, inter) - self.assert_scaling_equal(img.header, slope, inter) - # Array from image unchanged by scaling - assert_array_equal(img.get_fdata(), arr) - # But the array scaled after round trip - img_rt = bytesio_round_trip(img) - assert_array_equal( - img_rt.get_fdata(), apply_read_scaling(arr, effective_slope, effective_inter) - ) - # The scaling set into the array proxy - do_slope, do_inter = img.header.get_slope_inter() - assert_array_equal(img_rt.dataobj.slope, 1 if do_slope is None else do_slope) - assert_array_equal(img_rt.dataobj.inter, 0 if do_inter is None else do_inter) - # The new header scaling has been reset - self.assert_scale_me_scaling(img_rt.header) - # But the original is the same as it was when we set it - self.assert_scaling_equal(img.header, slope, inter) - # The data gets rounded nicely if we need to do conversion - img.header.set_data_dtype(np.uint8) - with np.errstate(invalid='ignore'): - img_rt = bytesio_round_trip(img) - assert_array_equal( - img_rt.get_fdata(), apply_read_scaling(np.round(arr), effective_slope, effective_inter) - ) - # But we have to clip too - arr[-1, -1, -1] = 256 - arr[-2, -1, -1] = -1 - with np.errstate(invalid='ignore'): - img_rt = bytesio_round_trip(img) - exp_unscaled_arr = np.clip(np.round(arr), 0, 255) - assert_array_equal( - img_rt.get_fdata(), - apply_read_scaling(exp_unscaled_arr, effective_slope, effective_inter), - ) - - def test_int_int_scaling(self): - # Check int to int conversion without slope, inter - img_class = self.image_class - arr = np.array([-1, 0, 256], dtype=np.int16)[:, None, None] - img = img_class(arr, np.eye(4)) - hdr = img.header - img.set_data_dtype(np.uint8) - self._set_raw_scaling(hdr, 1, 0 if hdr.has_data_intercept else None) - img_rt = bytesio_round_trip(img) - assert_array_equal(img_rt.get_fdata(), np.clip(arr, 0, 255)) - - # NOTE: Need to check complex scaling - @pytest.mark.parametrize('in_dtype', FLOAT_TYPES + IUINT_TYPES) - def test_no_scaling(self, in_dtype, supported_dtype): - # Test writing image converting types when not calculating scaling - img_class = self.image_class - hdr_class = img_class.header_class - hdr = hdr_class() - # Any old non-default slope and intercept - slope = 2 - inter = 10 if hdr.has_data_intercept else 0 - - mn_in, mx_in = _dt_min_max(in_dtype) - mn = -1 if np.dtype(in_dtype).kind != 'u' else 0 - arr = np.array([mn_in, mn, 0, 1, 10, mx_in], dtype=in_dtype) - img = img_class(arr, np.eye(4), hdr) - img.set_data_dtype(supported_dtype) - # Setting the scaling means we don't calculate it later - img.header.set_slope_inter(slope, inter) - with np.errstate(invalid='ignore'): - rt_img = bytesio_round_trip(img) - with suppress_warnings(): # invalid mult - back_arr = np.asanyarray(rt_img.dataobj) - exp_back = arr.copy() - # If converting to floating point type, casting is direct. - # Otherwise we will need to do float-(u)int casting at some point - if supported_dtype in IUINT_TYPES: - if in_dtype in FLOAT_TYPES: - # Working precision is (at least) float - exp_back = exp_back.astype(float) - # Float to iu conversion will always round, clip - with np.errstate(invalid='ignore'): - exp_back = np.round(exp_back) - if in_dtype in FLOAT_TYPES: - # Clip to shared range of working precision - exp_back = np.clip(exp_back, *shared_range(float, supported_dtype)) - else: # iu input and output type - # No scaling, never gets converted to float. - # Does get clipped to range of output type - mn_out, mx_out = _dt_min_max(supported_dtype) - if (mn_in, mx_in) != (mn_out, mx_out): - # Use smaller of input, output range to avoid np.clip - # upcasting the array because of large clip limits. - exp_back = np.clip(exp_back, max(mn_in, mn_out), min(mx_in, mx_out)) - if supported_dtype in COMPLEX_TYPES: - # always cast to real from complex - exp_back = exp_back.astype(supported_dtype) - else: - # Cast to working precision - exp_back = exp_back.astype(float) - # Allow for small differences in large numbers - with suppress_warnings(): # invalid value - assert_allclose_safely(back_arr, exp_back * slope + inter) - - def test_write_scaling(self): - # Check writes with scaling set - for slope, inter, e_slope, e_inter in ( - (1, None, 1, None), - (0, None, 1, None), - (np.inf, None, 1, None), - (2, None, 2, None), - ): - self._check_write_scaling(slope, inter, e_slope, e_inter) - - def test_nan2zero_range_ok(self): - # Check that a floating point image with range not including zero gets - # nans scaled correctly - img_class = self.image_class - arr = np.arange(24, dtype=np.float32).reshape((2, 3, 4)) - arr[0, 0, 0] = np.nan - arr[1, 0, 0] = 256 # to push outside uint8 range - img = img_class(arr, np.eye(4)) - rt_img = bytesio_round_trip(img) - assert_array_equal(rt_img.get_fdata(), arr) - # Uncontroversial so far, but now check that nan2zero works correctly - # for int type - img.set_data_dtype(np.uint8) - with np.errstate(invalid='ignore'): - rt_img = bytesio_round_trip(img) - assert rt_img.get_fdata()[0, 0, 0] == 0 - - -class TestSpm99AnalyzeImage(test_analyze.TestAnalyzeImage, ImageScalingMixin): - # class for testing images - image_class = Spm99AnalyzeImage - - # Decorating the old way, before the team invented @ - test_data_hdr_cache = needs_scipy(test_analyze.TestAnalyzeImage.test_data_hdr_cache) - test_header_updating = needs_scipy(test_analyze.TestAnalyzeImage.test_header_updating) - test_offset_to_zero = needs_scipy(test_analyze.TestAnalyzeImage.test_offset_to_zero) - test_big_offset_exts = needs_scipy(test_analyze.TestAnalyzeImage.test_big_offset_exts) - test_dtype_to_filename_arg = needs_scipy( - test_analyze.TestAnalyzeImage.test_dtype_to_filename_arg - ) - test_header_scaling = needs_scipy(ImageScalingMixin.test_header_scaling) - test_int_int_scaling = needs_scipy(ImageScalingMixin.test_int_int_scaling) - test_write_scaling = needs_scipy(ImageScalingMixin.test_write_scaling) - test_no_scaling = needs_scipy(ImageScalingMixin.test_no_scaling) - test_nan2zero_range_ok = needs_scipy(ImageScalingMixin.test_nan2zero_range_ok) - - @needs_scipy - def test_mat_read(self): - # Test mat file reading and writing for the SPM analyze types - img_klass = self.image_class - arr = np.arange(24, dtype=np.int32).reshape((2, 3, 4)) - aff = np.diag([2, 3, 4, 1]) # no LR flip in affine - img = img_klass(arr, aff) - fm = img.file_map - for value in fm.values(): - value.fileobj = BytesIO() - # Test round trip - img.to_file_map() - r_img = img_klass.from_file_map(fm) - assert_array_equal(r_img.get_fdata(), arr) - assert_array_equal(r_img.affine, aff) - # mat files are for matlab and have 111 voxel origins. We need to - # adjust for that, when loading and saving. Check for signs of that in - # the saved mat file - mat_fileobj = img.file_map['mat'].fileobj - from scipy.io import loadmat, savemat - - mat_fileobj.seek(0) - mats = loadmat(mat_fileobj) - assert 'M' in mats and 'mat' in mats - from_111 = np.eye(4) - from_111[:3, 3] = -1 - to_111 = np.eye(4) - to_111[:3, 3] = 1 - assert_array_equal(mats['mat'], np.dot(aff, from_111)) - # The M matrix does not include flips, so if we only have the M matrix - # in the mat file, and we have default flipping, the mat resulting - # should have a flip. The 'mat' matrix does include flips and so - # should be unaffected by the flipping. If both are present we prefer - # the the 'mat' matrix. - assert img.header.default_x_flip # check the default - flipper = np.diag([-1, 1, 1, 1]) - assert_array_equal(mats['M'], np.dot(aff, np.dot(flipper, from_111))) - mat_fileobj.seek(0) - savemat(mat_fileobj, dict(M=np.diag([3, 4, 5, 1]), mat=np.diag([6, 7, 8, 1]))) - # Check we are preferring the 'mat' matrix - r_img = img_klass.from_file_map(fm) - assert_array_equal(r_img.get_fdata(), arr) - assert_array_equal(r_img.affine, np.dot(np.diag([6, 7, 8, 1]), to_111)) - # But will use M if present - mat_fileobj.seek(0) - mat_fileobj.truncate(0) - savemat(mat_fileobj, dict(M=np.diag([3, 4, 5, 1]))) - r_img = img_klass.from_file_map(fm) - assert_array_equal(r_img.get_fdata(), arr) - assert_array_equal(r_img.affine, np.dot(np.diag([3, 4, 5, 1]), np.dot(flipper, to_111))) - - def test_none_affine(self): - # Allow for possibility of no affine resulting in nothing written into - # mat file. If the mat file is a filename, we just get no file, but if - # it's a fileobj, we get an empty fileobj - img_klass = self.image_class - # With a None affine - no matfile written - img = img_klass(np.zeros((2, 3, 4)), None) - aff = img.header.get_best_affine() - # Save / reload using bytes IO objects - for value in img.file_map.values(): - value.fileobj = BytesIO() - img.to_file_map() - img_back = img.from_file_map(img.file_map) - assert_array_equal(img_back.affine, aff) - - -def test_origin_affine(): - hdr = Spm99AnalyzeHeader() - aff = hdr.get_origin_affine() - assert_array_equal(aff, hdr.get_base_affine()) - hdr.set_data_shape((3, 5, 7)) - hdr.set_zooms((3, 2, 1)) - assert hdr.default_x_flip - assert_array_almost_equal( - hdr.get_origin_affine(), # from center of image - [ - [-3.0, 0.0, 0.0, 3.0], - [0.0, 2.0, 0.0, -4.0], - [0.0, 0.0, 1.0, -3.0], - [0.0, 0.0, 0.0, 1.0], - ], - ) - hdr['origin'][:3] = [3, 4, 5] - assert_array_almost_equal( - hdr.get_origin_affine(), # using origin - [ - [-3.0, 0.0, 0.0, 6.0], - [0.0, 2.0, 0.0, -6.0], - [0.0, 0.0, 1.0, -4.0], - [0.0, 0.0, 0.0, 1.0], - ], - ) - hdr['origin'] = 0 # unset origin - hdr.set_data_shape((3, 5)) - assert_array_almost_equal( - hdr.get_origin_affine(), - [ - [-3.0, 0.0, 0.0, 3.0], - [0.0, 2.0, 0.0, -4.0], - [0.0, 0.0, 1.0, -0.0], - [0.0, 0.0, 0.0, 1.0], - ], - ) - hdr.set_data_shape((3, 5, 7)) - assert_array_almost_equal( - hdr.get_origin_affine(), # from center of image - [ - [-3.0, 0.0, 0.0, 3.0], - [0.0, 2.0, 0.0, -4.0], - [0.0, 0.0, 1.0, -3.0], - [0.0, 0.0, 0.0, 1.0], - ], - ) diff --git a/nibabel/tests/test_testing.py b/nibabel/tests/test_testing.py deleted file mode 100644 index ec147baa95..0000000000 --- a/nibabel/tests/test_testing.py +++ /dev/null @@ -1,197 +0,0 @@ -"""Tests for warnings context managers""" - -import os -import sys -import warnings - -import numpy as np -import pytest - -from ..casting import sctypes -from ..testing import ( - assert_allclose_safely, - assert_re_in, - clear_and_catch_warnings, - data_path, - error_warnings, - get_fresh_mod, - get_test_data, - suppress_warnings, -) - - -def test_assert_allclose_safely(): - # Test the safe version of allclose - assert_allclose_safely([1, 1], [1, 1]) - assert_allclose_safely(1, 1) - assert_allclose_safely(1, [1, 1]) - assert_allclose_safely([1, 1], 1 + 1e-6) - with pytest.raises(AssertionError): - assert_allclose_safely([1, 1], 1 + 1e-4) - # Broadcastable matrices - a = np.ones((2, 3)) - b = np.ones((3, 2, 3)) - eps = np.finfo(np.float64).eps - a[0, 0] = 1 + eps - assert_allclose_safely(a, b) - a[0, 0] = 1 + 1.1e-5 - with pytest.raises(AssertionError): - assert_allclose_safely(a, b) - # Nans in same place - a[0, 0] = np.nan - b[:, 0, 0] = np.nan - assert_allclose_safely(a, b) - # Never equal with nans present, if not matching nans - with pytest.raises(AssertionError): - assert_allclose_safely(a, b, match_nans=False) - b[0, 0, 0] = 1 - with pytest.raises(AssertionError): - assert_allclose_safely(a, b) - # Test allcloseness of inf, especially np.float128 infs - for dtt in sctypes['float']: - a = np.array([-np.inf, 1, np.inf], dtype=dtt) - b = np.array([-np.inf, 1, np.inf], dtype=dtt) - assert_allclose_safely(a, b) - b[1] = 0 - with pytest.raises(AssertionError): - assert_allclose_safely(a, b) - # Empty compares equal to empty - assert_allclose_safely([], []) - - -def assert_warn_len_equal(mod, n_in_context): - mod_warns = mod.__warningregistry__ - # Python 3 appears to clear any pre-existing warnings of the same type, - # when raising warnings inside a catch_warnings block. So, there is a - # warning generated by the tests within the context manager, but no - # previous warnings. - if 'version' in mod_warns: - assert len(mod_warns) == 2 # including 'version' - else: - assert len(mod_warns) == n_in_context - - -def test_clear_and_catch_warnings(): - # Initial state of module, no warnings - my_mod = get_fresh_mod(__name__) - assert getattr(my_mod, '__warningregistry__', {}) == {} - with clear_and_catch_warnings(modules=[my_mod]): - warnings.simplefilter('ignore') - warnings.warn('Some warning') - assert my_mod.__warningregistry__ == {} - # Without specified modules, don't clear warnings during context - with clear_and_catch_warnings(): - warnings.warn('Some warning') - assert_warn_len_equal(my_mod, 1) - # Confirm that specifying module keeps old warning, does not add new - with clear_and_catch_warnings(modules=[my_mod]): - warnings.warn('Another warning') - assert_warn_len_equal(my_mod, 1) - # Another warning, no module spec does add to warnings dict, except on - # Python 3 (see comments in `assert_warn_len_equal`) - with clear_and_catch_warnings(): - warnings.warn('Another warning') - assert_warn_len_equal(my_mod, 2) - - -class my_cacw(clear_and_catch_warnings): - class_modules = (sys.modules[__name__],) - - -def test_clear_and_catch_warnings_inherit(): - # Test can subclass and add default modules - my_mod = get_fresh_mod(__name__) - with my_cacw(): - warnings.simplefilter('ignore') - warnings.warn('Some warning') - assert my_mod.__warningregistry__ == {} - - -def test_warn_error(): - # Check warning error context manager - n_warns = len(warnings.filters) - with error_warnings(): - with pytest.raises(UserWarning): - warnings.warn('A test') - with error_warnings(): - with pytest.raises(UserWarning): - warnings.warn('A test') - assert n_warns == len(warnings.filters) - # Check other errors are propagated - - def f(): - with error_warnings(): - raise ValueError('An error') - - with pytest.raises(ValueError): - f() - - -def test_warn_ignore(): - # Check warning ignore context manager - n_warns = len(warnings.filters) - with suppress_warnings(): - warnings.warn('Here is a warning, you will not see it') - warnings.warn('Nor this one', DeprecationWarning) - with suppress_warnings(): - warnings.warn('Here is a warning, you will not see it') - warnings.warn('Nor this one', DeprecationWarning) - assert n_warns == len(warnings.filters) - # Check other errors are propagated - - def f(): - with suppress_warnings(): - raise ValueError('An error') - - with pytest.raises(ValueError): - f() - - -@pytest.mark.parametrize( - ('regex', 'entries'), - [ - ['.*', ''], - ['.*', ['any']], - ['ab', 'abc'], - # Sufficient to have one entry matching - ['ab', ['', 'abc', 'laskdjf']], - # Tuples should be ok too - ['ab', ('', 'abc', 'laskdjf')], - # Should do match not search - pytest.param('ab', 'cab', marks=pytest.mark.xfail), - pytest.param('ab$', 'abc', marks=pytest.mark.xfail), - pytest.param('ab$', ['ddd', ''], marks=pytest.mark.xfail), - pytest.param('ab$', ('ddd', ''), marks=pytest.mark.xfail), - # Shouldn't "match" the empty list - pytest.param('', [], marks=pytest.mark.xfail), - ], -) -def test_assert_re_in(regex, entries): - assert_re_in(regex, entries) - - -def test_test_data(): - assert str(get_test_data()) == str(data_path) # Always get the same result - # Works the same as using __file__ and os.path utilities - assert str(get_test_data()) == os.path.abspath( - os.path.join(os.path.dirname(__file__), '..', 'tests', 'data') - ) - # Check action of subdir and that existence checks work - for subdir in ('nicom', 'gifti', 'externals'): - assert get_test_data(subdir) == data_path.parent.parent / subdir / 'tests' / 'data' - assert os.path.exists(get_test_data(subdir)) - assert not os.path.exists(get_test_data(subdir, 'doesnotexist')) - - for subdir in ('freesurfer', 'doesnotexist'): - with pytest.raises(ValueError): - get_test_data(subdir) - - assert not os.path.exists(get_test_data(None, 'doesnotexist')) - - for subdir, fname in [ - ('gifti', 'ascii.gii'), - ('nicom', '0.dcm'), - ('externals', 'example_1.nc'), - (None, 'empty.tck'), - ]: - assert os.path.exists(get_test_data(subdir, fname)) diff --git a/nibabel/tests/test_tmpdirs.py b/nibabel/tests/test_tmpdirs.py deleted file mode 100644 index 3b2e5d5466..0000000000 --- a/nibabel/tests/test_tmpdirs.py +++ /dev/null @@ -1,22 +0,0 @@ -"""Test tmpdirs module""" - -from os import getcwd -from os.path import abspath, dirname, isfile, realpath - -from ..tmpdirs import InGivenDirectory - -MY_PATH = abspath(__file__) -MY_DIR = dirname(MY_PATH) - - -def test_given_directory(): - # Test InGivenDirectory - cwd = getcwd() - with InGivenDirectory() as tmpdir: - assert tmpdir == abspath(cwd) - assert tmpdir == abspath(getcwd()) - with InGivenDirectory(MY_DIR) as tmpdir: - assert tmpdir == MY_DIR - assert realpath(MY_DIR) == realpath(abspath(getcwd())) - # We were deleting the Given directory! Check not so now. - assert isfile(MY_PATH) diff --git a/nibabel/tests/test_tripwire.py b/nibabel/tests/test_tripwire.py deleted file mode 100644 index 4bf91923f2..0000000000 --- a/nibabel/tests/test_tripwire.py +++ /dev/null @@ -1,20 +0,0 @@ -"""Testing tripwire module""" - -import pytest - -from ..tripwire import TripWire, TripWireError, is_tripwire - - -def test_is_tripwire(): - assert not is_tripwire(object()) - assert is_tripwire(TripWire('some message')) - - -def test_tripwire(): - # Test tripwire object - silly_module_name = TripWire('We do not have silly_module_name') - with pytest.raises(TripWireError): - silly_module_name.do_silly_thing - # Check AttributeError can be checked too - with pytest.raises(AttributeError): - silly_module_name.__wrapped__ diff --git a/nibabel/tests/test_viewers.py b/nibabel/tests/test_viewers.py deleted file mode 100644 index fa22d9021a..0000000000 --- a/nibabel/tests/test_viewers.py +++ /dev/null @@ -1,331 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## - -import unittest -from collections import namedtuple as nt - -import numpy as np -import pytest -from numpy.testing import assert_array_equal, assert_equal - -from ..optpkg import optional_package -from ..viewers import OrthoSlicer3D - -# Need at least MPL 1.3 for viewer tests. -# 2020.02.11 - 1.3 wheels are no longer distributed, so the minimum we test with is 1.5 -matplotlib, has_mpl, _ = optional_package('matplotlib', min_version='1.5') - -needs_mpl = unittest.skipUnless(has_mpl, 'These tests need matplotlib') -if has_mpl: - matplotlib.use('Agg') - - -@needs_mpl -def test_viewer(): - # Test viewer - plt = optional_package('matplotlib.pyplot')[0] - a = np.sin(np.linspace(0, np.pi, 20)) - b = np.sin(np.linspace(0, np.pi * 5, 30)) - data = (np.outer(a, b)[..., np.newaxis] * a)[:, :, :, np.newaxis] - data = data * np.array([1.0, 2.0]) # give it a # of volumes > 1 - v = OrthoSlicer3D(data) - assert_array_equal(v.position, (0, 0, 0)) - assert 'OrthoSlicer3D' in repr(v) - - # fake some events, inside and outside axes - v._on_scroll(nt('event', 'button inaxes key')('up', None, None)) - for ax in (v._axes[0], v._axes[3]): - v._on_scroll(nt('event', 'button inaxes key')('up', ax, None)) - v._on_scroll(nt('event', 'button inaxes key')('up', ax, 'shift')) - # "click" outside axes, then once in each axis, then move without click - v._on_mouse(nt('event', 'xdata ydata inaxes button')(0.5, 0.5, None, 1)) - for ax in v._axes: - v._on_mouse(nt('event', 'xdata ydata inaxes button')(0.5, 0.5, ax, 1)) - v._on_mouse(nt('event', 'xdata ydata inaxes button')(0.5, 0.5, None, None)) - v.set_volume_idx(1) - v.cmap = 'hot' - v.clim = (0, 3) - with pytest.raises(ValueError): - OrthoSlicer3D.clim.fset(v, (0.0,)) # bad limits - with pytest.raises( - ( - ValueError, # MPL3.5 and lower - KeyError, # MPL3.6 and higher - ) - ): - OrthoSlicer3D.cmap.fset(v, 'foo') # wrong cmap - - # decrement/increment volume numbers via keypress - v.set_volume_idx(1) # should just pass - v._on_keypress(nt('event', 'key')('-')) # decrement - assert_equal(v._data_idx[3], 0) - v._on_keypress(nt('event', 'key')('+')) # increment - assert_equal(v._data_idx[3], 1) - v._on_keypress(nt('event', 'key')('-')) - v._on_keypress(nt('event', 'key')('=')) # alternative increment key - assert_equal(v._data_idx[3], 1) - - v.close() - v._draw() # should be safe - - # non-multi-volume - v = OrthoSlicer3D(data[:, :, :, 0]) - v._on_scroll(nt('event', 'button inaxes key')('up', v._axes[0], 'shift')) - v._on_keypress(nt('event', 'key')('escape')) - v.close() - - # complex input should raise a TypeError prior to figure creation - with pytest.raises(TypeError): - OrthoSlicer3D(data[:, :, :, 0].astype(np.complex64)) - - # other cases - fig, axes = plt.subplots(1, 4) - plt.close(fig) - v1 = OrthoSlicer3D(data, axes=axes) - aff = np.array([[0, 1, 0, 3], [-1, 0, 0, 2], [0, 0, 2, 1], [0, 0, 0, 1]], float) - v2 = OrthoSlicer3D(data, affine=aff, axes=axes[:3]) - # bad data (not 3+ dim) - with pytest.raises(ValueError): - OrthoSlicer3D(data[:, :, 0, 0]) - # bad affine (not 4x4) - with pytest.raises(ValueError): - OrthoSlicer3D(data, affine=np.eye(3)) - with pytest.raises(TypeError): - v2.link_to(1) - v2.link_to(v1) - v2.link_to(v1) # shouldn't do anything - v1.close() - v2.close() - - -@needs_mpl -def test_viewer_nonRAS(): - data1 = np.random.rand(10, 20, 40) - data1[5, 10, :] = 0 - data1[5, :, 30] = 0 - data1[:, 10, 30] = 0 - # RSA affine - aff1 = np.array([[1, 0, 0, -5], [0, 0, 1, -30], [0, 1, 0, -10], [0, 0, 0, 1]]) - o1 = OrthoSlicer3D(data1, aff1) - sag = o1._ims[0].get_array() - cor = o1._ims[1].get_array() - axi = o1._ims[2].get_array() - - # Sagittal view: [0, I->S, P->A], so data is transposed, matching plot array - assert_array_equal(sag, data1[5, :, :]) - # Coronal view: [L->R, I->S, 0]. Data is not transposed, transpose to match plot array - assert_array_equal(cor, data1[:, :, 30].T) - # Axial view: [L->R, 0, P->A]. Data is not transposed, transpose to match plot array - assert_array_equal(axi, data1[:, 10, :].T) - - o1.set_position(1, 2, 3) # R, A, S coordinates - - sag = o1._ims[0].get_array() - cor = o1._ims[1].get_array() - axi = o1._ims[2].get_array() - - # Shift 1 right, 2 anterior, 3 superior - assert_array_equal(sag, data1[6, :, :]) - assert_array_equal(cor, data1[:, :, 32].T) - assert_array_equal(axi, data1[:, 13, :].T) - - -@needs_mpl -def test_viewer_nonRAS_on_mouse(): - """ - test on_mouse selection on non RAS matrices - - """ - # This affine simulates an acquisition on a quadruped subject that is in a prone position. - # This corresponds to an acquisition with: - # - LR inverted on scanner x (i) - # - IS on scanner y (j) - # - PA on scanner z (k) - # This example enables to test also OrthoSlicer3D properties `_flips` and `_order`. - - (I, J, K) = (10, 20, 40) - data1 = np.random.rand(I, J, K) - (i_target, j_target, k_target) = (2, 14, 12) - i1 = i_target - 2 - i2 = i_target + 2 - j1 = j_target - 3 - j2 = j_target + 3 - k1 = k_target - 4 - k2 = k_target + 4 - data1[i1 : i2 + 1, j1 : j2 + 1, k1 : k2 + 1] = 0 - data1[i_target, j_target, k_target] = 1 - valp1 = 1.5 - valm1 = 0.5 - data1[i_target - 1, j_target, k_target] = valp1 # x flipped - data1[i_target + 1, j_target, k_target] = valm1 # x flipped - data1[i_target, j_target - 1, k_target] = valm1 - data1[i_target, j_target + 1, k_target] = valp1 - data1[i_target, j_target, k_target - 1] = valm1 - data1[i_target, j_target, k_target + 1] = valp1 - - aff1 = np.array([[-1, 0, 0, 5], [0, 0, 1, -10], [0, 1, 0, -30], [0, 0, 0, 1]]) - - o1 = OrthoSlicer3D(data1, aff1) - - class Event: - def __init__(self): - self.name = 'simulated mouse event' - self.button = 1 - - event = Event() - event.xdata = k_target - event.ydata = j_target - event.inaxes = o1._ims[0].axes - o1._on_mouse(event) - - event.inaxes = o1._ims[1].axes - event.xdata = (I - 1) - i_target # x flipped - event.ydata = j_target - o1._on_mouse(event) - - event.inaxes = o1._ims[2].axes - event.xdata = (I - 1) - i_target # x flipped - event.ydata = k_target - o1._on_mouse(event) - - sag = o1._ims[0].get_array() - cor = o1._ims[1].get_array() - axi = o1._ims[2].get_array() - - assert_array_equal(sag, data1[i_target, :, :]) # - assert_array_equal(cor, data1[::-1, :, k_target].T) # x flipped - assert_array_equal(axi, data1[::-1, j_target, :].T) # x flipped - return None - - -@needs_mpl -def test_viewer_nonRAS_on_scroll(): - """ - test scrolling on non RAS matrices - - """ - # This affine simulates an acquisition on a quadruped subject that is in a prone position. - # This corresponds to an acquisition with: - # - LR inverted on scanner x (i) - # - IS on scanner y (j) - # - PA on scanner z (k) - # This example enables to test also OrthoSlicer3D properties `_flips` and `_order`. - - (I, J, K) = (10, 20, 40) - data1 = np.random.rand(I, J, K) - (i_target, j_target, k_target) = (2, 14, 12) - i1 = i_target - 2 - i2 = i_target + 2 - j1 = j_target - 3 - j2 = j_target + 3 - k1 = k_target - 4 - k2 = k_target + 4 - data1[i1 : i2 + 1, j1 : j2 + 1, k1 : k2 + 1] = 0 - data1[i_target, j_target, k_target] = 1 - valp1 = 1.5 - valm1 = 0.5 - data1[i_target - 1, j_target, k_target] = valp1 # x flipped - data1[i_target + 1, j_target, k_target] = valm1 # x flipped - data1[i_target, j_target - 1, k_target] = valm1 - data1[i_target, j_target + 1, k_target] = valp1 - data1[i_target, j_target, k_target - 1] = valm1 - data1[i_target, j_target, k_target + 1] = valp1 - - aff1 = np.array([[-1, 0, 0, 5], [0, 0, 1, -10], [0, 1, 0, -30], [0, 0, 0, 1]]) - - o1 = OrthoSlicer3D(data1, aff1) - - class Event: - def __init__(self): - self.name = 'simulated mouse event' - self.button = None - self.key = None - - [x_t, y_t, z_t] = list(aff1.dot(np.array([i_target, j_target, k_target, 1]))[:3]) - # print(x_t, y_t, z_t) - # scanner positions are x_t=3, y_t=2, z_t=16 - - event = Event() - - # Sagittal plane - one scroll up - # x coordinate is flipped so index decrease by 1 - o1.set_position(x_t, y_t, z_t) - event.inaxes = o1._ims[0].axes - event.button = 'up' - o1._on_scroll(event) - sag = o1._ims[0].get_array() - cor = o1._ims[1].get_array() - axi = o1._ims[2].get_array() - assert_array_equal(sag, data1[i_target - 1, :, :]) - assert_array_equal(cor, data1[::-1, :, k_target].T) # ::-1 because the array is flipped in x - assert_array_equal(axi, data1[::-1, j_target, :].T) # ::-1 because the array is flipped in x - - # Sagittal plane - one scrolled down - o1.set_position(x_t, y_t, z_t) - event.button = 'down' - o1._on_scroll(event) - sag = o1._ims[0].get_array() - cor = o1._ims[1].get_array() - axi = o1._ims[2].get_array() - assert_array_equal(sag, data1[i_target + 1, :, :]) - assert_array_equal(cor, data1[::-1, :, k_target].T) - assert_array_equal(axi, data1[::-1, j_target, :].T) - - # Coronal plane - one scroll up - # y coordinate is increase by 1 - o1.set_position(x_t, y_t, z_t) - event.inaxes = o1._ims[1].axes - event.button = 'up' - o1._on_scroll(event) - sag = o1._ims[0].get_array() - cor = o1._ims[1].get_array() - axi = o1._ims[2].get_array() - assert_array_equal(sag, data1[i_target, :, :]) - assert_array_equal( - cor, data1[::-1, :, k_target + 1].T - ) # ::-1 because the array is flipped in x - assert_array_equal(axi, data1[::-1, j_target, :].T) # ::-1 because the array is flipped in x - - # Coronal plane - one scrolled down - o1.set_position(x_t, y_t, z_t) - event.button = 'down' - o1._on_scroll(event) - sag = o1._ims[0].get_array() - cor = o1._ims[1].get_array() - axi = o1._ims[2].get_array() - assert_array_equal(sag, data1[i_target, :, :]) - assert_array_equal(cor, data1[::-1, :, k_target - 1].T) - assert_array_equal(axi, data1[::-1, j_target, :].T) - - # Axial plane - one scroll up - # y is increase by 1 - o1.set_position(x_t, y_t, z_t) - event.inaxes = o1._ims[2].axes - event.button = 'up' - o1._on_scroll(event) - sag = o1._ims[0].get_array() - cor = o1._ims[1].get_array() - axi = o1._ims[2].get_array() - assert_array_equal(sag, data1[i_target, :, :]) - assert_array_equal(cor, data1[::-1, :, k_target].T) # ::-1 because the array is flipped in x - assert_array_equal( - axi, data1[::-1, j_target + 1, :].T - ) # ::-1 because the array is flipped in x - - # Axial plane - one scrolled down - o1.set_position(x_t, y_t, z_t) - event.button = 'down' - o1._on_scroll(event) - sag = o1._ims[0].get_array() - cor = o1._ims[1].get_array() - axi = o1._ims[2].get_array() - assert_array_equal(sag, data1[i_target, :, :]) - assert_array_equal(cor, data1[::-1, :, k_target].T) - assert_array_equal(axi, data1[::-1, j_target - 1, :].T) - return None diff --git a/nibabel/tests/test_volumeutils.py b/nibabel/tests/test_volumeutils.py deleted file mode 100644 index 1bd44cbd0a..0000000000 --- a/nibabel/tests/test_volumeutils.py +++ /dev/null @@ -1,1321 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Test for volumeutils module""" - -import bz2 -import functools -import gzip -import itertools -import os -import tempfile -import threading -import time -import warnings -from io import BytesIO -from os.path import exists - -import numpy as np -import pytest -from numpy.testing import assert_array_almost_equal, assert_array_equal -from packaging.version import Version - -from nibabel.testing import ( - assert_allclose_safely, - assert_dt_equal, - error_warnings, - suppress_warnings, -) - -from ..casting import OK_FLOATS, floor_log2, sctypes, shared_range, type_info -from ..openers import BZ2File, ImageOpener, Opener -from ..optpkg import optional_package -from ..tmpdirs import InTemporaryDirectory -from ..volumeutils import ( - _dt_min_max, - _ftype4scaled_finite, - _is_compressed_fobj, - _write_data, - apply_read_scaling, - array_from_file, - array_to_file, - best_write_scale_ftype, - better_float_of, - fname_ext_ul_case, - int_scinter_ftype, - make_dt_codes, - native_code, - rec2dict, - seek_tell, - shape_zoom_affine, - working_type, - write_zeros, -) - -pyzstd, HAVE_ZSTD, _ = optional_package('pyzstd') - -# convenience variables for numpy types -FLOAT_TYPES = sctypes['float'] -COMPLEX_TYPES = sctypes['complex'] -CFLOAT_TYPES = FLOAT_TYPES + COMPLEX_TYPES -INT_TYPES = sctypes['int'] -IUINT_TYPES = INT_TYPES + sctypes['uint'] -NUMERIC_TYPES = CFLOAT_TYPES + IUINT_TYPES - -FP_RUNTIME_WARN = Version(np.__version__) >= Version('1.24.0.dev0+239') -NP_2 = Version(np.__version__) >= Version('2.0.0.dev0') - -try: - from numpy.exceptions import ComplexWarning -except ModuleNotFoundError: # NumPy < 1.25 - from numpy import ComplexWarning - - -def test__is_compressed_fobj(): - # _is_compressed helper function - with InTemporaryDirectory(): - file_openers = [('', open, False), ('.gz', gzip.open, True), ('.bz2', BZ2File, True)] - if HAVE_ZSTD: - file_openers += [('.zst', pyzstd.ZstdFile, True)] - for ext, opener, compressed in file_openers: - fname = 'test.bin' + ext - for mode in ('wb', 'rb'): - fobj = opener(fname, mode) - assert _is_compressed_fobj(fobj) == compressed - fobj.close() - - -def test_fobj_string_assumptions(): - # Test assumptions made in array_from_file about whether string returned - # from file read needs a copy. - dtype = np.dtype(np.int32) - - def make_array(n, bytes): - arr = np.ndarray(n, dtype, buffer=bytes) - arr.flags.writeable = True - return arr - - # Check whether file, gzip file, bz2, zst file reread memory from cache - fname = 'test.bin' - with InTemporaryDirectory(): - openers = [open, gzip.open, BZ2File] - if HAVE_ZSTD: - openers += [pyzstd.ZstdFile] - for n, opener in itertools.product((256, 1024, 2560, 25600), openers): - in_arr = np.arange(n, dtype=dtype) - # Write array to file - fobj_w = opener(fname, 'wb') - fobj_w.write(in_arr.tobytes()) - fobj_w.close() - # Read back from file - fobj_r = opener(fname, 'rb') - try: - contents1 = bytearray(4 * n) - fobj_r.readinto(contents1) - # Second element is 1 - assert contents1[0:8] != b'\x00' * 8 - out_arr = make_array(n, contents1) - assert_array_equal(in_arr, out_arr) - # Set second element to 0 - out_arr[1] = 0 - # Show this changed the bytes string - assert contents1[:8] == b'\x00' * 8 - # Reread, to get unmodified contents - fobj_r.seek(0) - contents2 = bytearray(4 * n) - fobj_r.readinto(contents2) - out_arr2 = make_array(n, contents2) - assert_array_equal(in_arr, out_arr2) - assert out_arr[1] == 0 - finally: - fobj_r.close() - os.unlink(fname) - - -def test_array_from_file(): - shape = (2, 3, 4) - dtype = np.dtype(np.float32) - in_arr = np.arange(24, dtype=dtype).reshape(shape) - # Check on string buffers - offset = 0 - assert buf_chk(in_arr, BytesIO(), None, offset) - offset = 10 - assert buf_chk(in_arr, BytesIO(), None, offset) - # check on real file - fname = 'test.bin' - with InTemporaryDirectory(): - # fortran ordered - out_buf = open(fname, 'wb') - in_buf = open(fname, 'rb') - assert buf_chk(in_arr, out_buf, in_buf, offset) - # Drop offset to check that shape's not coming from file length - out_buf.seek(0) - in_buf.seek(0) - offset = 5 - assert buf_chk(in_arr, out_buf, in_buf, offset) - del out_buf, in_buf - # Make sure empty shape, and zero length, give empty arrays - arr = array_from_file((), np.dtype('f8'), BytesIO()) - assert len(arr) == 0 - arr = array_from_file((0,), np.dtype('f8'), BytesIO()) - assert len(arr) == 0 - # Check error from small file - with pytest.raises(OSError): - array_from_file(shape, dtype, BytesIO()) - # check on real file - fd, fname = tempfile.mkstemp() - with InTemporaryDirectory(): - open(fname, 'wb').write(b'1') - in_buf = open(fname, 'rb') - with pytest.raises(OSError): - array_from_file(shape, dtype, in_buf) - del in_buf - - -def test_array_from_file_mmap(): - # Test memory mapping - shape = (2, 21) - with InTemporaryDirectory(): - for dt in (np.int16, np.float64): - arr = np.arange(np.prod(shape), dtype=dt).reshape(shape) - with open('test.bin', 'wb') as fobj: - fobj.write(arr.tobytes(order='F')) - with open('test.bin', 'rb') as fobj: - res = array_from_file(shape, dt, fobj) - assert_array_equal(res, arr) - assert isinstance(res, np.memmap) - assert res.mode == 'c' - with open('test.bin', 'rb') as fobj: - res = array_from_file(shape, dt, fobj, mmap=True) - assert_array_equal(res, arr) - assert isinstance(res, np.memmap) - assert res.mode == 'c' - with open('test.bin', 'rb') as fobj: - res = array_from_file(shape, dt, fobj, mmap='c') - assert_array_equal(res, arr) - assert isinstance(res, np.memmap) - assert res.mode == 'c' - with open('test.bin', 'rb') as fobj: - res = array_from_file(shape, dt, fobj, mmap='r') - assert_array_equal(res, arr) - assert isinstance(res, np.memmap) - assert res.mode == 'r' - with open('test.bin', 'rb+') as fobj: - res = array_from_file(shape, dt, fobj, mmap='r+') - assert_array_equal(res, arr) - assert isinstance(res, np.memmap) - assert res.mode == 'r+' - with open('test.bin', 'rb') as fobj: - res = array_from_file(shape, dt, fobj, mmap=False) - assert_array_equal(res, arr) - assert not isinstance(res, np.memmap) - with open('test.bin', 'rb') as fobj: - with pytest.raises(ValueError): - array_from_file(shape, dt, fobj, mmap='p') - - -def buf_chk(in_arr, out_buf, in_buf, offset): - """Write contents of in_arr into fileobj, read back, check same""" - instr = b' ' * offset + in_arr.tobytes(order='F') - out_buf.write(instr) - out_buf.flush() - if in_buf is None: # we're using in_buf from out_buf - out_buf.seek(0) - in_buf = out_buf - arr = array_from_file(in_arr.shape, in_arr.dtype, in_buf, offset) - return np.allclose(in_arr, arr) - - -def test_array_from_file_openers(): - # Test array_from_file also works with Opener objects - shape = (2, 3, 4) - dtype = np.dtype(np.float32) - in_arr = np.arange(24, dtype=dtype).reshape(shape) - with InTemporaryDirectory(): - extensions = ['', '.gz', '.bz2'] - if HAVE_ZSTD: - extensions += ['.zst'] - for ext, offset in itertools.product(extensions, (0, 5, 10)): - fname = 'test.bin' + ext - with Opener(fname, 'wb') as out_buf: - if offset != 0: # avoid https://bugs.python.org/issue16828 - out_buf.write(b' ' * offset) - out_buf.write(in_arr.tobytes(order='F')) - with Opener(fname, 'rb') as in_buf: - out_arr = array_from_file(shape, dtype, in_buf, offset) - assert_array_almost_equal(in_arr, out_arr) - # Delete object holding onto file for Windows - del out_arr - - -def test_array_from_file_reread(): - # Check that reading, modifying, reading again returns original. - # This is the live check for the generic checks in - # test_fobj_string_assumptions - offset = 9 - fname = 'test.bin' - with InTemporaryDirectory(): - openers = [open, gzip.open, bz2.BZ2File, BytesIO] - if HAVE_ZSTD: - openers += [pyzstd.ZstdFile] - for shape, opener, dtt, order in itertools.product( - ((64,), (64, 65), (64, 65, 66)), openers, (np.int16, np.float32), ('F', 'C') - ): - n_els = np.prod(shape) - in_arr = np.arange(n_els, dtype=dtt).reshape(shape) - is_bio = hasattr(opener, 'getvalue') - # Write array to file - fobj_w = opener() if is_bio else opener(fname, 'wb') - fobj_w.write(b' ' * offset) - fobj_w.write(in_arr.tobytes(order=order)) - if is_bio: - fobj_r = fobj_w - else: - fobj_w.close() - fobj_r = opener(fname, 'rb') - # Read back from file - try: - out_arr = array_from_file(shape, dtt, fobj_r, offset, order) - assert_array_equal(in_arr, out_arr) - out_arr[..., 0] = -1 - assert not np.allclose(in_arr, out_arr) - out_arr2 = array_from_file(shape, dtt, fobj_r, offset, order) - assert_array_equal(in_arr, out_arr2) - finally: - fobj_r.close() - # Delete arrays holding onto file objects so Windows can delete - del out_arr, out_arr2 - if not is_bio: - os.unlink(fname) - - -def test_array_to_file(): - arr = np.arange(10).reshape(5, 2) - str_io = BytesIO() - for tp in (np.uint64, np.float64, np.complex128): - dt = np.dtype(tp) - for code in '<>': - ndt = dt.newbyteorder(code) - for allow_intercept in (True, False): - scale, intercept, mn, mx = _calculate_scale(arr, ndt, allow_intercept) - data_back = write_return(arr, str_io, ndt, 0, intercept, scale) - assert_array_almost_equal(arr, data_back) - # Test array-like - str_io = BytesIO() - array_to_file(arr.tolist(), str_io, float) - data_back = array_from_file(arr.shape, float, str_io) - assert_array_almost_equal(arr, data_back) - - -def test_a2f_intercept_scale(): - arr = np.array([0.0, 1.0, 2.0]) - str_io = BytesIO() - # intercept - data_back = write_return(arr, str_io, np.float64, 0, 1.0) - assert_array_equal(data_back, arr - 1) - # scaling - data_back = write_return(arr, str_io, np.float64, 0, 1.0, 2.0) - assert_array_equal(data_back, (arr - 1) / 2.0) - - -def test_a2f_upscale(): - # Test working type scales with needed range - info = type_info(np.float32) - # Test values discovered from stress testing. The largish value (2**115) - # overflows to inf after the intercept is subtracted, using float32 as the - # working precision. The difference between inf and this value is lost. - arr = np.array([[info['min'], 2**115, info['max']]], dtype=np.float32) - slope = np.float32(2**121) - inter = info['min'] - str_io = BytesIO() - # We need to provide mn, mx for function to be able to calculate upcasting - array_to_file( - arr, str_io, np.uint8, intercept=inter, divslope=slope, mn=info['min'], mx=info['max'] - ) - raw = array_from_file(arr.shape, np.uint8, str_io) - back = apply_read_scaling(raw, slope, inter) - top = back - arr - score = np.abs(top / arr) - assert np.all(score < 10) - - -def test_a2f_min_max(): - # Check min and max thresholding of array to file - str_io = BytesIO() - for in_dt in (np.float32, np.int8): - for out_dt in (np.float32, np.int8): - arr = np.arange(4, dtype=in_dt) - # min thresholding - with np.errstate(invalid='ignore'): - data_back = write_return(arr, str_io, out_dt, 0, 0, 1, 1) - assert_array_equal(data_back, [1, 1, 2, 3]) - # max thresholding - with np.errstate(invalid='ignore'): - data_back = write_return(arr, str_io, out_dt, 0, 0, 1, None, 2) - assert_array_equal(data_back, [0, 1, 2, 2]) - # min max thresholding - data_back = write_return(arr, str_io, out_dt, 0, 0, 1, 1, 2) - assert_array_equal(data_back, [1, 1, 2, 2]) - # Check that works OK with scaling and intercept - arr = np.arange(4, dtype=np.float32) - data_back = write_return(arr, str_io, int, 0, -1, 0.5, 1, 2) - assert_array_equal(data_back * 0.5 - 1, [1, 1, 2, 2]) - # Even when scaling is negative - data_back = write_return(arr, str_io, int, 0, 1, -0.5, 1, 2) - assert_array_equal(data_back * -0.5 + 1, [1, 1, 2, 2]) - # Check complex numbers - arr = np.arange(4, dtype=np.complex64) + 100j - with suppress_warnings(): # cast to real - data_back = write_return(arr, str_io, out_dt, 0, 0, 1, 1, 2) - assert_array_equal(data_back, [1, 1, 2, 2]) - - -def test_a2f_order(): - ndt = np.dtype(np.float64) - arr = np.array([0.0, 1.0, 2.0]) - str_io = BytesIO() - # order makes no difference in 1D case - data_back = write_return(arr, str_io, ndt, order='C') - assert_array_equal(data_back, [0.0, 1.0, 2.0]) - # but does in the 2D case - arr = np.array([[0.0, 1.0], [2.0, 3.0]]) - data_back = write_return(arr, str_io, ndt, order='F') - assert_array_equal(data_back, arr) - data_back = write_return(arr, str_io, ndt, order='C') - assert_array_equal(data_back, arr.T) - - -def test_a2f_nan2zero(): - ndt = np.dtype(np.float64) - str_io = BytesIO() - # nans set to 0 for integer output case, not float - arr = np.array([[np.nan, 0], [0, np.nan]]) - data_back = write_return(arr, str_io, ndt) # float, thus no effect - assert_array_equal(data_back, arr) - # True is the default, but just to show it's possible - data_back = write_return(arr, str_io, ndt, nan2zero=True) - assert_array_equal(data_back, arr) - with np.errstate(invalid='ignore'): - data_back = write_return(arr, str_io, np.int64, nan2zero=True) - assert_array_equal(data_back, [[0, 0], [0, 0]]) - # otherwise things get a bit weird; tidied here - # How weird? Look at arr.astype(np.int64) - with np.errstate(invalid='ignore'): - data_back = write_return(arr, str_io, np.int64, nan2zero=False) - assert_array_equal(data_back, arr.astype(np.int64)) - - -def test_a2f_nan2zero_scaling(): - # Check that nan gets translated to the nearest equivalent to zero - # - # nan can be represented as zero of we can store (0 - intercept) / divslope - # in the output data - because reading back the data as `stored_array * divslope + - # intercept` will reconstruct zeros for the nans in the original input. - # - # Check with array containing nan, matching array containing zero and - # Array containing zero - # Array values otherwise not including zero without scaling - # Same with negative sign - # Array values including zero before scaling but not after - bio = BytesIO() - for in_dt, out_dt, zero_in, inter in itertools.product( - FLOAT_TYPES, IUINT_TYPES, (True, False), (0, -100) - ): - in_info = np.finfo(in_dt) - out_info = np.iinfo(out_dt) - mx = min(in_info.max, out_info.max * 2.0, 2**32) + inter - mn = 0 if zero_in or inter else 100 - vals = [np.nan] + [mn, mx] - nan_arr = np.array(vals, dtype=in_dt) - zero_arr = np.nan_to_num(nan_arr) - with np.errstate(invalid='ignore'): - back_nan = write_return(nan_arr, bio, np.int64, intercept=inter) - back_zero = write_return(zero_arr, bio, np.int64, intercept=inter) - assert_array_equal(back_nan, back_zero) - - -def test_a2f_offset(): - # check that non-zero file offset works - arr = np.array([[0.0, 1.0], [2.0, 3.0]]) - str_io = BytesIO() - str_io.write(b'a' * 42) - array_to_file(arr, str_io, np.float64, 42) - data_back = array_from_file(arr.shape, np.float64, str_io, 42) - assert_array_equal(data_back, arr.astype(np.float64)) - # And that offset=None respected - str_io.truncate(22) - str_io.seek(22) - array_to_file(arr, str_io, np.float64, None) - data_back = array_from_file(arr.shape, np.float64, str_io, 22) - assert_array_equal(data_back, arr.astype(np.float64)) - - -def test_a2f_dtype_default(): - # that default dtype is input dtype - arr = np.array([[0.0, 1.0], [2.0, 3.0]]) - str_io = BytesIO() - array_to_file(arr.astype(np.int16), str_io) - data_back = array_from_file(arr.shape, np.int16, str_io) - assert_array_equal(data_back, arr.astype(np.int16)) - - -def test_a2f_zeros(): - # Check that, if there is no valid data, we get zeros - arr = np.array([[0.0, 1.0], [2.0, 3.0]]) - str_io = BytesIO() - # With slope=None signal - array_to_file(arr + np.inf, str_io, np.int32, 0, 0.0, None) - data_back = array_from_file(arr.shape, np.int32, str_io) - assert_array_equal(data_back, np.zeros(arr.shape)) - # With mn, mx = 0 signal - array_to_file(arr, str_io, np.int32, 0, 0.0, 1.0, 0, 0) - data_back = array_from_file(arr.shape, np.int32, str_io) - assert_array_equal(data_back, np.zeros(arr.shape)) - # With mx < mn signal - array_to_file(arr, str_io, np.int32, 0, 0.0, 1.0, 4, 2) - data_back = array_from_file(arr.shape, np.int32, str_io) - assert_array_equal(data_back, np.zeros(arr.shape)) - - -def test_a2f_big_scalers(): - # Check that clip works even for overflowing scalers / data - info = type_info(np.float32) - arr = np.array([info['min'], 0, info['max']], dtype=np.float32) - str_io = BytesIO() - # Intercept causes overflow - does routine scale correctly? - # We check whether the routine correctly clips extreme values. - # We need nan2zero=False because we can't represent 0 in the input, given - # the scaling and the output range. - with suppress_warnings(): # overflow - array_to_file(arr, str_io, np.int8, intercept=np.float32(2**120), nan2zero=False) - data_back = array_from_file(arr.shape, np.int8, str_io) - assert_array_equal(data_back, [-128, -128, 127]) - # Scales also if mx, mn specified? Same notes and complaints as for the test - # above. - str_io.seek(0) - array_to_file( - arr, - str_io, - np.int8, - mn=info['min'], - mx=info['max'], - intercept=np.float32(2**120), - nan2zero=False, - ) - data_back = array_from_file(arr.shape, np.int8, str_io) - assert_array_equal(data_back, [-128, -128, 127]) - # And if slope causes overflow? - str_io.seek(0) - with suppress_warnings(): # overflow in divide - array_to_file(arr, str_io, np.int8, divslope=np.float32(0.5)) - data_back = array_from_file(arr.shape, np.int8, str_io) - assert_array_equal(data_back, [-128, 0, 127]) - # with mn, mx specified? - str_io.seek(0) - array_to_file(arr, str_io, np.int8, mn=info['min'], mx=info['max'], divslope=np.float32(0.5)) - data_back = array_from_file(arr.shape, np.int8, str_io) - assert_array_equal(data_back, [-128, 0, 127]) - - -def test_a2f_int_scaling(): - # Check that we can use integers for intercept and divslope - arr = np.array([0, 1, 128, 255], dtype=np.uint8) - fobj = BytesIO() - back_arr = write_return(arr, fobj, np.uint8, intercept=1) - assert_array_equal(back_arr, np.clip(arr - 1.0, 0, 255)) - back_arr = write_return(arr, fobj, np.uint8, divslope=2) - assert_array_equal(back_arr, np.round(np.clip(arr / 2.0, 0, 255))) - back_arr = write_return(arr, fobj, np.uint8, intercept=1, divslope=2) - assert_array_equal(back_arr, np.round(np.clip((arr - 1.0) / 2.0, 0, 255))) - back_arr = write_return(arr, fobj, np.int16, intercept=1, divslope=2) - assert_array_equal(back_arr, np.round((arr - 1.0) / 2.0)) - - -def test_a2f_scaled_unscaled(): - # Test behavior of array_to_file when writing different types with and - # without scaling - fobj = BytesIO() - for in_dtype, out_dtype, intercept, divslope in itertools.product( - NUMERIC_TYPES, NUMERIC_TYPES, (0, 0.5, -1, 1), (1, 0.5, 2) - ): - mn_in, mx_in = _dt_min_max(in_dtype) - vals = [mn_in, 0, 1, mx_in] - if np.dtype(in_dtype).kind != 'u': - vals.append(-1) - if in_dtype in CFLOAT_TYPES: - vals.append(np.nan) - arr = np.array(vals, dtype=in_dtype) - mn_out, mx_out = _dt_min_max(out_dtype) - # 0 when scaled to output will also be the output value for NaN - nan_fill = -intercept / divslope - if out_dtype in IUINT_TYPES: - nan_fill = np.round(nan_fill) - # nan2zero will check whether 0 in scaled to a valid value in output - if in_dtype in CFLOAT_TYPES and not mn_out <= nan_fill <= mx_out: - with pytest.raises(ValueError): - array_to_file( - arr, fobj, out_dtype=out_dtype, divslope=divslope, intercept=intercept - ) - continue - with suppress_warnings(): - back_arr = write_return( - arr, fobj, out_dtype=out_dtype, divslope=divslope, intercept=intercept - ) - exp_back = arr.copy() - if ( - in_dtype in IUINT_TYPES - and out_dtype in IUINT_TYPES - and (intercept, divslope) == (0, 1) - ): - # Direct iu to iu casting. - # Need to clip if ranges not the same. - # Use smaller of input, output range to avoid np.clip upcasting - # the array because of large clip limits. - if (mn_in, mx_in) != (mn_out, mx_out): - exp_back = np.clip(exp_back, max(mn_in, mn_out), min(mx_in, mx_out)) - else: # Need to deal with nans, casting to float, clipping - if in_dtype in CFLOAT_TYPES and out_dtype in IUINT_TYPES: - exp_back[np.isnan(exp_back)] = 0 - if in_dtype not in COMPLEX_TYPES: - exp_back = exp_back.astype(float) - if intercept != 0: - exp_back -= intercept - if divslope != 1: - exp_back /= divslope - if exp_back.dtype.type in CFLOAT_TYPES and out_dtype in IUINT_TYPES: - exp_back = np.round(exp_back).astype(float) - exp_back = np.clip(exp_back, *shared_range(float, out_dtype)) - exp_back = exp_back.astype(out_dtype) - # Allow for small differences in large numbers - assert_allclose_safely(back_arr, exp_back) - - -def test_a2f_nanpos(): - # Strange behavior of nan2zero - arr = np.array([np.nan]) - fobj = BytesIO() - back_arr = write_return(arr, fobj, np.int8, divslope=2) - assert_array_equal(back_arr, 0) - back_arr = write_return(arr, fobj, np.int8, intercept=10, divslope=2) - assert_array_equal(back_arr, -5) - - -def test_a2f_bad_scaling(): - # Test that pathological scalers raise an error - NUMERICAL_TYPES = sum((sctypes[key] for key in ['int', 'uint', 'float', 'complex']), []) - for in_type, out_type, slope, inter in itertools.product( - NUMERICAL_TYPES, - NUMERICAL_TYPES, - (None, 1, 0, np.nan, -np.inf, np.inf), - (0, np.nan, -np.inf, np.inf), - ): - arr = np.ones((2,), dtype=in_type) - fobj = BytesIO() - cm = error_warnings() - if np.issubdtype(in_type, np.complexfloating) and not np.issubdtype( - out_type, np.complexfloating - ): - cm = pytest.warns(ComplexWarning) - if (slope, inter) == (1, 0): - with cm: - assert_array_equal( - arr, write_return(arr, fobj, out_type, intercept=inter, divslope=slope) - ) - elif (slope, inter) == (None, 0): - assert_array_equal( - 0, write_return(arr, fobj, out_type, intercept=inter, divslope=slope) - ) - else: - with pytest.raises(ValueError): - array_to_file(arr, fobj, np.int8, intercept=inter, divslope=slope) - - -def test_a2f_nan2zero_range(): - # array_to_file should check if nan can be represented as zero - # This comes about when the writer can't write the value (-intercept / - # divslope) because it does not fit in the output range. Input clipping - # should not affect this - fobj = BytesIO() - # No problem for input integer types - they don't have NaNs - for dt in INT_TYPES: - arr_no_nan = np.array([-1, 0, 1, 2], dtype=dt) - # No errors from explicit thresholding (nor for input float types) - back_arr = write_return(arr_no_nan, fobj, np.int8, mn=1, nan2zero=True) - assert_array_equal([1, 1, 1, 2], back_arr) - back_arr = write_return(arr_no_nan, fobj, np.int8, mx=-1, nan2zero=True) - assert_array_equal([-1, -1, -1, -1], back_arr) - # Pushing zero outside the output data range does not generate error - back_arr = write_return(arr_no_nan, fobj, np.int8, intercept=129, nan2zero=True) - assert_array_equal([-128, -128, -128, -127], back_arr) - back_arr = write_return( - arr_no_nan, fobj, np.int8, intercept=257.1, divslope=2, nan2zero=True - ) - assert_array_equal([-128, -128, -128, -128], back_arr) - for dt in CFLOAT_TYPES: - arr = np.array([-1, 0, 1, np.nan], dtype=dt) - # Error occurs for arrays without nans too - arr_no_nan = np.array([-1, 0, 1, 2], dtype=dt) - complex_warn = (ComplexWarning,) if np.issubdtype(dt, np.complexfloating) else () - # Casting nan to int will produce a RuntimeWarning in numpy 1.24 - nan_warn = (RuntimeWarning,) if FP_RUNTIME_WARN else () - c_and_n_warn = complex_warn + nan_warn - # No errors from explicit thresholding - # mn thresholding excluding zero - with pytest.warns(complex_warn) if complex_warn else error_warnings(): - assert_array_equal([1, 1, 1, 0], write_return(arr, fobj, np.int8, mn=1)) - # mx thresholding excluding zero - with pytest.warns(complex_warn) if complex_warn else error_warnings(): - assert_array_equal([-1, -1, -1, 0], write_return(arr, fobj, np.int8, mx=-1)) - # Errors from datatype threshold after scaling - with pytest.warns(complex_warn) if complex_warn else error_warnings(): - back_arr = write_return(arr, fobj, np.int8, intercept=128) - assert_array_equal([-128, -128, -127, -128], back_arr) - with pytest.raises(ValueError): - write_return(arr, fobj, np.int8, intercept=129) - with pytest.raises(ValueError): - write_return(arr_no_nan, fobj, np.int8, intercept=129) - # OK with nan2zero false, but we get whatever nan casts to - with pytest.warns(c_and_n_warn) if c_and_n_warn else error_warnings(): - nan_cast = np.array(np.nan, dtype=dt).astype(np.int8) - with pytest.warns(c_and_n_warn) if c_and_n_warn else error_warnings(): - back_arr = write_return(arr, fobj, np.int8, intercept=129, nan2zero=False) - assert_array_equal([-128, -128, -128, nan_cast], back_arr) - # divslope - with pytest.warns(complex_warn) if complex_warn else error_warnings(): - back_arr = write_return(arr, fobj, np.int8, intercept=256, divslope=2) - assert_array_equal([-128, -128, -128, -128], back_arr) - with pytest.raises(ValueError): - write_return(arr, fobj, np.int8, intercept=257.1, divslope=2) - with pytest.raises(ValueError): - write_return(arr_no_nan, fobj, np.int8, intercept=257.1, divslope=2) - # OK with nan2zero false - with pytest.warns(c_and_n_warn) if c_and_n_warn else error_warnings(): - back_arr = write_return( - arr, fobj, np.int8, intercept=257.1, divslope=2, nan2zero=False - ) - assert_array_equal([-128, -128, -128, nan_cast], back_arr) - - -def test_a2f_non_numeric(): - # Reminder that we may get structured dtypes - dt = np.dtype([('f1', 'f'), ('f2', 'i2')]) - arr = np.zeros((2,), dtype=dt) - arr['f1'] = 0.4, 0.6 - arr['f2'] = 10, 12 - fobj = BytesIO() - back_arr = write_return(arr, fobj, dt) - assert_array_equal(back_arr, arr) - # Some versions of numpy can cast structured types to float, others not - try: - arr.astype(float) - except (TypeError, ValueError): - pass - else: - back_arr = write_return(arr, fobj, float) - assert_array_equal(back_arr, arr.astype(float)) - # mn, mx never work for structured types - with pytest.raises(ValueError): - write_return(arr, fobj, float, mn=0) - with pytest.raises(ValueError): - write_return(arr, fobj, float, mx=10) - - -def write_return(data, fileobj, out_dtype, *args, **kwargs): - fileobj.truncate(0) - fileobj.seek(0) - array_to_file(data, fileobj, out_dtype, *args, **kwargs) - data = array_from_file(data.shape, out_dtype, fileobj) - return data - - -def test_apply_scaling(): - # Null scaling, same array returned - arr = np.zeros((3,), dtype=np.int16) - assert apply_read_scaling(arr) is arr - assert apply_read_scaling(arr, np.float64(1.0)) is arr - assert apply_read_scaling(arr, inter=np.float64(0)) is arr - f32, f64 = np.float32, np.float64 - f32_arr = np.zeros((1,), dtype=f32) - i16_arr = np.zeros((1,), dtype=np.int16) - # Check float upcast (not the normal numpy scalar rule) - # This is the normal rule - no upcast from Python scalar - assert (f32_arr * 1.0).dtype == np.float32 - assert (f32_arr + 1.0).dtype == np.float32 - # This is the normal rule - no upcast from scalar - # before NumPy 2.0, after 2.0, it upcasts - want_dtype = np.float64 if NP_2 else np.float32 - assert (f32_arr * f64(1)).dtype == want_dtype - assert (f32_arr + f64(1)).dtype == want_dtype - # The function does upcast though - ret = apply_read_scaling(np.float32(0), np.float64(2)) - assert ret.dtype == np.float64 - ret = apply_read_scaling(np.float32(0), inter=np.float64(2)) - assert ret.dtype == np.float64 - # Check integer inf upcast - big = f32(type_info(f32)['max']) - # Normally this would not upcast - assert (i16_arr * big).dtype == np.float32 - # An equivalent case is a little hard to find for the intercept - nmant_32 = type_info(np.float32)['nmant'] - big_delta = np.float32(2 ** (floor_log2(big) - nmant_32)) - assert (i16_arr * big_delta + big).dtype == np.float32 - # Upcasting does occur with this routine - assert apply_read_scaling(i16_arr, big).dtype == np.float64 - assert apply_read_scaling(i16_arr, big_delta, big).dtype == np.float64 - # If float32 passed, no overflow, float32 returned - assert apply_read_scaling(np.int8(0), f32(-1.0), f32(0.0)).dtype == np.float32 - # float64 passed, float64 returned - assert apply_read_scaling(np.int8(0), -1.0, 0.0).dtype == np.float64 - # float32 passed, overflow, float64 returned - assert apply_read_scaling(np.int8(0), f32(1e38), f32(0.0)).dtype == np.float64 - assert apply_read_scaling(np.int8(0), f32(-1e38), f32(0.0)).dtype == np.float64 - # Non-zero intercept still generates floats - assert_dt_equal(apply_read_scaling(i16_arr, 1.0, 1.0).dtype, float) - assert_dt_equal(apply_read_scaling(np.zeros((1,), dtype=np.int32), 1.0, 1.0).dtype, float) - assert_dt_equal(apply_read_scaling(np.zeros((1,), dtype=np.int64), 1.0, 1.0).dtype, float) - - -def test_apply_read_scaling_ints(): - # Test that apply_read_scaling copes with integer scaling inputs - arr = np.arange(10, dtype=np.int16) - assert_array_equal(apply_read_scaling(arr, 1, 0), arr) - assert_array_equal(apply_read_scaling(arr, 1, 1), arr + 1) - assert_array_equal(apply_read_scaling(arr, 2, 1), arr * 2 + 1) - - -def test_apply_read_scaling_nones(): - # Check that we can pass None as slope and inter to apply read scaling - arr = np.arange(10, dtype=np.int16) - assert_array_equal(apply_read_scaling(arr, None, None), arr) - assert_array_equal(apply_read_scaling(arr, 2, None), arr * 2) - assert_array_equal(apply_read_scaling(arr, None, 1), arr + 1) - - -def test_int_scinter(): - # Finding float type needed for applying scale, offset to ints - assert int_scinter_ftype(np.int8, 1.0, 0.0) == np.float32 - assert int_scinter_ftype(np.int8, -1.0, 0.0) == np.float32 - assert int_scinter_ftype(np.int8, 1e38, 0.0) == np.float64 - assert int_scinter_ftype(np.int8, -1e38, 0.0) == np.float64 - - -def test_working_type(): - # Which type do input types with slope and inter cast to in numpy? - # Wrapper function because we need to use the dtype str for comparison. We - # need this because of the very confusing np.int32 != np.intp (on 32 bit). - def wt(*args, **kwargs): - return np.dtype(working_type(*args, **kwargs)).str - - d1 = np.atleast_1d - for in_type in NUMERIC_TYPES: - in_ts = np.dtype(in_type).str - assert wt(in_type) == in_ts - assert wt(in_type, 1, 0) == in_ts - assert wt(in_type, 1.0, 0.0) == in_ts - in_val = d1(in_type(0)) - for slope_type in NUMERIC_TYPES: - sl_val = slope_type(1) # no scaling, regardless of type - assert wt(in_type, sl_val, 0.0) == in_ts - sl_val = slope_type(2) # actual scaling - out_val = in_val / d1(sl_val) - assert wt(in_type, sl_val) == out_val.dtype.str - for inter_type in NUMERIC_TYPES: - i_val = inter_type(0) # no scaling, regardless of type - assert wt(in_type, 1, i_val) == in_ts - i_val = inter_type(1) # actual scaling - out_val = in_val - d1(i_val) - assert wt(in_type, 1, i_val) == out_val.dtype.str - # Combine scaling and intercept - out_val = (in_val - d1(i_val)) / d1(sl_val) - assert wt(in_type, sl_val, i_val) == out_val.dtype.str - # Confirm that type codes and dtypes work as well - f32s = np.dtype(np.float32).str - assert wt('f4', 1, 0) == f32s - assert wt(np.dtype('f4'), 1, 0) == f32s - - -def test_better_float(): - # Better float function - def check_against(f1, f2): - return f1 if FLOAT_TYPES.index(f1) >= FLOAT_TYPES.index(f2) else f2 - - for first in FLOAT_TYPES: - for other in IUINT_TYPES + sctypes['complex']: - assert better_float_of(first, other) == first - assert better_float_of(other, first) == first - for other2 in IUINT_TYPES + sctypes['complex']: - assert better_float_of(other, other2) == np.float32 - assert better_float_of(other, other2, np.float64) == np.float64 - for second in FLOAT_TYPES: - assert better_float_of(first, second) == check_against(first, second) - # Check codes and dtypes work - assert better_float_of('f4', 'f8', 'f4') == np.float64 - assert better_float_of('i4', 'i8', 'f8') == np.float64 - - -def test_best_write_scale_ftype(): - # Test best write scaling type - # Types return better of (default, array type) unless scale overflows. - # Return float type cannot be less capable than the input array type - for dtt in IUINT_TYPES + FLOAT_TYPES: - arr = np.arange(10, dtype=dtt) - assert best_write_scale_ftype(arr, 1, 0) == better_float_of(dtt, np.float32) - assert best_write_scale_ftype(arr, 1, 0, np.float64) == better_float_of(dtt, np.float64) - assert best_write_scale_ftype(arr, np.float32(2), 0) == better_float_of(dtt, np.float32) - assert best_write_scale_ftype(arr, 1, np.float32(1)) == better_float_of(dtt, np.float32) - # Overflowing ints with scaling results in upcast - best_vals = ((np.float32, np.float64),) - if np.longdouble in OK_FLOATS: - best_vals += ((np.float64, np.longdouble),) - for lower_t, higher_t in best_vals: - # Information on this float - L_info = type_info(lower_t) - t_max = L_info['max'] - nmant = L_info['nmant'] # number of significand digits - big_delta = lower_t(2 ** (floor_log2(t_max) - nmant)) # delta below max - # Even large values that don't overflow don't change output - arr = np.array([0, t_max], dtype=lower_t) - assert best_write_scale_ftype(arr, 1, 0) == lower_t - # Scaling > 1 reduces output values, so no upcast needed - assert best_write_scale_ftype(arr, lower_t(1.01), 0) == lower_t - # Scaling < 1 increases values, so upcast may be needed (and is here) - assert best_write_scale_ftype(arr, lower_t(0.99), 0) == higher_t - # Large minus offset on large array can cause upcast - assert best_write_scale_ftype(arr, 1, -big_delta / 2.01) == lower_t - assert best_write_scale_ftype(arr, 1, -big_delta / 2.0) == higher_t - # With infs already in input, default type returns - arr[0] = np.inf - assert best_write_scale_ftype(arr, lower_t(0.5), 0) == lower_t - arr[0] = -np.inf - assert best_write_scale_ftype(arr, lower_t(0.5), 0) == lower_t - - -def test_write_zeros(): - bio = BytesIO() - write_zeros(bio, 10000) - assert bio.getvalue() == b'\x00' * 10000 - bio.seek(0) - bio.truncate(0) - write_zeros(bio, 10000, 256) - assert bio.getvalue() == b'\x00' * 10000 - bio.seek(0) - bio.truncate(0) - write_zeros(bio, 200, 256) - assert bio.getvalue() == b'\x00' * 200 - - -def test_seek_tell(): - # Test seek tell routine - bio = BytesIO() - in_files = [bio, 'test.bin', 'test.gz', 'test.bz2'] - if HAVE_ZSTD: - in_files += ['test.zst'] - start = 10 - end = 100 - diff = end - start - tail = 7 - with InTemporaryDirectory(): - for in_file, write0 in itertools.product(in_files, (False, True)): - st = functools.partial(seek_tell, write0=write0) - bio.seek(0) - # First write the file - with ImageOpener(in_file, 'wb') as fobj: - assert fobj.tell() == 0 - # already at position - OK - st(fobj, 0) - assert fobj.tell() == 0 - # Move position by writing - fobj.write(b'\x01' * start) - assert fobj.tell() == start - # Files other than BZ2Files can seek forward on write, leaving - # zeros in their wake. BZ2Files can't seek when writing, - # unless we enable the write0 flag to seek_tell - # ZstdFiles also does not support seek forward on write - if not write0 and in_file in ('test.bz2', 'test.zst'): - # write the zeros by hand for the read test below - fobj.write(b'\x00' * diff) - else: - st(fobj, end) - assert fobj.tell() == end - # Write tail - fobj.write(b'\x02' * tail) - bio.seek(0) - # Now read back the file testing seek_tell in reading mode - with ImageOpener(in_file, 'rb') as fobj: - assert fobj.tell() == 0 - st(fobj, 0) - assert fobj.tell() == 0 - st(fobj, start) - assert fobj.tell() == start - st(fobj, end) - assert fobj.tell() == end - # Seek anywhere works in read mode for all files - st(fobj, 0) - bio.seek(0) - # Check we have the expected written output - with ImageOpener(in_file, 'rb') as fobj: - assert fobj.read() == b'\x01' * start + b'\x00' * diff + b'\x02' * tail - input_files = ['test2.gz', 'test2.bz2'] - if HAVE_ZSTD: - input_files += ['test2.zst'] - for in_file in input_files: - # Check failure of write seek backwards - with ImageOpener(in_file, 'wb') as fobj: - fobj.write(b'g' * 10) - assert fobj.tell() == 10 - seek_tell(fobj, 10) - assert fobj.tell() == 10 - with pytest.raises(OSError): - seek_tell(fobj, 5) - # Make sure read seeks don't affect file - with ImageOpener(in_file, 'rb') as fobj: - seek_tell(fobj, 10) - seek_tell(fobj, 0) - with ImageOpener(in_file, 'rb') as fobj: - assert fobj.read() == b'g' * 10 - - -def test_seek_tell_logic(): - # Test logic of seek_tell write0 with dummy class - # Seek works? OK - bio = BytesIO() - seek_tell(bio, 10) - assert bio.tell() == 10 - - class BabyBio(BytesIO): - def seek(self, *args): - raise OSError - - bio = BabyBio() - # Fresh fileobj, position 0, can't seek - error - with pytest.raises(OSError): - bio.seek(10) - # Put fileobj in correct position by writing - ZEROB = b'\x00' - bio.write(ZEROB * 10) - seek_tell(bio, 10) # already there, nothing to do - assert bio.tell() == 10 - assert bio.getvalue() == ZEROB * 10 - # Try write zeros to get to new position - with pytest.raises(OSError): - bio.seek(20) - seek_tell(bio, 20, write0=True) - assert bio.getvalue() == ZEROB * 20 - - -def test_fname_ext_ul_case(): - # Get filename ignoring the case of the filename extension - with InTemporaryDirectory(): - with open('afile.TXT', 'w') as fobj: - fobj.write('Interesting information') - # OSX usually has case-insensitive file systems; Windows also - os_cares_case = not exists('afile.txt') - with open('bfile.txt', 'w') as fobj: - fobj.write('More interesting information') - # If there is no file, the case doesn't change - assert fname_ext_ul_case('nofile.txt') == 'nofile.txt' - assert fname_ext_ul_case('nofile.TXT') == 'nofile.TXT' - # If there is a file, accept upper or lower case for ext - if os_cares_case: - assert fname_ext_ul_case('afile.txt') == 'afile.TXT' - assert fname_ext_ul_case('bfile.TXT') == 'bfile.txt' - else: - assert fname_ext_ul_case('afile.txt') == 'afile.txt' - assert fname_ext_ul_case('bfile.TXT') == 'bfile.TXT' - assert fname_ext_ul_case('afile.TXT') == 'afile.TXT' - assert fname_ext_ul_case('bfile.txt') == 'bfile.txt' - # Not mixed case though - assert fname_ext_ul_case('afile.TxT') == 'afile.TxT' - - -def test_shape_zoom_affine(): - shape = (3, 5, 7) - zooms = (3, 2, 1) - res = shape_zoom_affine(shape, zooms) - exp = np.array( - [ - [-3.0, 0.0, 0.0, 3.0], - [0.0, 2.0, 0.0, -4.0], - [0.0, 0.0, 1.0, -3.0], - [0.0, 0.0, 0.0, 1.0], - ] - ) - assert_array_almost_equal(res, exp) - res = shape_zoom_affine((3, 5), (3, 2)) - exp = np.array( - [ - [-3.0, 0.0, 0.0, 3.0], - [0.0, 2.0, 0.0, -4.0], - [0.0, 0.0, 1.0, -0.0], - [0.0, 0.0, 0.0, 1.0], - ] - ) - assert_array_almost_equal(res, exp) - res = shape_zoom_affine(shape, zooms, False) - exp = np.array( - [ - [3.0, 0.0, 0.0, -3.0], - [0.0, 2.0, 0.0, -4.0], - [0.0, 0.0, 1.0, -3.0], - [0.0, 0.0, 0.0, 1.0], - ] - ) - assert_array_almost_equal(res, exp) - - -def test_rec2dict(): - r = np.zeros((), dtype=[('x', 'i4'), ('s', 'S10')]) - d = rec2dict(r) - assert d == {'x': 0, 's': b''} - - -def test_dtypes(): - # numpy - at least up to 1.5.1 - has odd behavior for hashing - - # specifically: - # In [9]: hash(dtype(' bytes, diagnose_bytes - -With deprecation warnings - -_field_recoders -> field_recoders -""" - -import logging -from io import BytesIO, StringIO - -import numpy as np -import pytest -from numpy.testing import assert_array_equal - -from .. import imageglobals -from ..batteryrunners import Report -from ..casting import sctypes -from ..spatialimages import HeaderDataError -from ..volumeutils import Recoder, native_code, swapped_code -from ..wrapstruct import LabeledWrapStruct, WrapStruct, WrapStructError - -INTEGER_TYPES = sctypes['int'] + sctypes['uint'] - - -def log_chk(hdr, level): - """Utility method to check header checking / logging - - Asserts that log entry appears during ``hdr.check_fix`` for logging level - below `level`. - - Parameters - ---------- - hdr : instance - Instance of header class, with methods ``copy`` and check_fix``. The - header has some minor error (defect) which can be detected with - ``check_fix``. - level : int - Level (severity) of defect present in `hdr`. When logging threshold is - at or below `level`, a message appears in the default log (we test that - happens). - - Returns - ------- - hdrc : instance - Header, with defect corrected. - message : str - Message generated in log when defect was detected. - raiser : tuple - Tuple of error type, callable, arguments that will raise an exception - when then defect is detected. Can be empty. Check with ``if raiser != - (): assert_raises(*raiser)``. - """ - str_io = StringIO() - logger = logging.getLogger('test.logger') - handler = logging.StreamHandler(str_io) - logger.addHandler(handler) - str_io.truncate(0) - hdrc = hdr.copy() - if level == 0: # Should never log or raise error - logger.setLevel(0) - hdrc.check_fix(logger=logger, error_level=0) - assert str_io.getvalue() == '' - logger.removeHandler(handler) - return hdrc, '', () - # Non zero defect level, test above and below threshold. - # Set error level above defect level to prevent exception when defect - # detected. - e_lev = level + 1 - # Logging level above threshold, no log. - logger.setLevel(level + 1) - hdrc.check_fix(logger=logger, error_level=e_lev) - assert str_io.getvalue() == '' - # Logging level below threshold, log appears, store logged message - logger.setLevel(level - 1) - hdrc = hdr.copy() - hdrc.check_fix(logger=logger, error_level=e_lev) - assert str_io.getvalue() != '' - message = str_io.getvalue().strip() - logger.removeHandler(handler) - # When error level == level, check_fix should raise an error - hdrc2 = hdr.copy() - raiser = (HeaderDataError, hdrc2.check_fix, logger, level) - return hdrc, message, raiser - - -class _TestWrapStructBase: - """Class implements base tests for binary headers - - It serves as a base class for other binary header tests - """ - - header_class = None - - def get_bad_bb(self): - # Value for the binaryblock that will raise an error on checks. None - # means do not check - return None - - def test_general_init(self): - hdr = self.header_class() - # binaryblock has length given by header data dtype - binblock = hdr.binaryblock - assert len(binblock) == hdr.structarr.dtype.itemsize - # Endianness will be native by default for empty header - assert hdr.endianness == native_code - # But you can change this if you want - hdr = self.header_class(endianness='swapped') - assert hdr.endianness == swapped_code - # You can also pass in a check flag, without data this has no - # effect - hdr = self.header_class(check=False) - - def _set_something_into_hdr(self, hdr): - # Called from test_bytes test method. Specific to the header data type - raise NotImplementedError('Not in base type') - - def test__eq__(self): - # Test equal and not equal - hdr1 = self.header_class() - hdr2 = self.header_class() - assert hdr1 == hdr2 - self._set_something_into_hdr(hdr1) - assert hdr1 != hdr2 - self._set_something_into_hdr(hdr2) - assert hdr1 == hdr2 - # Check byteswapping maintains equality - hdr3 = hdr2.as_byteswapped() - assert hdr2 == hdr3 - # Check comparing to funny thing says no - assert hdr1 != None - assert hdr1 != 1 - - def test_to_from_fileobj(self): - # Successful write using write_to - hdr = self.header_class() - str_io = BytesIO() - hdr.write_to(str_io) - str_io.seek(0) - hdr2 = self.header_class.from_fileobj(str_io) - assert hdr2.endianness == native_code - assert hdr2.binaryblock == hdr.binaryblock - - def test_mappingness(self): - hdr = self.header_class() - with pytest.raises(ValueError): - hdr['nonexistent key'] = 0.1 - hdr_dt = hdr.structarr.dtype - keys = hdr.keys() - assert keys == list(hdr) - vals = hdr.values() - assert len(vals) == len(keys) - assert keys == list(hdr_dt.names) - for key, val in hdr.items(): - assert_array_equal(hdr[key], val) - # verify that .get operates as destined - assert hdr.get('nonexistent key') is None - assert hdr.get('nonexistent key', 'default') == 'default' - assert hdr.get(keys[0]) == vals[0] - assert hdr.get(keys[0], 'default') == vals[0] - - # make sure .get returns values which evaluate to False. We have to - # use a different falsy value depending on the data type of the first - # header field. - falsyval = 0 if np.issubdtype(hdr_dt[0], np.number) else b'' - - hdr[keys[0]] = falsyval - assert hdr[keys[0]] == falsyval - assert hdr.get(keys[0]) == falsyval - assert hdr.get(keys[0], -1) == falsyval - - def test_endianness_ro(self): - # endianness is a read only property - """Its use in initialization tested in the init tests. - Endianness gives endian interpretation of binary data. It is - read only because the only common use case is to set the - endianness on initialization (or occasionally byteswapping the - data) - but this is done via via the as_byteswapped method - """ - hdr = self.header_class() - with pytest.raises(AttributeError): - hdr.endianness = '<' - - def test_endian_guess(self): - # Check guesses of endian - eh = self.header_class() - assert eh.endianness == native_code - hdr_data = eh.structarr.copy() - hdr_data = hdr_data.byteswap(swapped_code) - eh_swapped = self.header_class(hdr_data.tobytes()) - assert eh_swapped.endianness == swapped_code - - def test_binblock_is_file(self): - # Checks that the binary string representation is the whole of the - # header file. This is true for Analyze types, but not true Nifti - # single file headers, for example, because they will have extension - # strings following. More generally, there may be other perhaps - # optional data after the binary block, in which case you will need to - # override this test - hdr = self.header_class() - str_io = BytesIO() - hdr.write_to(str_io) - assert str_io.getvalue() == hdr.binaryblock - - def test_structarr(self): - # structarr attribute also read only - hdr = self.header_class() - # Just check we can get structarr - hdr.structarr - # That it's read only - with pytest.raises(AttributeError): - hdr.structarr = 0 - - def log_chk(self, hdr, level): - return log_chk(hdr, level) - - def assert_no_log_err(self, hdr): - """Assert that no logging or errors result from this `hdr`""" - fhdr, message, raiser = self.log_chk(hdr, 0) - assert (fhdr, message) == (hdr, '') - - def test_bytes(self): - # Test get of bytes - hdr1 = self.header_class() - bb = hdr1.binaryblock - hdr2 = self.header_class(hdr1.binaryblock) - assert hdr1 == hdr2 - assert hdr1.binaryblock == hdr2.binaryblock - # Do a set into the header, and try again. The specifics of 'setting - # something' will depend on the nature of the bytes object - self._set_something_into_hdr(hdr1) - hdr2 = self.header_class(hdr1.binaryblock) - assert hdr1 == hdr2 - assert hdr1.binaryblock == hdr2.binaryblock - # Short and long binaryblocks give errors - # (here set through init) - with pytest.raises(WrapStructError): - self.header_class(bb[:-1]) - with pytest.raises(WrapStructError): - self.header_class(bb + b'\x00') - # Checking set to true by default, and prevents nonsense being - # set into the header. - bb_bad = self.get_bad_bb() - if bb_bad is None: - return - with imageglobals.LoggingOutputSuppressor(): - with pytest.raises(HeaderDataError): - self.header_class(bb_bad) - # now slips past without check - _ = self.header_class(bb_bad, check=False) - - def test_as_byteswapped(self): - # Check byte swapping - hdr = self.header_class() - assert hdr.endianness == native_code - # same code just returns a copy - hdr2 = hdr.as_byteswapped(native_code) - assert not hdr is hdr2 - # Different code gives byteswapped copy - hdr_bs = hdr.as_byteswapped(swapped_code) - assert hdr_bs.endianness == swapped_code - assert hdr.binaryblock != hdr_bs.binaryblock - # Note that contents is not rechecked on swap / copy - - class DC(self.header_class): - def check_fix(self, *args, **kwargs): - raise Exception - - # Assumes check=True default - with pytest.raises(Exception): - DC(hdr.binaryblock) - hdr = DC(hdr.binaryblock, check=False) - hdr2 = hdr.as_byteswapped(native_code) - hdr_bs = hdr.as_byteswapped(swapped_code) - - def test_empty_check(self): - # Empty header should be error free - hdr = self.header_class() - hdr.check_fix(error_level=0) - - def _dxer(self, hdr): - # Return diagnostics on bytes in `hdr` - binblock = hdr.binaryblock - return self.header_class.diagnose_binaryblock(binblock) - - def test_str(self): - hdr = self.header_class() - # Check something returns from str - s1 = str(hdr) - assert len(s1) > 0 - - -class _TestLabeledWrapStruct(_TestWrapStructBase): - """Test a wrapstruct with value labeling""" - - def test_get_value_label(self): - # Test get value label method - # Make a new class to avoid overwriting recoders of original - class MyHdr(self.header_class): - _field_recoders = {} - - hdr = MyHdr() - # Key not existing raises error - with pytest.raises(ValueError): - hdr.get_value_label('improbable') - # Even if there is a recoder - assert 'improbable' not in hdr.keys() - rec = Recoder([[0, 'fullness of heart']], ('code', 'label')) - hdr._field_recoders['improbable'] = rec - with pytest.raises(ValueError): - hdr.get_value_label('improbable') - # If the key exists in the structure, and is intable, then we can recode - for key, value in hdr.items(): - # No recoder at first - with pytest.raises(ValueError): - hdr.get_value_label(0) - if not value.dtype.type in INTEGER_TYPES or not np.isscalar(value): - continue - code = int(value) - rec = Recoder([[code, 'fullness of heart']], ('code', 'label')) - hdr._field_recoders[key] = rec - assert hdr.get_value_label(key) == 'fullness of heart' - # If key exists, but value is missing, we get 'unknown code' - # Speculating that we can set code value 0 or 1 - new_code = 1 if code == 0 else 0 - hdr[key] = new_code - assert hdr.get_value_label(key) == f'' - - -class MyWrapStruct(WrapStruct): - """An example wrapped struct class""" - - template_dtype = np.dtype([('an_integer', 'i2'), ('a_str', 'S10')]) - - @classmethod - def guessed_endian(klass, hdr): - if hdr['an_integer'] < 256: - return native_code - return swapped_code - - @classmethod - def default_structarr(klass, endianness=None): - structarr = super().default_structarr(endianness) - structarr['an_integer'] = 1 - structarr['a_str'] = b'a string' - return structarr - - @classmethod - def _get_checks(klass): - """Return sequence of check functions for this class""" - return (klass._chk_integer, klass._chk_string) - - """ Check functions in format expected by BatteryRunner class """ - - @staticmethod - def _chk_integer(hdr, fix=False): - rep = Report(HeaderDataError) - if hdr['an_integer'] == 1: - return hdr, rep - rep.problem_level = 40 - rep.problem_msg = 'an_integer should be 1' - if fix: - hdr['an_integer'] = 1 - rep.fix_msg = 'set an_integer to 1' - return hdr, rep - - @staticmethod - def _chk_string(hdr, fix=False): - rep = Report(HeaderDataError) - hdr_str = str(hdr['a_str']) - if hdr_str.lower() == hdr_str: - return hdr, rep - rep.problem_level = 20 - rep.problem_msg = 'a_str should be lower case' - if fix: - hdr['a_str'] = hdr_str.lower() - rep.fix_msg = 'set a_str to lower case' - return hdr, rep - - -class MyLabeledWrapStruct(LabeledWrapStruct, MyWrapStruct): - _field_recoders = {} # for recoding values for str - - -class TestMyWrapStruct(_TestWrapStructBase): - """Test fake binary header defined at top of module""" - - header_class = MyWrapStruct - - def get_bad_bb(self): - # A value for the binary block that should raise an error - # Completely zeros binary block (nearly) always (fairly) bad - return b'\x00' * self.header_class.template_dtype.itemsize - - def _set_something_into_hdr(self, hdr): - # Called from test_bytes test method. Specific to the header data type - hdr['a_str'] = 'reggie' - - def test_empty(self): - # Test contents of default header - hdr = self.header_class() - assert hdr['an_integer'] == 1 - assert hdr['a_str'] == b'a string' - - def test_str(self): - hdr = self.header_class() - s1 = str(hdr) - assert len(s1) > 0 - assert 'an_integer' in s1 - assert 'a_str' in s1 - - def test_copy(self): - hdr = self.header_class() - hdr2 = hdr.copy() - assert hdr == hdr2 - self._set_something_into_hdr(hdr) - assert hdr != hdr2 - self._set_something_into_hdr(hdr2) - assert hdr == hdr2 - - def test_checks(self): - # Test header checks - hdr_t = self.header_class() - # _dxer just returns the diagnostics as a string - # Default hdr is OK - assert self._dxer(hdr_t) == '' - # An integer should be 1 - hdr = hdr_t.copy() - hdr['an_integer'] = 2 - assert self._dxer(hdr) == 'an_integer should be 1' - # String should be lower case - hdr = hdr_t.copy() - hdr['a_str'] = 'My Name' - assert self._dxer(hdr) == 'a_str should be lower case' - - def test_log_checks(self): - # Test logging, fixing, errors for header checking - # This is specific to the particular header type. Here we use the - # pretent header defined at the top of this file - HC = self.header_class - hdr = HC() - hdr['an_integer'] = 2 # severity 40 - fhdr, message, raiser = self.log_chk(hdr, 40) - return - assert fhdr['an_integer'] == 1 - assert message == 'an_integer should be 1; set an_integer to 1' - pytest.raises(*raiser) - # lower case string - hdr = HC() - hdr['a_str'] = 'Hello' # severity = 20 - fhdr, message, raiser = self.log_chk(hdr, 20) - assert message == 'a_str should be lower case; set a_str to lower case' - pytest.raises(*raiser) - - def test_logger_error(self): - # Check that we can reset the logger and error level - # This is again specific to this pretend header - HC = self.header_class - hdr = HC() - # Make a new logger - str_io = StringIO() - logger = logging.getLogger('test.logger') - logger.setLevel(20) - logger.addHandler(logging.StreamHandler(str_io)) - # Prepare something that needs fixing - hdr['a_str'] = 'Fullness' # severity 20 - log_cache = imageglobals.logger, imageglobals.error_level - try: - # Check log message appears in new logger - imageglobals.logger = logger - hdr.copy().check_fix() - assert str_io.getvalue() == 'a_str should be lower case; set a_str to lower case\n' - # Check that error_level in fact causes error to be raised - imageglobals.error_level = 20 - with pytest.raises(HeaderDataError): - hdr.copy().check_fix() - finally: - imageglobals.logger, imageglobals.error_level = log_cache - - -class TestMyLabeledWrapStruct(TestMyWrapStruct, _TestLabeledWrapStruct): - header_class = MyLabeledWrapStruct - - def test_str(self): - # Make sure not to overwrite class dictionary - class MyHdr(self.header_class): - _field_recoders = {} - - hdr = MyHdr() - s1 = str(hdr) - assert len(s1) > 0 - assert 'an_integer : 1' in s1 - assert 'fullness of heart' not in s1 - rec = Recoder([[1, 'fullness of heart']], ('code', 'label')) - hdr._field_recoders['an_integer'] = rec - s2 = str(hdr) - assert 'fullness of heart' in s2 - hdr['an_integer'] = 10 - s1 = str(hdr) - assert '' in s1 diff --git a/nibabel/tmpdirs.py b/nibabel/tmpdirs.py deleted file mode 100644 index 2bcf9fdeba..0000000000 --- a/nibabel/tmpdirs.py +++ /dev/null @@ -1,122 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Contexts for *with* statement providing temporary directories""" - -import os -import tempfile -from contextlib import contextmanager - -try: - from contextlib import chdir as _chdir -except ImportError: # PY310 - - @contextmanager # type: ignore[no-redef] - def _chdir(path): - cwd = os.getcwd() - os.chdir(path) - try: - yield - finally: - os.chdir(cwd) - - -from .deprecated import deprecate_with_version - - -class TemporaryDirectory(tempfile.TemporaryDirectory): - """Create and return a temporary directory. This has the same - behavior as mkdtemp but can be used as a context manager. - - Upon exiting the context, the directory and everything contained - in it are removed. - """ - - @deprecate_with_version( - 'Please use the standard library tempfile.TemporaryDirectory', - '5.0', - '7.0', - ) - def __init__(self, suffix='', prefix=tempfile.template, dir=None): - """ - Examples - -------- - >>> import os - >>> with TemporaryDirectory() as tmpdir: - ... fname = os.path.join(tmpdir, 'example_file.txt') - ... with open(fname, 'wt') as fobj: - ... _ = fobj.write('a string\\n') - >>> os.path.exists(tmpdir) - False - """ - super().__init__(suffix, prefix, dir) - - -@contextmanager -def InTemporaryDirectory(): - """Create, return, and change directory to a temporary directory - - Notes - ----- - As its name suggests, the class temporarily changes the working - directory of the Python process, and this is not thread-safe. We suggest - using it only for tests. - - Examples - -------- - >>> import os - >>> from pathlib import Path - >>> my_cwd = os.getcwd() - >>> with InTemporaryDirectory() as tmpdir: - ... _ = Path('test.txt').write_text('some text') - ... assert os.path.isfile('test.txt') - ... assert os.path.isfile(os.path.join(tmpdir, 'test.txt')) - >>> os.path.exists(tmpdir) - False - >>> os.getcwd() == my_cwd - True - """ - with tempfile.TemporaryDirectory() as tmpdir, _chdir(tmpdir): - yield tmpdir - - -@contextmanager -def InGivenDirectory(path=None): - """Change directory to given directory for duration of ``with`` block - - Useful when you want to use `InTemporaryDirectory` for the final test, but - you are still debugging. For example, you may want to do this in the end: - - >>> with InTemporaryDirectory() as tmpdir: - ... # do something complicated which might break - ... pass - - But indeed the complicated thing does break, and meanwhile the - ``InTemporaryDirectory`` context manager wiped out the directory with the - temporary files that you wanted for debugging. So, while debugging, you - replace with something like: - - >>> with InGivenDirectory() as tmpdir: # Use working directory by default - ... # do something complicated which might break - ... pass - - You can then look at the temporary file outputs to debug what is happening, - fix, and finally replace ``InGivenDirectory`` with ``InTemporaryDirectory`` - again. - - Parameters - ---------- - path : None or str, optional - path to change directory to, for duration of ``with`` block. - Defaults to ``os.getcwd()`` if None - """ - if path is None: - path = os.getcwd() - os.makedirs(path, exist_ok=True) - with _chdir(path): - yield os.path.abspath(path) diff --git a/nibabel/tripwire.py b/nibabel/tripwire.py deleted file mode 100644 index efe651fd93..0000000000 --- a/nibabel/tripwire.py +++ /dev/null @@ -1,53 +0,0 @@ -"""Class to raise error for missing modules or other misfortunes""" - -from typing import Any - - -class TripWireError(AttributeError): - """Exception if trying to use TripWire object""" - - # Has to be subclass of AttributeError, to work round Python 3.5 inspection - # for doctests. Python 3.5 looks for a ``__wrapped__`` attribute during - # initialization of doctests, and only allows AttributeError as signal this - # is not present. - - -def is_tripwire(obj: Any) -> bool: - """Returns True if `obj` appears to be a TripWire object - - Examples - -------- - >>> is_tripwire(object()) - False - >>> is_tripwire(TripWire('some message')) - True - """ - try: - obj.any_attribute - except TripWireError: - return True - except Exception: - pass - return False - - -class TripWire: - """Class raising error if used - - Standard use is to proxy modules that we could not import - - Examples - -------- - >>> a_module = TripWire('We do not have a_module') - >>> a_module.do_silly_thing('with silly string') #doctest: +IGNORE_EXCEPTION_DETAIL - Traceback (most recent call last): - ... - TripWireError: We do not have a_module - """ - - def __init__(self, msg: str) -> None: - self._msg = msg - - def __getattr__(self, attr_name: str) -> Any: - """Raise informative error accessing attributes""" - raise TripWireError(self._msg) diff --git a/nibabel/viewers.py b/nibabel/viewers.py deleted file mode 100644 index 7f7f1d5a41..0000000000 --- a/nibabel/viewers.py +++ /dev/null @@ -1,545 +0,0 @@ -"""Utilities for viewing images - -Includes version of OrthoSlicer3D code originally written by our own -Paul Ivanov. -""" - -import weakref - -import numpy as np - -from .affines import voxel_sizes -from .optpkg import optional_package -from .orientations import aff2axcodes, axcodes2ornt - - -class OrthoSlicer3D: - """Orthogonal-plane slice viewer - - OrthoSlicer3d expects 3- or 4-dimensional array data. It treats - 4D data as a sequence of 3D spatial volumes, where a slice over the final - array axis gives a single 3D spatial volume. - - For 3D data, the default behavior is to create a figure with 3 axes, one - for each slice orientation of the spatial volume. - - Clicking and dragging the mouse in any one axis will select out the - corresponding slices in the other two. Scrolling up and - down moves the slice up and down in the current axis. - - For 4D data, the fourth figure axis can be used to control which - 3D volume is displayed. Alternatively, the ``-`` key can be used to - decrement the displayed volume and the ``+`` or ``=`` keys can be used to - increment it. - - Examples - -------- - >>> import numpy as np - >>> a = np.sin(np.linspace(0, np.pi, 20)) - >>> b = np.sin(np.linspace(0, np.pi*5, 20)) - >>> data = np.outer(a, b)[..., np.newaxis] * a - >>> OrthoSlicer3D(data).show() # doctest: +SKIP - """ - - # Skip doctest above b/c not all systems have mpl installed - - def __init__(self, data, affine=None, axes=None, title=None): - """ - Parameters - ---------- - data : array-like - The data that will be displayed by the slicer. Should have 3+ - dimensions. - affine : array-like or None, optional - Affine transform for the data. This is used to determine - how the data should be sliced for plotting into the sagittal, - coronal, and axial view axes. If None, identity is assumed. - The aspect ratio of the data are inferred from the affine - transform. - axes : tuple of mpl.Axes or None, optional - 3 or 4 axes instances for the 3 slices plus volumes, - or None (default). - title : str or None, optional - The title to display. Can be None (default) to display no - title. - """ - # Use these late imports of matplotlib so that we have some hope that - # the test functions are the first to set the matplotlib backend. The - # tests set the backend to something that doesn't require a display. - self._plt = plt = optional_package('matplotlib.pyplot')[0] - mpl_patch = optional_package('matplotlib.patches')[0] - self._title = title - self._closed = False - self._cross = True - - data = np.asanyarray(data) - if data.ndim < 3: - raise ValueError('data must have at least 3 dimensions') - if np.iscomplexobj(data): - raise TypeError('Complex data not supported') - affine = np.array(affine, float) if affine is not None else np.eye(4) - if affine.shape != (4, 4): - raise ValueError('affine must be a 4x4 matrix') - # determine our orientation - self._affine = affine - codes = axcodes2ornt(aff2axcodes(self._affine)) - self._order = np.argsort([c[0] for c in codes]) - self._flips = np.array([c[1] < 0 for c in codes])[self._order] - self._flips = list(self._flips) + [False] # add volume dim - self._scalers = voxel_sizes(self._affine) - self._inv_affine = np.linalg.inv(affine) - # current volume info - self._volume_dims = data.shape[3:] - self._current_vol_data = data[:, :, :, 0] if data.ndim > 3 else data - self._data = data - self._clim = np.percentile(data, (1.0, 99.0)) - del data - - if axes is None: # make the axes - # ^ +---------+ ^ +---------+ - # | | | | | | - # | Sag | | Cor | - # S | 0 | S | 1 | - # | | | | - # | | | | - # +---------+ +---------+ - # A --> R --> - # ^ +---------+ +---------+ - # | | | | | - # | Axial | | Vol | - # A | 2 | | 3 | - # | | | | - # | | | | - # +---------+ +---------+ - # R --> <-- t --> - - fig, axes = plt.subplots(2, 2) - fig.set_size_inches((8, 8), forward=True) - self._axes = [axes[0, 0], axes[0, 1], axes[1, 0], axes[1, 1]] - plt.tight_layout(pad=0.1) - if self.n_volumes <= 1: - fig.delaxes(self._axes[3]) - self._axes.pop(-1) - if self._title is not None: - fig.canvas.manager.set_window_title(str(title)) - else: - self._axes = [axes[0], axes[1], axes[2]] - if len(axes) > 3: - self._axes.append(axes[3]) - - # Start midway through each axis, idx is current slice number - self._ims, self._data_idx = list(), list() - - # set up axis crosshairs - self._crosshairs = [None] * 3 - r = [ - self._scalers[self._order[2]] / self._scalers[self._order[1]], - self._scalers[self._order[2]] / self._scalers[self._order[0]], - self._scalers[self._order[1]] / self._scalers[self._order[0]], - ] - self._sizes = [self._data.shape[order] for order in self._order] - for ii, xax, yax, ratio, label in zip( - [0, 1, 2], [1, 0, 0], [2, 2, 1], r, ('SAIP', 'SRIL', 'ARPL') - ): - ax = self._axes[ii] - d = np.zeros((self._sizes[yax], self._sizes[xax])) - im = self._axes[ii].imshow( - d, - vmin=self._clim[0], - vmax=self._clim[1], - aspect=1, - cmap='gray', - interpolation='nearest', - origin='lower', - ) - self._ims.append(im) - vert = ax.plot( - [0] * 2, [-0.5, self._sizes[yax] - 0.5], color=(0, 1, 0), linestyle='-' - )[0] - horiz = ax.plot( - [-0.5, self._sizes[xax] - 0.5], [0] * 2, color=(0, 1, 0), linestyle='-' - )[0] - self._crosshairs[ii] = dict(vert=vert, horiz=horiz) - # add text labels (top, right, bottom, left) - lims = [0, self._sizes[xax], 0, self._sizes[yax]] - bump = 0.01 - poss = [ - [lims[1] / 2.0, lims[3]], - [(1 + bump) * lims[1], lims[3] / 2.0], - [lims[1] / 2.0, 0], - [lims[0] - bump * lims[1], lims[3] / 2.0], - ] - anchors = [ - ['center', 'bottom'], - ['left', 'center'], - ['center', 'top'], - ['right', 'center'], - ] - for pos, anchor, lab in zip(poss, anchors, label): - ax.text( - pos[0], pos[1], lab, horizontalalignment=anchor[0], verticalalignment=anchor[1] - ) - ax.axis(lims) - ax.set_aspect(ratio) - ax.patch.set_visible(False) - ax.set_frame_on(False) - ax.axes.get_yaxis().set_visible(False) - ax.axes.get_xaxis().set_visible(False) - self._data_idx.append(0) - self._data_idx.append(-1) # volume - - # Set up volumes axis - if self.n_volumes > 1 and len(self._axes) > 3: - ax = self._axes[3] - try: - ax.set_facecolor('k') - except AttributeError: # old mpl - ax.set_axis_bgcolor('k') - ax.set_title('Volumes') - y = np.zeros(self.n_volumes + 1) - x = np.arange(self.n_volumes + 1) - 0.5 - step = ax.step(x, y, where='post', color='y')[0] - ax.set_xticks(np.unique(np.linspace(0, self.n_volumes - 1, 5).astype(int))) - ax.set_xlim(x[0], x[-1]) - yl = [self._data.min(), self._data.max()] - yl = [lim + s * np.diff(lims)[0] for lim, s in zip(yl, [-1.01, 1.01])] - patch = mpl_patch.Rectangle( - [-0.5, yl[0]], - 1.0, - np.diff(yl)[0], - fill=True, - facecolor=(0, 1, 0), - edgecolor=(0, 1, 0), - alpha=0.25, - ) - ax.add_patch(patch) - ax.set_ylim(yl) - self._volume_ax_objs = dict(step=step, patch=patch) - - self._figs = {a.figure for a in self._axes} - for fig in self._figs: - fig.canvas.mpl_connect('scroll_event', self._on_scroll) - fig.canvas.mpl_connect('motion_notify_event', self._on_mouse) - fig.canvas.mpl_connect('button_press_event', self._on_mouse) - fig.canvas.mpl_connect('key_press_event', self._on_keypress) - fig.canvas.mpl_connect('close_event', self._cleanup) - - # actually set data meaningfully - self._position = np.zeros(4) - self._position[3] = 1.0 # convenience for affine multiplication - self._changing = False # keep track of status to avoid loops - self._links = [] # other viewers this one is linked to - self._plt.draw() - for fig in self._figs: - fig.canvas.draw() - self._set_volume_index(0, update_slices=False) - self._set_position(0.0, 0.0, 0.0) - self._draw() - - def __repr__(self): - title = '' if self._title is None else f'{self._title} ' - vol = '' if self.n_volumes <= 1 else f', {self.n_volumes}' - r = ( - f'<{self.__class__.__name__}: {title}({self._sizes[0]}, ' - f'{self._sizes[1]}, {self._sizes[2]}{vol})>' - ) - return r - - # User-level functions ################################################### - def show(self): - """Show the slicer in blocking mode; convenience for ``plt.show()``""" - self._plt.show() - - def close(self): - """Close the viewer figures""" - self._cleanup() - for f in self._figs: - self._plt.close(f) - - def _cleanup(self): - """Clean up before closing""" - self._closed = True - for link in list(self._links): # make a copy before iterating - self._unlink(link()) - - def draw(self): - """Redraw the current image""" - for fig in self._figs: - fig.canvas.draw() - - @property - def n_volumes(self): - """Number of volumes in the data""" - return int(np.prod(self._volume_dims)) - - @property - def position(self): - """The current coordinates""" - return self._position[:3].copy() - - @property - def figs(self): - """A tuple of the figure(s) containing the axes""" - return tuple(self._figs) - - @property - def cmap(self): - """The current colormap""" - return self._cmap - - @cmap.setter - def cmap(self, cmap): - for im in self._ims: - im.set_cmap(cmap) - self._cmap = cmap - self.draw() - - @property - def clim(self): - """The current color limits""" - return self._clim - - @clim.setter - def clim(self, clim): - clim = np.array(clim, float) - if clim.shape != (2,): - raise ValueError('clim must be a 2-element array-like') - for im in self._ims: - im.set_clim(clim) - self._clim = tuple(clim) - self.draw() - - def link_to(self, other): - """Link positional changes between two canvases - - Parameters - ---------- - other : instance of OrthoSlicer3D - Other viewer to use to link movements. - """ - if not isinstance(other, self.__class__): - raise TypeError( - f'other must be an instance of {self.__class__.__name__}, not {type(other)}' - ) - self._link(other, is_primary=True) - - def _link(self, other, is_primary): - """Link a viewer""" - ref = weakref.ref(other) - if ref in self._links: - return - self._links.append(ref) - if is_primary: - other._link(self, is_primary=False) - other.set_position(*self.position) - - def _unlink(self, other): - """Unlink a viewer""" - ref = weakref.ref(other) - if ref in self._links: - self._links.pop(self._links.index(ref)) - ref()._unlink(self) - - def _notify_links(self): - """Notify linked canvases of a position change""" - for link in self._links: - link().set_position(*self.position[:3]) - - def set_position(self, x=None, y=None, z=None): - """Set current displayed slice indices - - Parameters - ---------- - x : float | None - X coordinate to use. If None, do not change. - y : float | None - Y coordinate to use. If None, do not change. - z : float | None - Z coordinate to use. If None, do not change. - """ - self._set_position(x, y, z) - self._draw() - - def set_volume_idx(self, v): - """Set current displayed volume index - - Parameters - ---------- - v : int - Volume index. - """ - self._set_volume_index(v) - self._draw() - - def _set_volume_index(self, v, update_slices=True): - """Set the plot data using a volume index""" - v = self._data_idx[3] if v is None else round(v) - if v == self._data_idx[3]: - return - max_ = np.prod(self._volume_dims) - self._data_idx[3] = max(min(round(v), max_ - 1), 0) - idx = (slice(None), slice(None), slice(None)) - if self._data.ndim > 3: - idx = idx + tuple(np.unravel_index(self._data_idx[3], self._volume_dims)) - self._current_vol_data = self._data[idx] - # update all of our slice plots - if update_slices: - self._set_position(None, None, None, notify=False) - - def _set_position(self, x, y, z, notify=True): - """Set the plot data using a physical position""" - # deal with volume first - if self._changing: - return - self._changing = True - x = self._position[0] if x is None else float(x) - y = self._position[1] if y is None else float(y) - z = self._position[2] if z is None else float(z) - - # deal with slicing appropriately - self._position[:3] = [x, y, z] - idxs = np.dot(self._inv_affine, self._position)[:3] - idxs_new_order = idxs[self._order] - for ii, (size, idx) in enumerate(zip(self._sizes, idxs_new_order)): - self._data_idx[ii] = max(min(round(idx), size - 1), 0) - for ii in range(3): - # sagittal: get to S/A - # coronal: get to S/L - # axial: get to A/L - data = np.rollaxis(self._current_vol_data, axis=self._order[ii])[self._data_idx[ii]] - xax = [1, 0, 0][ii] - yax = [2, 2, 1][ii] - if self._order[xax] < self._order[yax]: - data = data.T - if self._flips[xax]: - data = data[:, ::-1] - if self._flips[yax]: - data = data[::-1] - self._ims[ii].set_data(data) - # deal with crosshairs - loc = self._data_idx[ii] - if self._flips[ii]: - loc = self._sizes[ii] - 1 - loc - loc = [loc] * 2 - if ii == 0: - self._crosshairs[2]['vert'].set_xdata(loc) - self._crosshairs[1]['vert'].set_xdata(loc) - elif ii == 1: - self._crosshairs[2]['horiz'].set_ydata(loc) - self._crosshairs[0]['vert'].set_xdata(loc) - else: # ii == 2 - self._crosshairs[1]['horiz'].set_ydata(loc) - self._crosshairs[0]['horiz'].set_ydata(loc) - - # Update volume trace - if self.n_volumes > 1 and len(self._axes) > 3: - idx = [slice(None)] * len(self._axes) - for ii in range(3): - idx[self._order[ii]] = self._data_idx[ii] - vdata = self._data[tuple(idx)].ravel() - vdata = np.concatenate((vdata, [vdata[-1]])) - self._volume_ax_objs['patch'].set_x(self._data_idx[3] - 0.5) - self._volume_ax_objs['step'].set_ydata(vdata) - if notify: - self._notify_links() - self._changing = False - - # Matplotlib handlers #################################################### - def _in_axis(self, event): - """Return axis index if within one of our axes, else None""" - if event.inaxes is None: - return None - for ii, ax in enumerate(self._axes): - if event.inaxes is ax: - return ii - - def _on_scroll(self, event): - """Handle mpl scroll wheel event""" - assert event.button in ('up', 'down') - ii = self._in_axis(event) - if ii is None: - return - if event.key is not None and 'shift' in event.key: - if self.n_volumes <= 1: - return - ii = 3 # shift: change volume in any axis - assert ii in range(4) - dv = 10.0 if event.key is not None and 'control' in event.key else 1.0 - dv *= 1.0 if event.button == 'up' else -1.0 - dv *= -1 if self._flips[ii] else 1 - val = self._data_idx[ii] + dv - - if ii == 3: - self._set_volume_index(val) - else: - coords = [self._data_idx[k] for k in range(3)] - coords[ii] = val - coords_ordered = [0, 0, 0, 1] - for k in range(3): - coords_ordered[self._order[k]] = coords[k] - position = np.dot(self._affine, coords_ordered)[:3] - self._set_position(*position) - self._draw() - - def _on_mouse(self, event): - """Handle mpl mouse move and button press events""" - if event.button != 1: # only enabled while dragging - return - ii = self._in_axis(event) - if ii is None: - return - if ii == 3: - # volume plot directly translates - self._set_volume_index(event.xdata) - else: - # translate click xdata/ydata to physical position - xax, yax = [ - [self._order[1], self._order[2]], - [self._order[0], self._order[2]], - [self._order[0], self._order[1]], - ][ii] - x, y = event.xdata, event.ydata - x = self._sizes[xax] - x - 1 if self._flips[xax] else x - y = self._sizes[yax] - y - 1 if self._flips[yax] else y - idxs = np.ones(4) - idxs[xax] = x - idxs[yax] = y - idxs[self._order[ii]] = self._data_idx[ii] - self._set_position(*np.dot(self._affine, idxs)[:3]) - self._draw() - - def _on_keypress(self, event): - """Handle mpl keypress events""" - if event.key is not None and 'escape' in event.key: - self.close() - elif event.key in ('=', '+'): - # increment volume index - new_idx = min(self._data_idx[3] + 1, self.n_volumes) - self._set_volume_index(new_idx, update_slices=True) - self._draw() - elif event.key == '-': - # decrement volume index - new_idx = max(self._data_idx[3] - 1, 0) - self._set_volume_index(new_idx, update_slices=True) - self._draw() - elif event.key == 'ctrl+x': - self._cross = not self._cross - self._draw() - - def _draw(self): - """Update all four (or three) plots""" - if self._closed: # make sure we don't draw when we shouldn't - return - for ii in range(3): - ax = self._axes[ii] - ax.draw_artist(self._ims[ii]) - if self._cross: - for line in self._crosshairs[ii].values(): - ax.draw_artist(line) - ax.figure.canvas.blit(ax.bbox) - if self.n_volumes > 1 and len(self._axes) > 3: - ax = self._axes[3] - ax.draw_artist(ax.patch) # axis bgcolor to erase old lines - for key in ('step', 'patch'): - ax.draw_artist(self._volume_ax_objs[key]) - ax.figure.canvas.blit(ax.bbox) diff --git a/nibabel/volumeutils.py b/nibabel/volumeutils.py deleted file mode 100644 index 41bff7275c..0000000000 --- a/nibabel/volumeutils.py +++ /dev/null @@ -1,1430 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Utility functions for analyze-like formats""" - -from __future__ import annotations - -import sys -import typing as ty -import warnings -from functools import reduce -from operator import getitem, mul -from os.path import exists, splitext - -import numpy as np - -from ._compression import COMPRESSED_FILE_LIKES -from .casting import OK_FLOATS, shared_range -from .externals.oset import OrderedSet - -if ty.TYPE_CHECKING: - import io - - import numpy.typing as npt - - from ._typing import TypeVar - - Scalar = np.number | float - - K = TypeVar('K') - V = TypeVar('V') - DT = TypeVar('DT', bound=np.generic) - -sys_is_le = sys.byteorder == 'little' -native_code: ty.Literal['<', '>'] = '<' if sys_is_le else '>' -swapped_code: ty.Literal['<', '>'] = '>' if sys_is_le else '<' - -_endian_codes = ( # numpy code, aliases - ('<', 'little', 'l', 'le', 'L', 'LE'), - ('>', 'big', 'BIG', 'b', 'be', 'B', 'BE'), - (native_code, 'native', 'n', 'N', '=', '|', 'i', 'I'), - (swapped_code, 'swapped', 's', 'S', '!'), -) -# We'll put these into the Recoder class after we define it - -#: default compression level when writing gz and bz2 files -default_compresslevel = 1 - - -class Recoder: - """class to return canonical code(s) from code or aliases - - The concept is a lot easier to read in the implementation and - tests than it is to explain, so... - - >>> # If you have some codes, and several aliases, like this: - >>> code1 = 1; aliases1=['one', 'first'] - >>> code2 = 2; aliases2=['two', 'second'] - >>> # You might want to do this: - >>> codes = [[code1]+aliases1,[code2]+aliases2] - >>> recodes = Recoder(codes) - >>> recodes.code['one'] - 1 - >>> recodes.code['second'] - 2 - >>> recodes.code[2] - 2 - >>> # Or maybe you have a code, a label and some aliases - >>> codes=((1,'label1','one', 'first'),(2,'label2','two')) - >>> # you might want to get back the code or the label - >>> recodes = Recoder(codes, fields=('code','label')) - >>> recodes.code['first'] - 1 - >>> recodes.code['label1'] - 1 - >>> recodes.label[2] - 'label2' - >>> # For convenience, you can get the first entered name by - >>> # indexing the object directly - >>> recodes[2] - 2 - """ - - fields: tuple[str, ...] - - def __init__( - self, - codes: ty.Sequence[ty.Sequence[ty.Hashable]], - fields: ty.Sequence[str] = ('code',), - map_maker: type[ty.Mapping[ty.Hashable, ty.Hashable]] = dict, - ): - """Create recoder object - - ``codes`` give a sequence of code, alias sequences - ``fields`` are names by which the entries in these sequences can be - accessed. - - By default ``fields`` gives the first column the name - "code". The first column is the vector of first entries - in each of the sequences found in ``codes``. Thence you can - get the equivalent first column value with ob.code[value], - where value can be a first column value, or a value in any of - the other columns in that sequence. - - You can give other columns names too, and access them in the - same way - see the examples in the class docstring. - - Parameters - ---------- - codes : sequence of sequences - Each sequence defines values (codes) that are equivalent - fields : {('code',) string sequence}, optional - names by which elements in sequences can be accessed - map_maker: callable, optional - constructor for dict-like objects used to store key value pairs. - Default is ``dict``. ``map_maker()`` generates an empty mapping. - The mapping need only implement ``__getitem__, __setitem__, keys, - values``. - """ - self.fields = tuple(fields) - self.field1 = {} # a placeholder for the check below - for name in fields: - if name in self.__dict__: - raise KeyError(f'Input name {name} already in object dict') - self.__dict__[name] = map_maker() - self.field1 = self.__dict__[fields[0]] - self.add_codes(codes) - - def __getattr__(self, key: str) -> ty.Mapping[ty.Hashable, ty.Hashable]: - # By setting this, we let static analyzers know that dynamic attributes will - # be dict-like (Mapping). - # However, __getattr__ is called if looking up the field in __dict__ fails, - # so we only get here if the attribute is really missing. - raise AttributeError(f'{self.__class__.__name__!r} object has no attribute {key!r}') - - def add_codes(self, code_syn_seqs: ty.Sequence[ty.Sequence[ty.Hashable]]) -> None: - """Add codes to object - - Parameters - ---------- - code_syn_seqs : sequence - sequence of sequences, where each sequence ``S = code_syn_seqs[n]`` - for n in 0..len(code_syn_seqs), is a sequence giving values in the - same order as ``self.fields``. Each S should be at least of the - same length as ``self.fields``. After this call, if ``self.fields - == ['field1', 'field2'], then ``self.field1[S[n]] == S[0]`` for all - n in 0..len(S) and ``self.field2[S[n]] == S[1]`` for all n in - 0..len(S). - - Examples - -------- - >>> code_syn_seqs = ((2, 'two'), (1, 'one')) - >>> rc = Recoder(code_syn_seqs) - >>> rc.value_set() == set((1,2)) - True - >>> rc.add_codes(((3, 'three'), (1, 'first'))) - >>> rc.value_set() == set((1,2,3)) - True - >>> print(rc.value_set()) # set is actually ordered - OrderedSet([2, 1, 3]) - """ - for code_syns in code_syn_seqs: - # Add all the aliases - for alias in code_syns: - # For all defined fields, make every value in the sequence be - # an entry to return matching index value. - for field_ind, field_name in enumerate(self.fields): - self.__dict__[field_name][alias] = code_syns[field_ind] - - def __getitem__(self, key: ty.Hashable) -> ty.Hashable: - """Return value from field1 dictionary (first column of values) - - Returns same value as ``obj.field1[key]`` and, with the - default initializing ``fields`` argument of fields=('code',), - this will return the same as ``obj.code[key]`` - - >>> codes = ((1, 'one'), (2, 'two')) - >>> Recoder(codes)['two'] - 2 - """ - return self.field1[key] - - def __contains__(self, key: ty.Hashable) -> bool: - """True if field1 in recoder contains `key`""" - return key in self.field1 - - def keys(self): - """Return all available code and alias values - - Returns same value as ``obj.field1.keys()`` and, with the - default initializing ``fields`` argument of fields=('code',), - this will return the same as ``obj.code.keys()`` - - >>> codes = ((1, 'one'), (2, 'two'), (1, 'repeat value')) - >>> k = Recoder(codes).keys() - >>> set(k) == set([1, 2, 'one', 'repeat value', 'two']) - True - """ - return self.field1.keys() - - def value_set(self, name: str | None = None) -> OrderedSet: - """Return OrderedSet of possible returned values for column - - By default, the column is the first column. - - Returns same values as ``set(obj.field1.values())`` and, - with the default initializing``fields`` argument of - fields=('code',), this will return the same as - ``set(obj.code.values())`` - - Parameters - ---------- - name : {None, string} - Where default of none gives result for first column - - >>> codes = ((1, 'one'), (2, 'two'), (1, 'repeat value')) - >>> vs = Recoder(codes).value_set() - >>> vs == set([1, 2]) # Sets are not ordered, hence this test - True - >>> rc = Recoder(codes, fields=('code', 'label')) - >>> rc.value_set('label') == set(('one', 'two', 'repeat value')) - True - """ - if name is None: - d = self.field1 - else: - d = self.__dict__[name] - return OrderedSet(d.values()) - - -# Endian code aliases -endian_codes = Recoder(_endian_codes) - - -class DtypeMapper(dict[ty.Hashable, ty.Hashable]): - """Specialized mapper for numpy dtypes - - We pass this mapper into the Recoder class to deal with numpy dtype - hashing. - - The hashing problem is that dtypes that compare equal may not have the same - hash. This is true for numpys up to the current at time of writing - (1.6.0). For numpy 1.2.1 at least, even dtypes that look exactly the same - in terms of fields don't always have the same hash. This makes dtypes - difficult to use as keys in a dictionary. - - This class wraps a dictionary in order to implement a __getitem__ to deal - with dtype hashing. If the key doesn't appear to be in the mapping, and it - is a dtype, we compare (using ==) all known dtype keys to the input key, - and return any matching values for the matching key. - """ - - def __init__(self) -> None: - super().__init__() - self._dtype_keys: list[np.dtype] = [] - - def __setitem__(self, key: ty.Hashable, value: ty.Hashable) -> None: - """Set item into mapping, checking for dtype keys - - Cache dtype keys for comparison test in __getitem__ - """ - super().__setitem__(key, value) - if isinstance(key, np.dtype): - self._dtype_keys.append(key) - - def __getitem__(self, key: ty.Hashable) -> ty.Hashable: - """Get item from mapping, checking for dtype keys - - First do simple hash lookup, then check for a dtype key that has failed - the hash lookup. Look then for any known dtype keys that compare equal - to `key`. - """ - try: - return super().__getitem__(key) - except KeyError: - pass - if isinstance(key, np.dtype): - for dt in self._dtype_keys: - if key == dt: - return super().__getitem__(dt) - raise KeyError(key) - - -def pretty_mapping( - mapping: ty.Mapping[K, V], - getterfunc: ty.Callable[[ty.Mapping[K, V], K], V] | None = None, -) -> str: - """Make pretty string from mapping - - Adjusts text column to print values on basis of longest key. - Probably only sensible if keys are mainly strings. - - You can pass in a callable that does clever things to get the values - out of the mapping, given the names. By default, we just use - ``__getitem__`` - - Parameters - ---------- - mapping : mapping - implementing iterator returning keys and .items() - getterfunc : None or callable - callable taking two arguments, ``obj`` and ``key`` where ``obj`` - is the passed mapping. If None, just use ``lambda obj, key: - obj[key]`` - - Returns - ------- - str : string - - Examples - -------- - >>> d = {'a key': 'a value'} - >>> print(pretty_mapping(d)) - a key : a value - >>> class C: # to control ordering, show get_ method - ... def __iter__(self): - ... return iter(('short_field','longer_field')) - ... def __getitem__(self, key): - ... if key == 'short_field': - ... return 0 - ... if key == 'longer_field': - ... return 'str' - ... def get_longer_field(self): - ... return 'method string' - >>> def getter(obj, key): - ... # Look for any 'get_' methods - ... try: - ... return obj.__getattribute__('get_' + key)() - ... except AttributeError: - ... return obj[key] - >>> print(pretty_mapping(C(), getter)) - short_field : 0 - longer_field : method string - """ - if getterfunc is None: - getterfunc = getitem - mxlen = max(len(str(name)) for name in mapping) - return '\n'.join(f'{name:{mxlen}s} : {getterfunc(mapping, name)}' for name in mapping) - - -def make_dt_codes(codes_seqs: ty.Sequence[ty.Sequence]) -> Recoder: - """Create full dt codes Recoder instance from datatype codes - - Include created numpy dtype (from numpy type) and opposite endian - numpy dtype - - Parameters - ---------- - codes_seqs : sequence of sequences - contained sequences make be length 3 or 4, but must all be the same - length. Elements are data type code, data type name, and numpy - type (such as ``np.float32``). The fourth element is the nifti string - representation of the code (e.g. "NIFTI_TYPE_FLOAT32") - - Returns - ------- - rec : ``Recoder`` instance - Recoder that, by default, returns ``code`` when indexed with any - of the corresponding code, name, type, dtype, or swapped dtype. - You can also index with ``niistring`` values if codes_seqs had sequences - of length 4 instead of 3. - """ - fields = ['code', 'label', 'type'] - len0 = len(codes_seqs[0]) - if len0 not in (3, 4): - raise ValueError('Sequences must be length 3 or 4') - if len0 == 4: - fields.append('niistring') - dt_codes = [] - for seq in codes_seqs: - if len(seq) != len0: - raise ValueError('Sequences must all have the same length') - np_type = seq[2] - this_dt = np.dtype(np_type) - # Add swapped dtype to synonyms - code_syns = list(seq) + [this_dt, this_dt.newbyteorder(swapped_code)] - dt_codes.append(code_syns) - return Recoder(dt_codes, fields + ['dtype', 'sw_dtype'], DtypeMapper) - - -def _is_compressed_fobj(fobj: io.IOBase) -> bool: - """Return True if fobj represents a compressed data file-like object""" - return isinstance(fobj, COMPRESSED_FILE_LIKES) - - -def array_from_file( - shape: tuple[int, ...], - in_dtype: np.dtype[DT], - infile: io.IOBase, - offset: int = 0, - order: ty.Literal['C', 'F'] = 'F', - mmap: bool | ty.Literal['c', 'r', 'r+'] = True, -) -> npt.NDArray[DT]: - """Get array from file with specified shape, dtype and file offset - - Parameters - ---------- - shape : sequence - sequence specifying output array shape - in_dtype : numpy dtype - fully specified numpy dtype, including correct endianness - infile : file-like - open file-like object implementing at least read() and seek() - offset : int, optional - offset in bytes into `infile` to start reading array data. Default is 0 - order : {'F', 'C'} string - order in which to write data. Default is 'F' (fortran order). - mmap : {True, False, 'c', 'r', 'r+'} - `mmap` controls the use of numpy memory mapping for reading data. If - False, do not try numpy ``memmap`` for data array. If one of {'c', - 'r', 'r+'}, try numpy memmap with ``mode=mmap``. A `mmap` value of - True gives the same behavior as ``mmap='c'``. If `infile` cannot be - memory-mapped, ignore `mmap` value and read array from file. - - Returns - ------- - arr : array-like - array like object that can be sliced, containing data - - Examples - -------- - >>> from io import BytesIO - >>> bio = BytesIO() - >>> arr = np.arange(6).reshape(1,2,3) - >>> _ = bio.write(arr.tobytes('F')) # outputs int - >>> arr2 = array_from_file((1,2,3), arr.dtype, bio) - >>> np.all(arr == arr2) - True - >>> bio = BytesIO() - >>> _ = bio.write(b' ' * 10) - >>> _ = bio.write(arr.tobytes('F')) - >>> arr2 = array_from_file((1,2,3), arr.dtype, bio, 10) - >>> np.all(arr == arr2) - True - """ - if mmap not in (True, False, 'c', 'r', 'r+'): - raise ValueError("mmap value should be one of True, False, 'c', 'r', 'r+'") - in_dtype = np.dtype(in_dtype) - # Get file-like object from Opener instance - infile = getattr(infile, 'fobj', infile) - if mmap and not _is_compressed_fobj(infile): - mode = 'c' if mmap is True else mmap - try: # Try memmapping file on disk - return np.memmap(infile, in_dtype, mode=mode, shape=shape, order=order, offset=offset) - # The error raised by memmap, for different file types, has - # changed in different incarnations of the numpy routine - except (AttributeError, TypeError, ValueError): - pass - if len(shape) == 0: - return np.array([], in_dtype) - # Use reduce and mul to work around numpy integer overflow - n_bytes = reduce(mul, shape) * in_dtype.itemsize - if n_bytes == 0: - return np.array([], in_dtype) - # Read data from file - infile.seek(offset) - if hasattr(infile, 'readinto'): - data_bytes = bytearray(n_bytes) - n_read = infile.readinto(data_bytes) - needs_copy = False - else: - data_bytes = infile.read(n_bytes) - n_read = len(data_bytes) - needs_copy = True - if n_bytes != n_read: - raise OSError( - f'Expected {n_bytes} bytes, got {n_read} bytes from ' - f'{getattr(infile, "name", "object")}\n - could the file be damaged?' - ) - arr: np.ndarray = np.ndarray(shape, in_dtype, buffer=data_bytes, order=order) - if needs_copy: - return arr.copy() - arr.flags.writeable = True - return arr - - -def array_to_file( - data: npt.ArrayLike, - fileobj: io.IOBase, - out_dtype: np.dtype | None = None, - offset: int = 0, - intercept: Scalar = 0.0, - divslope: Scalar | None = 1.0, - mn: Scalar | None = None, - mx: Scalar | None = None, - order: ty.Literal['C', 'F'] = 'F', - nan2zero: bool = True, -) -> None: - """Helper function for writing arrays to file objects - - Writes arrays as scaled by `intercept` and `divslope`, and clipped - at (prescaling) `mn` minimum, and `mx` maximum. - - * Clip `data` array at min `mn`, max `max` where there are not None -> - ``clipped`` (this is *pre scale clipping*) - * Scale ``clipped`` with ``clipped_scaled = (clipped - intercept) / - divslope`` - * Clip ``clipped_scaled`` to fit into range of `out_dtype` (*post scale - clipping*) -> ``clipped_scaled_clipped`` - * If converting to integer `out_dtype` and `nan2zero` is True, set NaN - values in ``clipped_scaled_clipped`` to 0 - * Write ``clipped_scaled_clipped_n2z`` to fileobj `fileobj` starting at - offset `offset` in memory layout `order` - - Parameters - ---------- - data : array-like - array or array-like to write. - fileobj : file-like - file-like object implementing ``write`` method. - out_dtype : None or dtype, optional - dtype to write array as. Data array will be coerced to this dtype - before writing. If None (default) then use input data type. - offset : None or int, optional - offset into fileobj at which to start writing data. Default is 0. None - means start at current file position - intercept : scalar, optional - scalar to subtract from data, before dividing by ``divslope``. Default - is 0.0 - divslope : None or scalar, optional - scalefactor to *divide* data by before writing. Default is 1.0. If - None, there is no valid data, we write zeros. - mn : scalar, optional - minimum threshold in (unscaled) data, such that all data below this - value are set to this value. Default is None (no threshold). The - typical use is to set -np.inf in the data to have this value (which - might be the minimum non-finite value in the data). - mx : scalar, optional - maximum threshold in (unscaled) data, such that all data above this - value are set to this value. Default is None (no threshold). The - typical use is to set np.inf in the data to have this value (which - might be the maximum non-finite value in the data). - order : {'F', 'C'}, optional - memory order to write array. Default is 'F' - nan2zero : {True, False}, optional - Whether to set NaN values to 0 when writing integer output. Defaults - to True. If False, NaNs will be represented as numpy does when - casting; this depends on the underlying C library and is undefined. In - practice `nan2zero` == False might be a good choice when you completely - sure there will be no NaNs in the data. This value ignored for float - output types. NaNs are treated as zero *before* applying `intercept` - and `divslope` - so an array ``[np.nan]`` with an `intercept` of 10 - becomes ``[-10]`` after conversion to integer `out_dtype` with - `nan2zero` set. That is because you will likely apply `divslope` and - `intercept` in reverse order when reading the data back, returning the - zero you probably expected from the input NaN. - - Examples - -------- - >>> from io import BytesIO - >>> sio = BytesIO() - >>> data = np.arange(10, dtype=np.float64) - >>> array_to_file(data, sio, np.float64) - >>> sio.getvalue() == data.tobytes('F') - True - >>> _ = sio.truncate(0); _ = sio.seek(0) # outputs 0 - >>> array_to_file(data, sio, np.int16) - >>> sio.getvalue() == data.astype(np.int16).tobytes() - True - >>> _ = sio.truncate(0); _ = sio.seek(0) - >>> array_to_file(data.byteswap(), sio, np.float64) - >>> sio.getvalue() == data.byteswap().tobytes('F') - True - >>> _ = sio.truncate(0); _ = sio.seek(0) - >>> array_to_file(data, sio, np.float64, order='C') - >>> sio.getvalue() == data.tobytes('C') - True - """ - # Shield special case - if not np.isfinite(np.array((intercept, 1.0 if divslope is None else divslope))).all(): - raise ValueError('divslope and intercept must be finite') - if divslope == 0: - raise ValueError('divslope cannot be zero') - data = np.asanyarray(data) - in_dtype = data.dtype - if out_dtype is None: - out_dtype = in_dtype - else: - out_dtype = np.dtype(out_dtype) - if offset is not None: - seek_tell(fileobj, offset) - if divslope is None or (mn, mx) == (0, 0) or ((mn is not None and mx is not None) and mx < mn): - write_zeros(fileobj, data.size * out_dtype.itemsize) - return - if order not in 'FC': - raise ValueError('Order should be one of F or C') - # Simple cases - pre_clips = None if (mn is None and mx is None) else (mn, mx) - null_scaling = intercept == 0 and divslope == 1 - if in_dtype.type == np.void: - if not null_scaling: - raise ValueError('Cannot scale non-numeric types') - if pre_clips is not None: - raise ValueError('Cannot clip non-numeric types') - return _write_data(data, fileobj, out_dtype, order) - if pre_clips is not None: - pre_clips = _dt_min_max(in_dtype, *pre_clips) - if null_scaling and np.can_cast(in_dtype, out_dtype): - return _write_data(data, fileobj, out_dtype, order, pre_clips=pre_clips) - # Force upcasting for floats by making atleast_1d. - slope, inter = (np.atleast_1d(v) for v in (divslope, intercept)) - # Default working point type for applying slope / inter - if slope.dtype.kind in 'iu': - slope = slope.astype(float) - if inter.dtype.kind in 'iu': - inter = inter.astype(float) - in_kind = in_dtype.kind - out_kind = out_dtype.kind - if out_kind in 'fc': - return _write_data( - data, fileobj, out_dtype, order, slope=slope, inter=inter, pre_clips=pre_clips - ) - assert out_kind in 'iu' - if in_kind in 'iu': - if null_scaling: - # Must be large int to small int conversion; add clipping to - # pre scale thresholds - mn, mx = _dt_min_max(in_dtype, mn, mx) - mn_out, mx_out = _dt_min_max(out_dtype) - pre_clips = max(mn, mn_out), min(mx, mx_out) # type: ignore[type-var] - return _write_data(data, fileobj, out_dtype, order, pre_clips=pre_clips) - # In any case, we do not want to check for nans because we've already - # disallowed scaling that generates nans - nan2zero = False - # We are either scaling into c/floats or starting with c/floats, then we're - # going to integers - # Because we're going to integers, complex inter and slope will only slow - # us down, cast to float - slope, inter = (v.astype(_matching_float(v.dtype)) for v in (slope, inter)) - # We'll do the thresholding on the scaled data, so turn off the - # thresholding on the unscaled data - pre_clips = None - # We may need to cast the original array to another type - cast_in_dtype = in_dtype - if in_kind == 'c': - # Cast to floats before anything else - cast_in_dtype = np.dtype(_matching_float(in_dtype)) - elif in_kind == 'f' and in_dtype.itemsize == 2: - # Make sure we don't use float16 as a working type - cast_in_dtype = np.dtype(np.float32) - w_type = working_type(cast_in_dtype, slope, inter) - dt_mnmx = _dt_min_max(cast_in_dtype, mn, mx) - # We explore for a good precision to avoid infs and clipping - # Find smallest float type equal or larger than the current working - # type, that can contain range of extremes after scaling, without going - # to +-inf - extremes = np.array(dt_mnmx, dtype=cast_in_dtype) - w_type = best_write_scale_ftype(extremes, slope, inter, w_type) - # Push up precision by casting the slope, inter - slope, inter = (v.astype(w_type) for v in (slope, inter)) - # We need to know the result of applying slope and inter to the min and - # max of the array, in order to clip the output array, after applying - # the slope and inter. Otherwise we'd need to clip twice, once before - # applying (slope, inter), and again after, to ensure we have not hit - # over- or under-flow. For the same reason we need to know the result of - # applying slope, inter to 0, in order to fill in the nan output value - # after scaling etc. We could fill with 0 before scaling, but then we'd - # have to do an extra copy before filling nans with 0, to avoid - # overwriting the input array - # Run min, max, 0 through scaling / rint - specials = np.array(dt_mnmx + (0,), dtype=w_type) - if inter != 0.0: - specials = specials - inter - if slope != 1.0: - specials = specials / slope - assert specials.dtype.type == w_type - post_mn, post_mx, nan_fill = np.rint(specials) - if post_mn > post_mx: # slope could be negative - post_mn, post_mx = post_mx, post_mn - # Make sure that the thresholds exclude any value that will get badly cast - # to the integer type. This is not the same as using the maximumum of the - # output dtype as thresholds, because these may not be exactly represented - # in the float type. - # - # The thresholds assume that the data are in `wtype` dtype after applying - # the slope and intercept. - both_mn, both_mx = shared_range(w_type, out_dtype) - # Check that nan2zero output value is in range - if nan2zero and not both_mn <= nan_fill <= both_mx: - # Estimated error for (0 - inter) / slope is 2 * eps * abs(inter / - # slope). Assume errors are for working float type. Round for integer - # rounding - est_err = np.round(2 * np.finfo(w_type).eps * abs(inter / slope)) - if (nan_fill < both_mn and abs(nan_fill - both_mn) < est_err) or ( - nan_fill > both_mx and abs(nan_fill - both_mx) < est_err - ): - # nan_fill can be (just) outside clip range - nan_fill = np.clip(nan_fill, both_mn, both_mx) - else: - raise ValueError( - f'nan_fill == {nan_fill}, outside safe int range ' - f'({int(both_mn)}-{int(both_mx)}); ' - 'change scaling or set nan2zero=False?' - ) - # Make sure non-nan output clipped to shared range - post_mn = np.max([post_mn, both_mn]) - post_mx = np.min([post_mx, both_mx]) - in_cast = None if cast_in_dtype == in_dtype else cast_in_dtype - return _write_data( - data, - fileobj, - out_dtype, - order, - in_cast=in_cast, - pre_clips=pre_clips, - inter=inter, - slope=slope, - post_clips=(post_mn, post_mx), - nan_fill=nan_fill if nan2zero else None, - ) - - -def _write_data( - data: np.ndarray, - fileobj: io.IOBase, - out_dtype: np.dtype, - order: ty.Literal['C', 'F'], - in_cast: np.dtype | None = None, - pre_clips: tuple[Scalar | None, Scalar | None] | None = None, - inter: Scalar | np.ndarray = 0.0, - slope: Scalar | np.ndarray = 1.0, - post_clips: tuple[Scalar | None, Scalar | None] | None = None, - nan_fill: Scalar | None = None, -) -> None: - """Write array `data` to `fileobj` as `out_dtype` type, layout `order` - - Does not modify `data` in-place. - - Parameters - ---------- - data : ndarray - fileobj : object - implementing ``obj.write`` - out_dtype : numpy type - Type to which to cast output data just before writing - order : {'F', 'C'} - memory layout of array in fileobj after writing - in_cast : None or numpy type, optional - If not None, initial cast to do on `data` slices before further - processing - pre_clips : None or 2-sequence, optional - If not None, minimum and maximum of input values at which to clip. - inter : scalar or array, optional - Intercept to subtract before writing ``out = data - inter`` - slope : scalar or array, optional - Slope by which to divide before writing ``out2 = out / slope`` - post_clips : None or 2-sequence, optional - If not None, minimum and maximum of scaled values at which to clip. - nan_fill : None or scalar, optional - If not None, values that were NaN in `data` will receive `nan_fill` - in array as output to disk (after scaling). - """ - data = np.squeeze(data) - if data.ndim < 2: # Trick to allow loop over rows for 1D arrays - data = np.atleast_2d(data) - elif order == 'F': - data = data.T - nan_need_copy = (pre_clips, in_cast, inter, slope, post_clips) == (None, None, 0, 1, None) - for dslice in data: # cycle over first dimension to save memory - if pre_clips is not None: - dslice = np.clip(dslice, *pre_clips) - if in_cast is not None: - dslice = dslice.astype(in_cast) - if inter != 0.0: - dslice = dslice - inter - if slope != 1.0: - dslice = dslice / slope - if post_clips is not None: - dslice = np.clip(np.rint(dslice), *post_clips) - if nan_fill is not None: - nans = np.isnan(dslice) - if np.any(nans): - if nan_need_copy: - dslice = dslice.copy() - dslice[nans] = nan_fill - if dslice.dtype != out_dtype: - dslice = dslice.astype(out_dtype) - fileobj.write(dslice.tobytes()) - - -def _dt_min_max( - dtype_like: npt.DTypeLike, mn: Scalar | None = None, mx: Scalar | None = None -) -> tuple[Scalar, Scalar]: - dt = np.dtype(dtype_like) - if dt.kind in 'fc': - dt_mn, dt_mx = (-np.inf, np.inf) - elif dt.kind in 'iu': - info = np.iinfo(dt) - dt_mn, dt_mx = (info.min, info.max) - else: - raise ValueError('unknown dtype') - return dt_mn if mn is None else mn, dt_mx if mx is None else mx - - -_CSIZE2FLOAT: dict[int, type[np.floating]] = { - 8: np.float32, - 16: np.float64, - 24: np.longdouble, - 32: np.longdouble, -} - - -def _matching_float(np_type: npt.DTypeLike) -> type[np.floating]: - """Return floating point type matching `np_type`""" - dtype = np.dtype(np_type) - if dtype.kind not in 'cf': - raise ValueError('Expecting float or complex type as input') - if issubclass(dtype.type, np.floating): - return dtype.type - return _CSIZE2FLOAT[dtype.itemsize] - - -def write_zeros(fileobj: io.IOBase, count: int, block_size: int = 8194) -> None: - """Write `count` zero bytes to `fileobj` - - Parameters - ---------- - fileobj : file-like object - with ``write`` method - count : int - number of bytes to write - block_size : int, optional - largest continuous block to write. - """ - nblocks = int(count // block_size) - rem = count % block_size - blk = b'\x00' * block_size - for bno in range(nblocks): - fileobj.write(blk) - fileobj.write(b'\x00' * rem) - - -def seek_tell(fileobj: io.IOBase, offset: int, write0: bool = False) -> None: - """Seek in `fileobj` or check we're in the right place already - - Parameters - ---------- - fileobj : file-like - object implementing ``seek`` and (if seek raises an OSError) ``tell`` - offset : int - position in file to which to seek - write0 : {False, True}, optional - If True, and standard seek fails, try to write zeros to the file to - reach `offset`. This can be useful when writing bz2 files, that cannot - do write seeks. - """ - try: - fileobj.seek(offset) - except OSError as e: - # This can be a negative seek in write mode for gz file object or any - # seek in write mode for a bz2 file object - pos = fileobj.tell() - if pos == offset: - return - if not write0: - raise OSError(str(e)) - if pos > offset: - raise OSError("Can't write to seek backwards") - fileobj.write(b'\x00' * (offset - pos)) - assert fileobj.tell() == offset - - -def apply_read_scaling( - arr: np.ndarray, - slope: Scalar | None = None, - inter: Scalar | None = None, -) -> np.ndarray: - """Apply scaling in `slope` and `inter` to array `arr` - - This is for loading the array from a file (as opposed to the reverse - scaling when saving an array to file) - - Return data will be ``arr * slope + inter``. The trick is that we have to - find a good precision to use for applying the scaling. The heuristic is - that the data is always upcast to the higher of the types from `arr, - `slope`, `inter` if `slope` and / or `inter` are not default values. If the - dtype of `arr` is an integer, then we assume the data more or less fills - the integer range, and upcast to a type such that the min, max of - ``arr.dtype`` * scale + inter, will be finite. - - Parameters - ---------- - arr : array-like - slope : None or float, optional - slope value to apply to `arr` (``arr * slope + inter``). None - corresponds to a value of 1.0 - inter : None or float, optional - intercept value to apply to `arr` (``arr * slope + inter``). None - corresponds to a value of 0.0 - - Returns - ------- - ret : array - array with scaling applied. Maybe upcast in order to give room for the - scaling. If scaling is default (1, 0), then `ret` may be `arr` ``ret is - arr``. - """ - if slope is None: - slope = 1.0 - if inter is None: - inter = 0.0 - if (slope, inter) == (1, 0): - return arr - shape = arr.shape - # Force float / float upcasting by promoting to arrays - slope1d, inter1d = (np.atleast_1d(v) for v in (slope, inter)) - arr = np.atleast_1d(arr) - if arr.dtype.kind in 'iu': - # int to float; get enough precision to avoid infs - # Find floating point type for which scaling does not overflow, - # starting at given type - default = slope1d.dtype.type if slope1d.dtype.kind == 'f' else np.float64 - ftype = int_scinter_ftype(arr.dtype, slope1d, inter1d, default) - slope1d = slope1d.astype(ftype) - inter1d = inter1d.astype(ftype) - if slope1d != 1.0: - arr = arr * slope1d - if inter1d != 0.0: - arr = arr + inter1d - return arr.reshape(shape) - - -def working_type( - in_type: npt.DTypeLike, - slope: npt.ArrayLike = 1.0, - inter: npt.ArrayLike = 0.0, -) -> type[np.number]: - """Return array type from applying `slope`, `inter` to array of `in_type` - - Numpy type that results from an array of type `in_type` being combined with - `slope` and `inter`. It returns something like the dtype type of - ``((np.zeros((2,), dtype=in_type) - inter) / slope)``, but ignoring the - actual values of `slope` and `inter`. - - Note that you would not necessarily get the same type by applying slope and - inter the other way round. Also, you'll see that the order in which slope - and inter are applied is the opposite of the order in which they are - passed. - - Parameters - ---------- - in_type : numpy type specifier - Numpy type of input array. Any valid input for ``np.dtype()`` - slope : scalar, optional - slope to apply to array. If 1.0 (default), ignore this value and its - type. - inter : scalar, optional - intercept to apply to array. If 0.0 (default), ignore this value and - its type. - - Returns - ------- - wtype: numpy type - Numpy type resulting from applying `inter` and `slope` to array of type - `in_type`. - """ - val = np.array([1], dtype=in_type) - # Don't use real values to avoid overflows. Promote to 1D to avoid scalar - # casting rules. Don't use ones_like, zeros_like because of a bug in numpy - # <= 1.5.1 in converting complex192 / complex256 scalars. - if inter != 0: - val = val + np.array([0], dtype=np.array(inter).dtype) - if slope != 1: - val = val / np.array([1], dtype=np.array(slope).dtype) - return val.dtype.type - - -def int_scinter_ftype( - ifmt: np.dtype[np.integer] | type[np.integer], - slope: npt.ArrayLike = 1.0, - inter: npt.ArrayLike = 0.0, - default: type[np.floating] = np.float32, -) -> type[np.floating]: - """float type containing int type `ifmt` * `slope` + `inter` - - Return float type that can represent the max and the min of the `ifmt` type - after multiplication with `slope` and addition of `inter` with something - like ``np.array([imin, imax], dtype=ifmt) * slope + inter``. - - Note that ``slope`` and ``inter`` get promoted to 1D arrays for this - purpose to avoid the numpy scalar casting rules, which prevent scalars - upcasting the array. - - Parameters - ---------- - ifmt : object - numpy integer type (e.g. np.int32) - slope : float, optional - slope, default 1.0 - inter : float, optional - intercept, default 0.0 - default_out : object, optional - numpy floating point type, default is ``np.float32`` - - Returns - ------- - ftype : object - numpy floating point type - - Examples - -------- - >>> int_scinter_ftype(np.int8, 1.0, 0.0) == np.float32 - True - >>> int_scinter_ftype(np.int8, 1e38, 0.0) == np.float64 - True - - Notes - ----- - It is difficult to make floats overflow with just addition because the - deltas are so large at the extremes of floating point. For example:: - - >>> arr = np.array([np.finfo(np.float32).max], dtype=np.float32) - >>> res = arr + np.iinfo(np.int16).max - >>> arr == res - array([ True]) - """ - ii = np.iinfo(ifmt) - tst_arr = np.array([ii.min, ii.max], dtype=ifmt) - try: - return _ftype4scaled_finite(tst_arr, slope, inter, 'read', default) - except ValueError: - raise ValueError('Overflow using highest floating point type') - - -def best_write_scale_ftype( - arr: np.ndarray, - slope: npt.ArrayLike = 1.0, - inter: npt.ArrayLike = 0.0, - default: type[np.number] = np.float32, -) -> type[np.floating]: - """Smallest float type to contain range of ``arr`` after scaling - - Scaling that will be applied to ``arr`` is ``(arr - inter) / slope``. - - Note that ``slope`` and ``inter`` get promoted to 1D arrays for this - purpose to avoid the numpy scalar casting rules, which prevent scalars - upcasting the array. - - Parameters - ---------- - arr : array-like - array that will be scaled - slope : array-like, optional - scalar such that output array will be ``(arr - inter) / slope``. - inter : array-like, optional - scalar such that output array will be ``(arr - inter) / slope`` - default : numpy type, optional - minimum float type to return - - Returns - ------- - ftype : numpy type - Best floating point type for scaling. If no floating point type - prevents overflow, return the top floating point type. If the input - array ``arr`` already contains inf values, return the greater of the - input type and the default type. - - Examples - -------- - >>> arr = np.array([0, 1, 2], dtype=np.int16) - >>> best_write_scale_ftype(arr, 1, 0) is np.float32 - True - - Specify higher default return value - - >>> best_write_scale_ftype(arr, 1, 0, default=np.float64) is np.float64 - True - - Even large values that don't overflow don't change output - - >>> arr = np.array([0, np.finfo(np.float32).max], dtype=np.float32) - >>> best_write_scale_ftype(arr, 1, 0) is np.float32 - True - - Scaling > 1 reduces output values, so no upcast needed - - >>> best_write_scale_ftype(arr, np.float32(2), 0) is np.float32 - True - - Scaling < 1 increases values, so upcast may be needed (and is here) - - >>> best_write_scale_ftype(arr, np.float32(0.5), 0) is np.float64 - True - """ - default = better_float_of(arr.dtype.type, default) - if not np.all(np.isfinite(arr)): - return default - try: - return _ftype4scaled_finite(arr, slope, inter, 'write', default) - except ValueError: - return OK_FLOATS[-1] - - -def better_float_of( - first: npt.DTypeLike, - second: npt.DTypeLike, - default: type[np.floating] = np.float32, -) -> type[np.floating]: - """Return more capable float type of `first` and `second` - - Return `default` if neither of `first` or `second` is a float - - Parameters - ---------- - first : numpy type specifier - Any valid input to `np.dtype()`` - second : numpy type specifier - Any valid input to `np.dtype()`` - default : numpy type specifier, optional - Any valid input to `np.dtype()`` - - Returns - ------- - better_type : numpy type - More capable of `first` or `second` if both are floats; if only one is - a float return that, otherwise return `default`. - - Examples - -------- - >>> better_float_of(np.float32, np.float64) is np.float64 - True - >>> better_float_of(np.float32, 'i4') is np.float32 - True - >>> better_float_of('i2', 'u4') is np.float32 - True - >>> better_float_of('i2', 'u4', np.float64) is np.float64 - True - """ - first = np.dtype(first) - second = np.dtype(second) - default = np.dtype(default).type - if issubclass(first.type, np.floating): - if issubclass(second.type, np.floating) and first.itemsize < second.itemsize: - return second.type - return first.type - if issubclass(second.type, np.floating): - return second.type - return default - - -def _ftype4scaled_finite( - tst_arr: np.ndarray, - slope: npt.ArrayLike, - inter: npt.ArrayLike, - direction: ty.Literal['read', 'write'] = 'read', - default: type[np.floating] = np.float32, -) -> type[np.floating]: - """Smallest float type for scaling of `tst_arr` that does not overflow""" - assert direction in ('read', 'write') - if default not in OK_FLOATS and default is np.longdouble: - # Omitted longdouble - return default - def_ind = OK_FLOATS.index(default) - # promote to arrays to avoid numpy scalar casting rules - tst_arr = np.atleast_1d(tst_arr) - slope = np.atleast_1d(slope) - inter = np.atleast_1d(inter) - for ftype in OK_FLOATS[def_ind:]: - tst_trans = tst_arr.copy() - slope = slope.astype(ftype) - inter = inter.astype(ftype) - try: - with warnings.catch_warnings(): - # Error on overflows to short circuit the logic - warnings.filterwarnings('error', '.*overflow.*', RuntimeWarning) - if direction == 'read': # as in reading of image from disk - if slope != 1.0: - tst_trans = tst_trans * slope - if inter != 0.0: - tst_trans = tst_trans + inter - elif direction == 'write': - if inter != 0.0: - tst_trans = tst_trans - inter - if slope != 1.0: - tst_trans = tst_trans / slope - # Double-check that result is finite - if np.all(np.isfinite(tst_trans)): - return ftype - except RuntimeWarning: - pass - raise ValueError('Overflow using highest floating point type') - - -@ty.overload -def finite_range( - arr: npt.ArrayLike, check_nan: ty.Literal[False] = False -) -> tuple[Scalar, Scalar]: ... - - -@ty.overload -def finite_range( - arr: npt.ArrayLike, check_nan: ty.Literal[True] -) -> tuple[Scalar, Scalar, bool]: ... - - -def finite_range( - arr: npt.ArrayLike, - check_nan: bool = False, -) -> tuple[Scalar, Scalar, bool] | tuple[Scalar, Scalar]: - """Get range (min, max) or range and flag (min, max, has_nan) from `arr` - - Parameters - ---------- - arr : array-like - check_nan : {False, True}, optional - Whether to return third output, a bool signaling whether there are NaN - values in `arr` - - Returns - ------- - mn : scalar - minimum of values in (flattened) array - mx : scalar - maximum of values in (flattened) array - has_nan : bool - Returned if `check_nan` is True. `has_nan` is True if there are one or - more NaN values in `arr` - - Examples - -------- - >>> a = np.array([[-1, 0, 1],[np.inf, np.nan, -np.inf]]) - >>> finite_range(a) - (-1.0, 1.0) - >>> a = np.array([[-1, 0, 1],[np.inf, np.nan, -np.inf]]) - >>> finite_range(a, check_nan=True) - (-1.0, 1.0, True) - >>> a = np.array([[np.nan],[np.nan]]) - >>> finite_range(a) == (np.inf, -np.inf) - True - >>> a = np.array([[-3, 0, 1],[2,-1,4]], dtype=int) - >>> finite_range(a) - (-3, 4) - >>> a = np.array([[1, 0, 1],[2,3,4]], dtype=np.uint) - >>> finite_range(a) - (0, 4) - >>> a = a + 1j - >>> finite_range(a) - (1j, (4+1j)) - >>> a = np.zeros((2,), dtype=[('f1', 'i2')]) - >>> finite_range(a) - Traceback (most recent call last): - ... - TypeError: Can only handle numeric types - """ - arr = np.asarray(arr) - if arr.size == 0: - if check_nan: - return (np.inf, -np.inf, False) - return (np.inf, -np.inf) - # Resort array to slowest->fastest memory change indices - stride_order = np.argsort(arr.strides)[::-1] - sarr = arr.transpose(stride_order) - kind = sarr.dtype.kind - if kind in 'iu': - if check_nan: - return np.min(sarr), np.max(sarr), False - return np.min(sarr), np.max(sarr) - if kind not in 'cf': - raise TypeError('Can only handle numeric types') - # Deal with 1D arrays in loop below - sarr = np.atleast_2d(sarr) - # Loop to avoid big temporary arrays - has_nan = False - n_slices = sarr.shape[0] - maxes = np.zeros(n_slices, dtype=sarr.dtype) - np.inf - mins = np.zeros(n_slices, dtype=sarr.dtype) + np.inf - for s in range(n_slices): - this_slice = sarr[s] # view - if not has_nan: - maxes[s] = np.max(this_slice) - # May have a non-nan non-inf max before we trip on min. If so, - # record so we don't recalculate - max_good = False - if np.isnan(maxes[s]): - has_nan = True - elif maxes[s] != np.inf: - max_good = True - mins[s] = np.min(this_slice) - if mins[s] != -np.inf: - # Only case where we escape the default np.isfinite - # algorithm - continue - tmp = this_slice[np.isfinite(this_slice)] - if tmp.size == 0: # No finite values - # Reset max, min in case set in tests above - maxes[s] = -np.inf - mins[s] = np.inf - continue - if not max_good: - maxes[s] = np.max(tmp) - mins[s] = np.min(tmp) - if check_nan: - return np.nanmin(mins), np.nanmax(maxes), has_nan - return np.nanmin(mins), np.nanmax(maxes) - - -def shape_zoom_affine( - shape: ty.Sequence[int] | np.ndarray, - zooms: ty.Sequence[float] | np.ndarray, - x_flip: bool = True, -) -> np.ndarray: - """Get affine implied by given shape and zooms - - We get the translations from the center of the image (implied by - `shape`). - - Parameters - ---------- - shape : (N,) array-like - shape of image data. ``N`` is the number of dimensions - zooms : (N,) array-like - zooms (voxel sizes) of the image - x_flip : {True, False} - whether to flip the X row of the affine. Corresponds to - radiological storage on disk. - - Returns - ------- - aff : (4,4) array - affine giving correspondence of voxel coordinates to mm - coordinates, taking the center of the image as origin - - Examples - -------- - >>> shape = (3, 5, 7) - >>> zooms = (3, 2, 1) - >>> shape_zoom_affine((3, 5, 7), (3, 2, 1)) - array([[-3., 0., 0., 3.], - [ 0., 2., 0., -4.], - [ 0., 0., 1., -3.], - [ 0., 0., 0., 1.]]) - >>> shape_zoom_affine((3, 5, 7), (3, 2, 1), False) - array([[ 3., 0., 0., -3.], - [ 0., 2., 0., -4.], - [ 0., 0., 1., -3.], - [ 0., 0., 0., 1.]]) - """ - shape = np.asarray(shape) - zooms = np.array(zooms) # copy because of flip below - ndims = len(shape) - if ndims != len(zooms): - raise ValueError('Should be same length of zooms and shape') - if ndims >= 3: - shape = shape[:3] - zooms = zooms[:3] - else: - full_shape = np.ones((3,)) - full_zooms = np.ones((3,)) - full_shape[:ndims] = shape[:] - full_zooms[:ndims] = zooms[:] - shape = full_shape - zooms = full_zooms - if x_flip: - zooms[0] *= -1 - # Get translations from center of image - origin = (shape - 1) / 2.0 - aff = np.eye(4) - aff[:3, :3] = np.diag(zooms) - aff[:3, -1] = -origin * zooms - return aff - - -def rec2dict(rec: np.ndarray) -> dict[str, np.generic | np.ndarray]: - """Convert recarray to dictionary - - Also converts scalar values to scalars - - Parameters - ---------- - rec : ndarray - structured ndarray - - Returns - ------- - dct : dict - dict with key, value pairs as for `rec` - - Examples - -------- - >>> r = np.zeros((), dtype = [('x', 'i4'), ('s', 'S10')]) - >>> d = rec2dict(r) - >>> d == {'x': 0, 's': b''} - True - """ - dct = {} - for key in rec.dtype.fields: - val = rec[key] - try: - val = val.item() - except ValueError: - pass - dct[key] = val - return dct - - -def fname_ext_ul_case(fname: str) -> str: - """`fname` with ext changed to upper / lower case if file exists - - Check for existence of `fname`. If it does exist, return unmodified. If - it doesn't, check for existence of `fname` with case changed from lower to - upper, or upper to lower. Return this modified `fname` if it exists. - Otherwise return `fname` unmodified - - Parameters - ---------- - fname : str - filename. - - Returns - ------- - mod_fname : str - filename, maybe with extension of opposite case - """ - if exists(fname): - return fname - froot, ext = splitext(fname) - if ext == ext.lower(): - mod_fname = froot + ext.upper() - if exists(mod_fname): - return mod_fname - elif ext == ext.upper(): - mod_fname = froot + ext.lower() - if exists(mod_fname): - return mod_fname - return fname diff --git a/nibabel/wrapstruct.py b/nibabel/wrapstruct.py deleted file mode 100644 index 5ffe04bc78..0000000000 --- a/nibabel/wrapstruct.py +++ /dev/null @@ -1,543 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Class to wrap numpy structured array - -============ - wrapstruct -============ - -The :class:`WrapStruct` class is a wrapper around a numpy structured array -type. - -It implements: - -* Mappingness from the underlying structured array fields -* ``from_fileobj``, ``write_to`` methods to read and write data to fileobj -* A mechanism for setting checks and fixes to the data on object creation -* Endianness guessing, and on-the-fly swapping - -The :class:`LabeledWrapStruct` subclass adds: - -* A pretty printing mechanism whereby field values can be displayed as - corresponding strings (see :meth:`LabeledWrapStruct.get_value_label` and - :meth:`LabeledWrapStruct.__str_`) - -Mappingness ------------ - -You can access and set fields of the contained structarr using standard -__getitem__ / __setitem__ syntax: - - wrapped['field'] = 10 - -Wrapped structures also implement general mappingness: - - wrapped.keys() - wrapped.items() - wrapped.values() - -Properties:: - - .endianness (read only) - .binaryblock (read only) - .structarr (read only) - -Methods:: - - .as_byteswapped(endianness) - .check_fix() - .__str__ - .__eq__ - .__ne__ - .get_value_label(name) - -Class methods:: - - .diagnose_binaryblock - .as_byteswapped(endianness) - .write_to(fileobj) - .from_fileobj(fileobj) - .default_structarr() - return default structured array - .guessed_endian(structarr) - return guessed endian code from this structarr - -Class variables: - template_dtype - native endian version of dtype for contained structarr - -Consistency checks ------------------- - -We have a file, and we would like information as to whether there are any -problems with the binary data in this file, and whether they are fixable. -``WrapStruct`` can hold checks for internal consistency of the contained data:: - - wrapped = WrapStruct.from_fileobj(open('myfile.bin'), check=False) - dx_result = WrapStruct.diagnose_binaryblock(wrapped.binaryblock) - -This will run all known checks, with no fixes, returning a string with -diagnostic output. See below for the ``check=False`` flag. - -In creating a ``WrapStruct`` object, we often want to check the consistency of -the contained data. The checks can test for problems of various levels of -severity. If the problem is severe enough, it should raise an Error. So, with -data that is consistent - no error:: - - wrapped = WrapStruct.from_fileobj(good_fileobj) - -whereas:: - - wrapped = WrapStruct.from_fileobj(bad_fileobj) - -would raise some error, with output to logging (see below). - -If we want the created object, come what may:: - - hdr = WrapStruct.from_fileobj(bad_fileobj, check=False) - -We set the error level (the level of problem that the ``check=True`` -versions will accept as OK) from global defaults:: - - import nibabel as nib - nib.imageglobals.error_level = 30 - -The same for logging:: - - nib.imageglobals.logger = logger -""" - -from __future__ import annotations - -import numpy as np - -from . import imageglobals as imageglobals -from .batteryrunners import BatteryRunner -from .volumeutils import Recoder, endian_codes, native_code, pretty_mapping, swapped_code - - -class WrapStructError(Exception): - pass - - -class WrapStruct: - # placeholder datatype - template_dtype = np.dtype([('integer', 'i2')]) - - def __init__(self, binaryblock=None, endianness=None, check=True): - """Initialize WrapStruct from binary data block - - Parameters - ---------- - binaryblock : {None, string} optional - binary block to set into object. By default, None, in - which case we insert the default empty block - endianness : {None, '<','>', other endian code} string, optional - endianness of the binaryblock. If None, guess endianness - from the data. - check : bool, optional - Whether to check content of binary data in initialization. - Default is True. - - Examples - -------- - >>> wstr1 = WrapStruct() # a default structure - >>> wstr1.endianness == native_code - True - >>> wstr1['integer'] - array(0, dtype=int16) - >>> wstr1['integer'] = 1 - >>> wstr1['integer'] - array(1, dtype=int16) - """ - if binaryblock is None: - self._structarr = self.__class__.default_structarr(endianness) - return - # check size - if len(binaryblock) != self.template_dtype.itemsize: - raise WrapStructError('Binary block is wrong size') - wstr = np.ndarray(shape=(), dtype=self.template_dtype, buffer=binaryblock) - if endianness is None: - endianness = self.__class__.guessed_endian(wstr) - else: - endianness = endian_codes[endianness] - if endianness != native_code: - dt = self.template_dtype.newbyteorder(endianness) - wstr = np.ndarray(shape=(), dtype=dt, buffer=binaryblock) - self._structarr = wstr.copy() - if check: - self.check_fix() - - @classmethod - def from_fileobj(klass, fileobj, endianness=None, check=True): - """Return read structure with given or guessed endiancode - - Parameters - ---------- - fileobj : file-like object - Needs to implement ``read`` method - endianness : None or endian code, optional - Code specifying endianness of read data - - Returns - ------- - wstr : WrapStruct object - WrapStruct object initialized from data in fileobj - """ - raw_str = fileobj.read(klass.template_dtype.itemsize) - return klass(raw_str, endianness, check) - - @property - def binaryblock(self): - """binary block of data as string - - Returns - ------- - binaryblock : string - string giving binary data block - - Examples - -------- - >>> # Make default empty structure - >>> wstr = WrapStruct() - >>> len(wstr.binaryblock) - 2 - """ - return self._structarr.tobytes() - - def write_to(self, fileobj): - """Write structure to fileobj - - Write starts at fileobj current file position. - - Parameters - ---------- - fileobj : file-like object - Should implement ``write`` method - - Returns - ------- - None - - Examples - -------- - >>> wstr = WrapStruct() - >>> from io import BytesIO - >>> str_io = BytesIO() - >>> wstr.write_to(str_io) - >>> wstr.binaryblock == str_io.getvalue() - True - """ - fileobj.write(self.binaryblock) - - @property - def endianness(self): - """endian code of binary data - - The endianness code gives the current byte order - interpretation of the binary data. - - Examples - -------- - >>> wstr = WrapStruct() - >>> code = wstr.endianness - >>> code == native_code - True - - Notes - ----- - Endianness gives endian interpretation of binary data. It is - read only because the only common use case is to set the - endianness on initialization, or occasionally byteswapping the - data - but this is done via the as_byteswapped method - """ - if self._structarr.dtype.isnative: - return native_code - return swapped_code - - def copy(self): - """Return copy of structure - - >>> wstr = WrapStruct() - >>> wstr['integer'] = 3 - >>> wstr2 = wstr.copy() - >>> wstr2 is wstr - False - >>> wstr2['integer'] - array(3, dtype=int16) - """ - return self.__class__(self.binaryblock, self.endianness, check=False) - - def __eq__(self, other): - """equality between two structures defined by binaryblock - - Examples - -------- - >>> wstr = WrapStruct() - >>> wstr2 = WrapStruct() - >>> wstr == wstr2 - True - >>> wstr3 = WrapStruct(endianness=swapped_code) - >>> wstr == wstr3 - True - """ - this_end = self.endianness - this_bb = self.binaryblock - try: - other_end = other.endianness - other_bb = other.binaryblock - except AttributeError: - return False - if this_end == other_end: - return this_bb == other_bb - other_bb = other._structarr.byteswap().tobytes() - return this_bb == other_bb - - def __ne__(self, other): - return not self == other - - def __getitem__(self, item): - """Return values from structure data - - Examples - -------- - >>> wstr = WrapStruct() - >>> wstr['integer'] == 0 - True - """ - return self._structarr[item] - - def __setitem__(self, item, value): - """Set values in structured data - - Examples - -------- - >>> wstr = WrapStruct() - >>> wstr['integer'] = 3 - >>> wstr['integer'] - array(3, dtype=int16) - """ - self._structarr[item] = value - - def __iter__(self): - return iter(self.keys()) - - def keys(self): - """Return keys from structured data""" - return list(self.template_dtype.names) - - def values(self): - """Return values from structured data""" - data = self._structarr - return [data[key] for key in self.template_dtype.names] - - def items(self): - """Return items from structured data""" - return zip(self.keys(), self.values()) - - def get(self, k, d=None): - """Return value for the key k if present or d otherwise""" - return self._structarr[k] if k in self.keys() else d - - def check_fix(self, logger=None, error_level=None): - """Check structured data with checks - - Parameters - ---------- - logger : None or logging.Logger - error_level : None or int - Level of error severity at which to raise error. Any error of - severity >= `error_level` will cause an exception. - """ - if logger is None: - logger = imageglobals.logger - if error_level is None: - error_level = imageglobals.error_level - battrun = BatteryRunner(self.__class__._get_checks()) - self, reports = battrun.check_fix(self) - for report in reports: - report.log_raise(logger, error_level) - - @classmethod - def diagnose_binaryblock(klass, binaryblock, endianness=None): - """Run checks over binary data, return string""" - wstr = klass(binaryblock, endianness=endianness, check=False) - battrun = BatteryRunner(klass._get_checks()) - reports = battrun.check_only(wstr) - return '\n'.join([report.message for report in reports if report.message]) - - @classmethod - def guessed_endian(self, mapping): - """Guess intended endianness from mapping-like ``mapping`` - - Parameters - ---------- - wstr : mapping-like - Something implementing a mapping. We will guess the endianness - from looking at the field values - - Returns - ------- - endianness : {'<', '>'} - Guessed endianness of binary data in ``wstr`` - """ - raise NotImplementedError - - @classmethod - def default_structarr(klass, endianness=None): - """Return structured array for default structure with given endianness""" - dt = klass.template_dtype - if endianness is not None: - endianness = endian_codes[endianness] - dt = dt.newbyteorder(endianness) - return np.zeros((), dtype=dt) - - @property - def structarr(self): - """Structured data, with data fields - - Examples - -------- - >>> wstr1 = WrapStruct() # with default data - >>> an_int = wstr1.structarr['integer'] - >>> wstr1.structarr = None - Traceback (most recent call last): - ... - AttributeError: ... - """ - return self._structarr - - def __str__(self): - """Return string representation for printing""" - summary = f"{self.__class__} object, endian='{self.endianness}'" - return '\n'.join([summary, pretty_mapping(self)]) - - def as_byteswapped(self, endianness=None): - """return new byteswapped object with given ``endianness`` - - Guaranteed to make a copy even if endianness is the same as - the current endianness. - - Parameters - ---------- - endianness : None or string, optional - endian code to which to swap. None means swap from current - endianness, and is the default - - Returns - ------- - wstr : ``WrapStruct`` - ``WrapStruct`` object with given endianness - - Examples - -------- - >>> wstr = WrapStruct() - >>> wstr.endianness == native_code - True - >>> bs_wstr = wstr.as_byteswapped() - >>> bs_wstr.endianness == swapped_code - True - >>> bs_wstr = wstr.as_byteswapped(swapped_code) - >>> bs_wstr.endianness == swapped_code - True - >>> bs_wstr is wstr - False - >>> bs_wstr == wstr - True - - If you write to the resulting byteswapped data, it does not - change the original. - - >>> bs_wstr['integer'] = 3 - >>> bs_wstr == wstr - False - - If you swap to the same endianness, it returns a copy - - >>> nbs_wstr = wstr.as_byteswapped(native_code) - >>> nbs_wstr.endianness == native_code - True - >>> nbs_wstr is wstr - False - """ - current = self.endianness - if endianness is None: - if current == native_code: - endianness = swapped_code - else: - endianness = native_code - else: - endianness = endian_codes[endianness] - if endianness == current: - return self.copy() - wstr_data = self._structarr.byteswap() - return self.__class__(wstr_data.tobytes(), endianness, check=False) - - @classmethod - def _get_checks(klass): - """Return sequence of check functions for this class""" - return () - - -class LabeledWrapStruct(WrapStruct): - """A WrapStruct with some fields having value labels for printing etc""" - - _field_recoders: dict[str, Recoder] = {} # for recoding values for str - - def get_value_label(self, fieldname): - """Returns label for coded field - - A coded field is an int field containing codes that stand for - discrete values that also have string labels. - - Parameters - ---------- - fieldname : str - name of header field to get label for - - Returns - ------- - label : str - label for code value in header field `fieldname` - - Raises - ------ - ValueError - if field is not coded. - - Examples - -------- - >>> from nibabel.volumeutils import Recoder - >>> recoder = Recoder(((1, 'one'), (2, 'two')), ('code', 'label')) - >>> class C(LabeledWrapStruct): - ... template_dtype = np.dtype([('datatype', 'i2')]) - ... _field_recoders = dict(datatype = recoder) - >>> hdr = C() - >>> hdr.get_value_label('datatype') - '' - >>> hdr['datatype'] = 2 - >>> hdr.get_value_label('datatype') - 'two' - """ - if fieldname not in self._field_recoders: - raise ValueError(f'{fieldname} not a coded field') - code = int(self._structarr[fieldname]) - try: - return self._field_recoders[fieldname].label[code] - except KeyError: - return f'' - - def __str__(self): - """Return string representation for printing""" - summary = f"{self.__class__} object, endian='{self.endianness}'" - - def _getter(obj, key): - try: - return obj.get_value_label(key) - except ValueError: - return obj[key] - - return '\n'.join([summary, pretty_mapping(self, _getter)]) diff --git a/nibabel/xmlutils.py b/nibabel/xmlutils.py deleted file mode 100644 index 12fd30f225..0000000000 --- a/nibabel/xmlutils.py +++ /dev/null @@ -1,118 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Thin layer around xml.etree.ElementTree, to abstract nibabel xml support""" - -from io import BytesIO -from xml.etree.ElementTree import Element, SubElement, tostring # noqa: F401 -from xml.parsers.expat import ParserCreate - -from .filebasedimages import FileBasedHeader - - -class XmlSerializable: - """Basic interface for serializing an object to XML""" - - def _to_xml_element(self) -> Element: - """Output should be a xml.etree.ElementTree.Element""" - raise NotImplementedError - - def to_xml(self, enc='utf-8', **kwargs) -> bytes: - r"""Generate an XML bytestring with a given encoding. - - Parameters - ---------- - enc : :class:`string` - Encoding to use for the generated bytestring. Default: 'utf-8' - \*\*kwargs : :class:`dict` - Additional keyword arguments to :func:`xml.etree.ElementTree.tostring`. - """ - ele = self._to_xml_element() - return tostring(ele, enc, **kwargs) - - -class XmlBasedHeader(FileBasedHeader, XmlSerializable): - """Basic wrapper around FileBasedHeader and XmlSerializable.""" - - -class XmlParser: - """Base class for defining how to parse xml-based image snippets. - - Image-specific parsers should define: - StartElementHandler - EndElementHandler - CharacterDataHandler - """ - - HANDLER_NAMES = ['StartElementHandler', 'EndElementHandler', 'CharacterDataHandler'] - - def __init__(self, encoding='utf-8', buffer_size=35000000, verbose=0): - """ - Parameters - ---------- - encoding : str - string containing xml document - - buffer_size: None or int, optional - size of read buffer. None uses default buffer_size - from xml.parsers.expat. - - verbose : int, optional - amount of output during parsing (0=silent, by default). - """ - self.encoding = encoding - self.buffer_size = buffer_size - self.verbose = verbose - self.fname = None # set on calls to parse - - def _create_parser(self): - """Internal function that allows subclasses to mess - with the underlying parser, if desired.""" - - parser = ParserCreate(encoding=self.encoding) # from xml package - parser.buffer_text = True - if self.buffer_size is not None: - parser.buffer_size = self.buffer_size - return parser - - def parse(self, string=None, fname=None, fptr=None): - """ - Parameters - ---------- - string : bytes - string (as a bytes object) containing xml document - - fname : str - file name of an xml document. - - fptr : file pointer - open file pointer to an xml documents - """ - if int(string is not None) + int(fptr is not None) + int(fname is not None) != 1: - raise ValueError('Exactly one of fptr, fname, string must be specified.') - - if string is not None: - fptr = BytesIO(string) - elif fname is not None: - fptr = open(fname) - - # store the name of the xml file in case it is needed during parsing - self.fname = getattr(fptr, 'name', None) - parser = self._create_parser() - for name in self.HANDLER_NAMES: - setattr(parser, name, getattr(self, name)) - parser.ParseFile(fptr) - - def StartElementHandler(self, name, attrs): - raise NotImplementedError - - def EndElementHandler(self, name): - raise NotImplementedError - - def CharacterDataHandler(self, data): - raise NotImplementedError diff --git a/nibabel_images.html b/nibabel_images.html new file mode 100644 index 0000000000..4ccd5036a7 --- /dev/null +++ b/nibabel_images.html @@ -0,0 +1,529 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    Nibabel images

    +

    A nibabel image object is the association of three things:

    +
      +
    • an N-D array containing the image data;

    • +
    • a (4, 4) affine matrix mapping array coordinates to coordinates in some +RAS+ world coordinate space (Coordinate systems and affines);

    • +
    • image metadata in the form of a header.

    • +
    +
    +

    The image object

    +

    First we load some libraries we are going to need for the examples:

    +
    >>> import os
    +>>> import numpy as np
    +
    +
    +

    There is an example image in the nibabel distribution.

    +
    >>> from nibabel.testing import data_path
    +>>> example_file = os.path.join(data_path, 'example4d.nii.gz')
    +
    +
    +

    We load the file to create a nibabel image object:

    +
    >>> import nibabel as nib
    +>>> img = nib.load(example_file)
    +
    +
    +

    The object img is an instance of a nibabel image. In fact it is an +instance of a nibabel nibabel.nifti1.Nifti1Image:

    +
    >>> img
    +<nibabel.nifti1.Nifti1Image object at ...>
    +
    +
    +

    As with any Python object, you can inspect img to see what attributes it +has. We recommend using IPython tab completion for this, but here are some +examples of interesting attributes:

    +

    dataobj is the object pointing to the image array data:

    +
    >>> img.dataobj
    +<nibabel.arrayproxy.ArrayProxy object at ...>
    +
    +
    +

    See Array proxies and proxy images for more on why this is an array proxy.

    +

    affine is the affine array relating array coordinates from the image data +array to coordinates in some RAS+ world coordinate system +(Coordinate systems and affines):

    +
    >>> # Set numpy to print only 2 decimal digits for neatness
    +>>> np.set_printoptions(precision=2, suppress=True)
    +
    +
    +
    >>> img.affine
    +array([[ -2.  ,   0.  ,   0.  , 117.86],
    +       [ -0.  ,   1.97,  -0.36, -35.72],
    +       [  0.  ,   0.32,   2.17,  -7.25],
    +       [  0.  ,   0.  ,   0.  ,   1.  ]])
    +
    +
    +

    header contains the metadata for this image. In this case it is +specifically NIfTI metadata:

    +
    >>> img.header
    +<nibabel.nifti1.Nifti1Header object at ...>
    +
    +
    +
    +
    +

    The image header

    +

    The header of an image contains the image metadata. The information in the +header will differ between different image formats. For example, the header +information for a NIfTI1 format file differs from the header information for a +MINC format file.

    +

    Our image is a NIfTI1 format image, and it therefore has a NIfTI1 format +header:

    +
    >>> header = img.header
    +>>> print(header)                           
    +<class 'nibabel.nifti1.Nifti1Header'> object, endian='<'
    +sizeof_hdr      : 348
    +data_type       : b''
    +db_name         : b''
    +extents         : 0
    +session_error   : 0
    +regular         : b'r'
    +dim_info        : 57
    +dim             : [  4 128  96  24   2   1   1   1]
    +intent_p1       : 0.0
    +intent_p2       : 0.0
    +intent_p3       : 0.0
    +intent_code     : none
    +datatype        : int16
    +bitpix          : 16
    +slice_start     : 0
    +pixdim          : [   -1.      2.      2.      2.2  2000.      1.      1.      1. ]
    +vox_offset      : 0.0
    +scl_slope       : nan
    +scl_inter       : nan
    +slice_end       : 23
    +slice_code      : unknown
    +xyzt_units      : 10
    +cal_max         : 1162.0
    +cal_min         : 0.0
    +slice_duration  : 0.0
    +toffset         : 0.0
    +glmax           : 0
    +glmin           : 0
    +descrip         : b'FSL3.3\x00 v2.25 NIfTI-1 Single file format'
    +aux_file        : b''
    +qform_code      : scanner
    +sform_code      : scanner
    +quatern_b       : -1.94510681403e-26
    +quatern_c       : -0.996708512306
    +quatern_d       : -0.081068739295
    +qoffset_x       : 117.855102539
    +qoffset_y       : -35.7229423523
    +qoffset_z       : -7.24879837036
    +srow_x          : [  -2.      0.      0.    117.86]
    +srow_y          : [ -0.     1.97  -0.36 -35.72]
    +srow_z          : [ 0.    0.32  2.17 -7.25]
    +intent_name     : b''
    +magic           : b'n+1'
    +
    +
    +

    The header of any image will normally have the following methods:

    +
      +
    • get_data_shape() to get the output shape of the image data array:

      +
      >>> print(header.get_data_shape())
      +(128, 96, 24, 2)
      +
      +
      +
    • +
    • get_data_dtype() to get the numpy data type in which the image data is +stored (or will be stored if you save the image):

      +
      >>> print(header.get_data_dtype())
      +int16
      +
      +
      +
    • +
    • get_zooms() to get the voxel sizes in millimeters:

      +
      >>> print(header.get_zooms())
      +(2.0, 2.0, 2.19999..., 2000.0)
      +
      +
      +

      The last value of header.get_zooms() is the time between scans in +milliseconds; this is the equivalent of voxel size on the time axis.

      +
    • +
    +
    +
    +

    The image data array

    +

    The image data array is a little more complicated, because the image array can +be stored in the image object as a numpy array or stored on disk for you to +access later via an array proxy.

    +
    +

    Array proxies and proxy images

    +

    When you load an image from disk, as we did here, the data is likely to be +accessible via an array proxy. An array proxy is not the array itself but +something that represents the array, and can provide the array when we ask for +it.

    +

    Our image does have an array proxy, as we have already seen:

    +
    >>> img.dataobj
    +<nibabel.arrayproxy.ArrayProxy object at ...>
    +
    +
    +

    The array proxy allows us to create the image object without immediately +loading all the array data from disk.

    +

    Images with an array proxy object like this one are called proxy images +because the image data is not yet an array, but the array proxy points to +(proxies) the array data on disk.

    +

    You can test if the image has a array proxy like this:

    +
    >>> nib.is_proxy(img.dataobj)
    +True
    +
    +
    +
    +
    +

    Array images

    +

    We can also create images from numpy arrays. For example:

    +
    >>> array_data = np.arange(24, dtype=np.int16).reshape((2, 3, 4))
    +>>> affine = np.diag([1, 2, 3, 1])
    +>>> array_img = nib.Nifti1Image(array_data, affine)
    +
    +
    +

    In this case the image array data is already a numpy array, and there is no +version of the array on disk. The dataobj property of the image is the +array itself rather than a proxy for the array:

    +
    >>> array_img.dataobj
    +array([[[ 0,  1,  2,  3],
    +        [ 4,  5,  6,  7],
    +        [ 8,  9, 10, 11]],
    +
    +       [[12, 13, 14, 15],
    +        [16, 17, 18, 19],
    +        [20, 21, 22, 23]]], dtype=int16)
    +>>> array_img.dataobj is array_data
    +True
    +
    +
    +

    dataobj is an array, not an array proxy, so:

    +
    >>> nib.is_proxy(array_img.dataobj)
    +False
    +
    +
    +
    +
    +

    Getting the image data the easy way

    +

    For either type of image (array or proxy) you can always get the data with the +get_fdata() method.

    +

    For the array image, get_fdata() just returns the data array, if it’s already the required floating point type (default 64-bit float). If it isn’t that type, get_fdata() casts it to one:

    +
    >>> image_data = array_img.get_fdata()
    +>>> image_data.shape
    +(2, 3, 4)
    +>>> image_data.dtype == np.dtype(np.float64)
    +True
    +
    +
    +

    The cast to floating point means the array is not the one attached to the image:

    +
    >>> image_data is array_img.dataobj
    +False
    +
    +
    +

    Here’s an image backed by a floating point array:

    +
    >>> farray_img = nib.Nifti1Image(image_data.astype(np.float64), affine)
    +>>> farray_data = farray_img.get_fdata()
    +>>> farray_data.dtype == np.dtype(np.float64)
    +True
    +
    +
    +

    There was no cast, so the array returned is exactly the array attached to the +image:

    +
    >>> farray_data is farray_img.dataobj
    +True
    +
    +
    +

    For the proxy image, the get_fdata() method fetches the array data from +disk using the proxy, and returns the array.

    +
    >>> image_data = img.get_fdata()
    +>>> image_data.shape
    +(128, 96, 24, 2)
    +
    +
    +

    The image dataobj property is still a proxy object:

    +
    >>> img.dataobj
    +<nibabel.arrayproxy.ArrayProxy object at ...>
    +
    +
    +
    +
    +

    Proxies and caching

    +

    You may not want to keep loading the image data off disk every time +you call get_fdata() on a proxy image. By default, when you call +get_fdata() the first time on a proxy image, the image object keeps a +cached copy of the loaded array. The next time you call img.get_fdata(), +the image returns the array from cache rather than loading it from disk again.

    +
    >>> data_again = img.get_fdata()
    +
    +
    +

    The returned data is the same (cached) copy we returned before:

    +
    >>> data_again is image_data
    +True
    +
    +
    +

    See Images and memory for more details on managing image memory and +controlling the image cache.

    +
    +
    +

    Image slicing

    +

    At times it is useful to manipulate an image’s shape while keeping it in the +same coordinate system. +The slicer attribute provides an array-slicing interface to produce new +images with an appropriately adjusted header, such that the data at a given +RAS+ location is unchanged.

    +
    >>> cropped_img = img.slicer[32:-32, ...]
    +>>> cropped_img.shape
    +(64, 96, 24, 2)
    +
    +
    +

    The data is identical to cropping the data block directly:

    +
    >>> np.array_equal(cropped_img.get_fdata(), img.get_fdata()[32:-32, ...])
    +True
    +
    +
    +

    However, unused data did not need to be loaded into memory or scaled. +Additionally, the image affine was adjusted so that the X-translation is +32 voxels (64mm) less:

    +
    >>> cropped_img.affine
    +array([[ -2.  ,   0.  ,   0.  ,  53.86],
    +       [ -0.  ,   1.97,  -0.36, -35.72],
    +       [  0.  ,   0.32,   2.17,  -7.25],
    +       [  0.  ,   0.  ,   0.  ,   1.  ]])
    +
    +
    +
    >>> img.affine - cropped_img.affine
    +array([[ 0.,  0.,  0., 64.],
    +       [ 0.,  0.,  0.,  0.],
    +       [ 0.,  0.,  0.,  0.],
    +       [ 0.,  0.,  0.,  0.]])
    +
    +
    +

    Another use for the slicer object is to choose specific volumes from a +time series:

    +
    >>> vol0 = img.slicer[..., 0]
    +>>> vol0.shape
    +(128, 96, 24)
    +
    +
    +

    Or a selection of volumes:

    +
    >>> img.slicer[..., :1].shape
    +(128, 96, 24, 1)
    +>>> img.slicer[..., :2].shape
    +(128, 96, 24, 2)
    +
    +
    +

    It is also possible to use an integer step when slicing, downsampling +the image without filtering. +Note that this will induce artifacts in the frequency spectrum +(aliasing) along any axis that is down-sampled.

    +
    >>> downsampled = vol0.slicer[::2, ::2, ::2]
    +>>> downsampled.header.get_zooms()
    +(4.0, 4.0, 4.399998)
    +
    +
    +

    Finally, an image can be flipped along an axis, maintaining an appropriate +affine matrix:

    +
    >>> nib.orientations.aff2axcodes(img.affine)
    +('L', 'A', 'S')
    +>>> ras = img.slicer[::-1]
    +>>> nib.orientations.aff2axcodes(ras.affine)
    +('R', 'A', 'S')
    +>>> ras.affine
    +array([[  2.  ,   0.  ,   0.  , 117.86],
    +       [  0.  ,   1.97,  -0.36, -35.72],
    +       [ -0.  ,   0.32,   2.17,  -7.25],
    +       [  0.  ,   0.  ,   0.  ,   1.  ]])
    +
    +
    +
    +
    +
    +

    Loading and saving

    +

    The save and load functions in nibabel should do all the work for you:

    +
    >>> nib.save(array_img, 'my_image.nii')
    +>>> img_again = nib.load('my_image.nii')
    +>>> img_again.shape
    +(2, 3, 4)
    +
    +
    +

    You can also use the to_filename method:

    +
    >>> array_img.to_filename('my_image_again.nii')
    +>>> img_again = nib.load('my_image_again.nii')
    +>>> img_again.shape
    +(2, 3, 4)
    +
    +
    +

    You can get and set the filename with get_filename() and +set_filename():

    +
    >>> img_again.set_filename('another_image.nii')
    +>>> img_again.get_filename()
    +'another_image.nii'
    +
    +
    +
    +
    +

    Details of files and images

    +

    If an image can be loaded or saved on disk, the image will have an attribute +called file_map. img.file_map is a dictionary where the keys are the +names of the files that the image uses to load / save on disk, and the values +are FileHolder objects, that usually contain the filenames that the image +has been loaded from or saved to. In the case of a NiFTI1 single file, this +is just a single image file with a .nii or .nii.gz extension:

    +
    >>> list(img_again.file_map)
    +['image']
    +>>> img_again.file_map['image'].filename
    +'another_image.nii'
    +
    +
    +

    Other file types need more than one file to make up the image. The NiFTI1 +pair type is one example. NIfTI pair images have one file containing the +header information and another containing the image array data:

    +
    >>> pair_img = nib.Nifti1Pair(array_data, np.eye(4))
    +>>> nib.save(pair_img, 'my_pair_image.img')
    +>>> sorted(pair_img.file_map)
    +['header', 'image']
    +>>> pair_img.file_map['header'].filename
    +'my_pair_image.hdr'
    +>>> pair_img.file_map['image'].filename
    +'my_pair_image.img'
    +
    +
    +

    The older Analyze format also has a separate header and image file:

    +
    >>> ana_img = nib.AnalyzeImage(array_data, np.eye(4))
    +>>> sorted(ana_img.file_map)
    +['header', 'image']
    +
    +
    +

    It is the contents of the file_map that gets changed when you use +set_filename or to_filename:

    +
    >>> ana_img.set_filename('analyze_image.img')
    +>>> ana_img.file_map['image'].filename
    +'analyze_image.img'
    +>>> ana_img.file_map['header'].filename
    +'analyze_image.hdr'
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/nifti_images.html b/nifti_images.html new file mode 100644 index 0000000000..e6a85b846b --- /dev/null +++ b/nifti_images.html @@ -0,0 +1,591 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    Working with NIfTI images

    +

    This page describes some features of the nibabel implementation of the NIfTI +format. Generally all these features apply equally to the NIfTI 1 and the +NIfTI 2 format, but we will note the differences when they come up. NIfTI 1 +is much more common than NIfTI 2.

    +
    +

    Preliminaries

    +

    We first set some display parameters to print out numpy arrays in a compact +form:

    +
    >>> import numpy as np
    +>>> # Set numpy to print only 2 decimal digits for neatness
    +>>> np.set_printoptions(precision=2, suppress=True)
    +
    +
    +
    +
    +

    Example NIfTI images

    +
    >>> import os
    +>>> import nibabel as nib
    +>>> from nibabel.testing import data_path
    +
    +
    +

    This is the example NIfTI 1 image:

    +
    >>> example_ni1 = os.path.join(data_path, 'example4d.nii.gz')
    +>>> n1_img = nib.load(example_ni1)
    +>>> n1_img
    +<nibabel.nifti1.Nifti1Image object at ...>
    +
    +
    +

    Here is the NIfTI 2 example image:

    +
    >>> example_ni2 = os.path.join(data_path, 'example_nifti2.nii.gz')
    +>>> n2_img = nib.load(example_ni2)
    +>>> n2_img
    +<nibabel.nifti2.Nifti2Image object at ...>
    +
    +
    +
    +
    +

    The NIfTI header

    +

    The NIfTI 1 header is a small C structure of size 352 bytes. It contains the +following fields:

    +
    >>> n1_header = n1_img.header
    +>>> print(n1_header)                     
    +<class 'nibabel.nifti1.Nifti1Header'> object, endian='<'
    +sizeof_hdr      : 348
    +data_type       : b''
    +db_name         : b''
    +extents         : 0
    +session_error   : 0
    +regular         : b'r'
    +dim_info        : 57
    +dim             : [  4 128  96  24   2   1   1   1]
    +intent_p1       : 0.0
    +intent_p2       : 0.0
    +intent_p3       : 0.0
    +intent_code     : none
    +datatype        : int16
    +bitpix          : 16
    +slice_start     : 0
    +pixdim          : [   -1.      2.      2.      2.2  2000.      1.      1.      1. ]
    +vox_offset      : 0.0
    +scl_slope       : nan
    +scl_inter       : nan
    +slice_end       : 23
    +slice_code      : unknown
    +xyzt_units      : 10
    +cal_max         : 1162.0
    +cal_min         : 0.0
    +slice_duration  : 0.0
    +toffset         : 0.0
    +glmax           : 0
    +glmin           : 0
    +descrip         : b'FSL3.3\x00 v2.25 NIfTI-1 Single file format'
    +aux_file        : b''
    +qform_code      : scanner
    +sform_code      : scanner
    +quatern_b       : -1.94510681403e-26
    +quatern_c       : -0.996708512306
    +quatern_d       : -0.081068739295
    +qoffset_x       : 117.855102539
    +qoffset_y       : -35.7229423523
    +qoffset_z       : -7.24879837036
    +srow_x          : [  -2.      0.      0.    117.86]
    +srow_y          : [ -0.     1.97  -0.36 -35.72]
    +srow_z          : [ 0.    0.32  2.17 -7.25]
    +intent_name     : b''
    +magic           : b'n+1'
    +
    +
    +

    The NIfTI 2 header is similar, but of length 540 bytes, with fewer fields:

    +
    >>> n2_header = n2_img.header
    +>>> print(n2_header)                     
    +    <class 'nibabel.nifti2.Nifti2Header'> object, endian='<'
    +    sizeof_hdr      : 540
    +    magic           : b'n+2'
    +    eol_check       : [13 10 26 10]
    +    datatype        : int16
    +    bitpix          : 16
    +    dim             : [ 4 32 20 12  2  1  1  1]
    +    intent_p1       : 0.0
    +    intent_p2       : 0.0
    +    intent_p3       : 0.0
    +    pixdim          : [   -1.      2.      2.      2.2  2000.      1.      1.      1. ]
    +    vox_offset      : 0
    +    scl_slope       : nan
    +    scl_inter       : nan
    +    cal_max         : 1162.0
    +    cal_min         : 0.0
    +    slice_duration  : 0.0
    +    toffset         : 0.0
    +    slice_start     : 0
    +    slice_end       : 23
    +    descrip         : b'FSL3.3\x00 v2.25 NIfTI-1 Single file format'
    +    aux_file        : b''
    +    qform_code      : scanner
    +    sform_code      : scanner
    +    quatern_b       : -1.94510681403e-26
    +    quatern_c       : -0.996708512306
    +    quatern_d       : -0.081068739295
    +    qoffset_x       : 117.855102539
    +    qoffset_y       : -35.7229423523
    +    qoffset_z       : -7.24879837036
    +    srow_x          : [  -2.      0.      0.    117.86]
    +    srow_y          : [ -0.     1.97  -0.36 -35.72]
    +    srow_z          : [ 0.    0.32  2.17 -7.25]
    +    slice_code      : unknown
    +    xyzt_units      : 10
    +    intent_code     : none
    +    intent_name     : b''
    +    dim_info        : 57
    +    unused_str      : b''
    +
    +
    +

    You can get and set individual fields in the header using dict (mapping-type) +item access. For example:

    +
    >>> n1_header['cal_max']
    +array(1162., dtype=float32)
    +>>> n1_header['cal_max'] = 1200
    +>>> n1_header['cal_max']
    +array(1200., dtype=float32)
    +
    +
    +

    Check the attributes of the header for get_ / set_ methods to get and +set various combinations of NIfTI header fields.

    +

    The get_ / set_ methods should check and apply valid combinations of +values from the header, whereas you can do anything you like with the dict / +mapping item access. It is safer to use the get_ / set_ methods and +use the mapping item access only if the get_ / set_ methods will not +do what you want.

    +
    +
    +

    The NIfTI affines

    +

    Like other nibabel image types, NIfTI images have an affine relating the voxel +coordinates to world coordinates in RAS+ space:

    +
    >>> n1_img.affine
    +array([[ -2.  ,   0.  ,   0.  , 117.86],
    +       [ -0.  ,   1.97,  -0.36, -35.72],
    +       [  0.  ,   0.32,   2.17,  -7.25],
    +       [  0.  ,   0.  ,   0.  ,   1.  ]])
    +
    +
    +

    Unlike other formats, the NIfTI header format can specify this affine in one +of three ways — the sform affine, the qform affine and the fall-back +header affine.

    +

    Nibabel uses an algorithm to chose which of +these three it will use for the overall image affine.

    +
    +

    The sform affine

    +

    The header stores the three first rows of the 4 by 4 affine in the header +fields srow_x, srow_y, srow_z. The header does not store the +fourth row because it is always [0, 0, 0, 1] (see +Coordinate systems and affines).

    +

    You can get the sform affine specifically with the get_sform() method of +the image or the header.

    +

    For example:

    +
    >>> print(n1_header['srow_x'])
    +[ -2.     0.     0.   117.86]
    +>>> print(n1_header['srow_y'])
    +[ -0.     1.97  -0.36 -35.72]
    +>>> print(n1_header['srow_z'])
    +[ 0.    0.32  2.17 -7.25]
    +>>> print(n1_header.get_sform())
    +[[ -2.     0.     0.   117.86]
    + [ -0.     1.97  -0.36 -35.72]
    + [  0.     0.32   2.17  -7.25]
    + [  0.     0.     0.     1.  ]]
    +
    +
    +

    This affine is valid only if the sform_code is not zero.

    +
    >>> print(n1_header['sform_code'])
    +1
    +
    +
    +

    The different sform code values specify which RAS+ space the sform affine +refers to, with these interpretations:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Code

    Label

    Meaning

    0

    unknown

    sform not defined

    1

    scanner

    RAS+ in scanner coordinates

    2

    aligned

    RAS+ aligned to some other scan

    3

    talairach

    RAS+ in Talairach atlas space

    4

    mni

    RAS+ in MNI atlas space

    +

    In our case the code is 1, meaning “scanner” alignment.

    +

    You can get the affine and the code using the coded=True argument to +get_sform():

    +
    >>> print(n1_header.get_sform(coded=True))
    +(array([[ -2.  ,   0.  ,   0.  , 117.86],
    +       [ -0.  ,   1.97,  -0.36, -35.72],
    +       [  0.  ,   0.32,   2.17,  -7.25],
    +       [  0.  ,   0.  ,   0.  ,   1.  ]]), 1)
    +
    +
    +

    You can set the sform with the set_sform() method of the header and +the image.

    +
    >>> n1_header.set_sform(np.diag([2, 3, 4, 1]))
    +>>> n1_header.get_sform()
    +array([[2., 0., 0., 0.],
    +       [0., 3., 0., 0.],
    +       [0., 0., 4., 0.],
    +       [0., 0., 0., 1.]])
    +
    +
    +

    Set the affine and code using the code parameter to set_sform():

    +
    >>> n1_header.set_sform(np.diag([3, 4, 5, 1]), code='mni')
    +>>> n1_header.get_sform(coded=True)
    +(array([[3., 0., 0., 0.],
    +       [0., 4., 0., 0.],
    +       [0., 0., 5., 0.],
    +       [0., 0., 0., 1.]]), 4)
    +
    +
    +
    +
    +

    The qform affine

    +

    This affine can be calculated from a combination of the voxel sizes (entries 1 +through 4 of the pixdim field), a sign flip called qfac stored in +entry 0 of pixdim, and a quaternion that can be +reconstructed from fields quatern_b, quatern_c, quatern_d.

    +

    See the code for the get_qform() method for details.

    +

    You can get and set the qform affine using the equivalent methods to those for +the sform: get_qform(), set_qform().

    +
    >>> n1_header.get_qform(coded=True)
    +(array([[ -2.  ,   0.  ,  -0.  , 117.86],
    +       [  0.  ,   1.97,  -0.36, -35.72],
    +       [  0.  ,   0.32,   2.17,  -7.25],
    +       [  0.  ,   0.  ,   0.  ,   1.  ]]), 1)
    +
    +
    +

    The qform also has a corresponding qform_code with the same interpretation +as the sform_code.

    +
    +
    +

    The fall-back header affine

    +

    This is the affine of last resort, constructed only from the pixdim voxel +sizes. The NIfTI specification says that this should set the +first voxel in the image as [0, 0, 0] in world coordinates, but we nibabblers +follow SPM in preferring to set the central voxel to have [0, 0, 0] world +coordinate. The NIfTI spec also implies that the image should be assumed to be +in RAS+ voxel orientation for this affine (see Coordinate systems and affines). +Again like SPM, we prefer to assume LAS+ voxel orientation by default.

    +

    You can always get the fall-back affine with get_base_affine():

    +
    >>> n1_header.get_base_affine()
    +array([[ -2. ,   0. ,   0. , 127. ],
    +       [  0. ,   2. ,   0. , -95. ],
    +       [  0. ,   0. ,   2.2, -25.3],
    +       [  0. ,   0. ,   0. ,   1. ]])
    +
    +
    +
    +
    +

    Choosing the image affine

    +

    Given there are three possible affines defined in the NIfTI header, nibabel +has to chose which of these to use for the image affine.

    +

    The algorithm is defined in the get_best_affine() method. It is:

    +
      +
    1. If sform_code != 0 (‘unknown’) use the sform affine; else

    2. +
    3. If qform_code != 0 (‘unknown’) use the qform affine; else

    4. +
    5. Use the fall-back affine.

    6. +
    +
    +
    +

    Default sform and qform codes

    +

    If you create a new image, e.g.:

    +
    >>> data = np.random.random((20, 20, 20))
    +>>> xform = np.eye(4) * 2
    +>>> img = nib.nifti1.Nifti1Image(data, xform)
    +
    +
    +

    The sform and qform codes will be initialised to 2 (aligned) and 0 (unknown) +respectively:

    +
    >>> img.get_sform(coded=True) 
    +(array([[2., 0., 0., 0.],
    +       [0., 2., 0., 0.],
    +       [0., 0., 2., 0.],
    +       [0., 0., 0., 1.]]), 2)
    +>>> img.get_qform(coded=True)
    +(None, 0)
    +
    +
    +

    This is based on the assumption that the affine you specify for a newly +created image will align the image to some known coordinate system. According +to the NIfTI specification, the qform is intended to encode a +transformation into scanner coordinates - for a programmatically created +image, we have no way of knowing what the scanner coordinate system is; +furthermore, the qform cannot be used to store an arbitrary affine transform, +as it is unable to encode shears. So the provided affine will be stored in the +sform, and the qform will be left uninitialised.

    +

    If you create a new image and specify an existing header, e.g.:

    +
    >>> example_ni1 = os.path.join(data_path, 'example4d.nii.gz')
    +>>> n1_img = nib.load(example_ni1)
    +>>> new_header = header=n1_img.header.copy()
    +>>> new_data = np.random.random(n1_img.shape[:3])
    +>>> new_img = nib.nifti1.Nifti1Image(data, None, header=new_header)
    +
    +
    +

    then the newly created image will inherit the same sform and qform codes that +are in the provided header. However, if you create a new image with both an +affine and a header specified, e.g.:

    +
    >>> xform = np.eye(4)
    +>>> new_img = nib.nifti1.Nifti1Image(data, xform, header=new_header)
    +
    +
    +

    then the sform and qform codes will only be preserved if the provided affine +is the same as the affine in the provided header. If the affines do not match, +the sform and qform codes will be set to their default values of 2 and 0 +respectively. This is done on the basis that, if you are changing the affine, +you are likely to be changing the space to which the affine is pointing. So +the original sform and qform codes can no longer be assumed to be valid.

    +

    If you wish to set the sform and qform affines and/or codes to some other +value, you can always set them after creation using the set_sform and +set_qform methods, as described above.

    +
    +
    +
    +

    Data scaling

    +

    NIfTI uses a simple scheme for data scaling.

    +

    By default, nibabel will take care of this scaling for you, but there may be +times that you want to control the data scaling yourself. If so, the next +section describes how the scaling works and the nibabel implementation of +same.

    +

    There are two scaling fields in the header called scl_slope and +scl_inter.

    +

    The output data from a NIfTI image comes from:

    +
      +
    1. Loading the binary data from the image file;

    2. +
    3. Casting the numbers to the binary format given in the header and returned +by get_data_dtype();

    4. +
    5. Reshaping to the output image shape;

    6. +
    7. Multiplying the result by the header scl_slope value, if +both of scl_slope and scl_inter are defined;

    8. +
    9. Adding the value header scl_inter value to the result, if both of +scl_slope and scl_inter are defined;

    10. +
    +

    ‘Defined’ means, the value is not NaN (not a number).

    +

    All this gets built into the array proxy when you load a NIfTI image.

    +

    When you load an image, the header scaling values automatically get set to NaN +(undefined) to mark the fact that the scaling values have been consumed by the +read. The scaling values read from the header on load only appear in the +array proxy object.

    +

    To see how this works, let’s make a new image with some scaling:

    +
    >>> array_data = np.arange(24, dtype=np.int16).reshape((2, 3, 4))
    +>>> affine = np.diag([1, 2, 3, 1])
    +>>> array_img = nib.Nifti1Image(array_data, affine)
    +>>> array_header = array_img.header
    +
    +
    +

    The default scaling values are NaN (undefined):

    +
    >>> array_header['scl_slope']
    +array(nan, dtype=float32)
    +>>> array_header['scl_inter']
    +array(nan, dtype=float32)
    +
    +
    +

    You can get the scaling values with the get_slope_inter() method:

    +
    >>> array_header.get_slope_inter()
    +(None, None)
    +
    +
    +

    None corresponds to the NaN scaling value (undefined).

    +

    We can set them in the image header, so they get saved to the header when the +image is written. We can do this by setting the fields directly, or with +set_slope_inter():

    +
    >>> array_header.set_slope_inter(2, 10)
    +>>> array_header.get_slope_inter()
    +(2.0, 10.0)
    +>>> array_header['scl_slope']
    +array(2., dtype=float32)
    +>>> array_header['scl_inter']
    +array(10., dtype=float32)
    +
    +
    +

    Setting the scale factors in the header has no effect on the image data before +we save and load again:

    +
    >>> array_img.get_fdata()
    +array([[[ 0.,  1.,  2.,  3.],
    +        [ 4.,  5.,  6.,  7.],
    +        [ 8.,  9., 10., 11.]],
    +
    +       [[12., 13., 14., 15.],
    +        [16., 17., 18., 19.],
    +        [20., 21., 22., 23.]]])
    +
    +
    +

    Now we save the image and load it again:

    +
    >>> nib.save(array_img, 'scaled_image.nii')
    +>>> scaled_img = nib.load('scaled_image.nii')
    +
    +
    +

    The data array has the scaling applied:

    +
    >>> scaled_img.get_fdata()
    +array([[[10., 12., 14., 16.],
    +        [18., 20., 22., 24.],
    +        [26., 28., 30., 32.]],
    +
    +       [[34., 36., 38., 40.],
    +        [42., 44., 46., 48.],
    +        [50., 52., 54., 56.]]])
    +
    +
    +

    The header for the loaded image has had the scaling reset to undefined, to +mark the fact that the scaling has been “consumed” by the load:

    +
    >>> scaled_img.header.get_slope_inter()
    +(None, None)
    +
    +
    +

    The original slope and intercept are still accessible in the array proxy +object:

    +
    >>> scaled_img.dataobj.slope
    +2.0
    +>>> scaled_img.dataobj.inter
    +10.0
    +
    +
    +

    If the header scaling is undefined when we save the image, nibabel will try to +find an optimum slope and intercept to best preserve the precision of the data +in the output data type. Because nibabel will set the scaling to undefined +when loading the image, or creating a new image, this is the default behavior.

    +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/notebooks/index.html b/notebooks/index.html new file mode 100644 index 0000000000..464c401681 --- /dev/null +++ b/notebooks/index.html @@ -0,0 +1,108 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    IPython notebooks for Nibabel project

    +
    +

    Rotation matrix orthogonality

    +

    See ATA error calculations and Cross +product error. You can use the IPython notebook +viewer to view these files; copy the URL and paste into the Notebook viewer +URL box.

    +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/objects.inv b/objects.inv new file mode 100644 index 0000000000..1d1514fe1b Binary files /dev/null and b/objects.inv differ diff --git a/old/ioimplementation.html b/old/ioimplementation.html new file mode 100644 index 0000000000..29346e584f --- /dev/null +++ b/old/ioimplementation.html @@ -0,0 +1,212 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    Relationship between images and io implementations

    +
    +

    Summary and sign-off

    +

    These were some meditations about splitting the image into two API parts.

    +

    The first part would be the lower level IO implementation. This part is +rather like a fusion of the Header and ArrayProxy objects +in current nibabel. It takes care of lower level details like i/o data dtype, +shape, offset, and it might help with slicing to get the data. On top of that +would be a high level interface implementing load, save, filename, +data. The top-level image also had the novel idea of a mode parameter +which, if 'r', would raise an error on attempting to save.

    +
    +
    +

    Images

    +

    An image houses the association of the:

    +
      +
    • data array

    • +
    • affine

    • +
    • output space

    • +
    • metadata

    • +
    • mode

    • +
    +

    These are straightforward attributes, and have no necessary relationship +to stuff on disk.

    +

    By ‘’disk’’, we mean, file-like objects - not necessarily on disk.

    +

    The io implementation manages the relationship of images and stuff on +disk.

    +

    Specifically, it manages load of images from disk, and save of +images to disk.

    +

    The user does not see the io implementation unless they ask to. In +standard use of images they will not need to do this.

    +
    +
    +

    IO implementations

    +

    By use case.

    +
    Creating array image, saving
    +
    +>>> import tempfile
    +>>> from nibabel.images import Image
    +>>> from nibabel import load, save
    +>>> fp, fname = tempfile.mkstemp('.nii')
    +>>> data = np.arange(24).reshape((2,3,4))
    +>>> img = Image(data)
    +>>> img.filename is None
    +True
    +>>> img.save()
    +Traceback (most recent call last):
    +   ...
    +ImageError: no filespec to save to
    +>>> save(img)
    +Traceback (most recent call last):
    +   ...
    +ImageError: no filespec to save to
    +>>> img2 = save(img, 'some_image.nii') # type guessed from filename
    +>>> img2.filename == fname
    +True
    +>>> img.filename is None # still
    +True
    +>>> img.filename = 'some_filename.nii' # read only property
    +Traceback (most recent call last):
    +   ...
    +AttributeError: can't set attribute
    +
    +Load, futz, save
    +
    +>>> img3 = load(fname, mode='r')
    +>>> img3.filename == fname
    +True
    +>>> np.all(img3.data == data)
    +True
    +>>> img3.data[0,0] = 99
    +>>> img3.save()
    +Traceback (most recent call last):
    +   ...
    +ImageError: trying to write to read only image
    +>>> img3.mode = 'rw'
    +>>> img3.save()
    +>>> load(img4)
    +>>> img4.mode # 'r' is the default
    +'r'
    +>>> mod_data = data.copy()
    +>>> mod_data[0,0] = 99
    +>>> np.all(img4.data = mod_data)
    +True
    +
    +Prepare image for later writing
    +
    +>>> img5 = Image(np.zeros(2,3,4))
    +>>> fp, fname2 = tempfile.mkstemp('.nii')
    +>>> img5.set_filespec(fname2)
    +>>> # then do some things to the image
    +>>> img5.save()
    +
    +This is an example where you do need the io API
    +
    +>>> from nibabel.ioimps import guessed_imp
    +>>> fp, fname3 = tempfile.mkstemp('.nii')
    +>>> ioimp = guessed_imp(fname3)
    +>>> ioimp.set_data_dtype(np.float64)
    +>>> ioimp.set_data_shape((2,3,4)) # set_data_shape method
    +>>> slice_def = (slice(None), slice(None), 0)
    +>>> ioimp.write_slice(data[slice_def], slice_def) # write_slice method
    +>>> slice_def = (2, 3, 1)
    +>>> ioimp.write_slice(data[slice_def], slice_def) # write_slice method
    +Traceback (most recent call last):
    +   ...
    +ImageIOError: data write is not contiguous
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/py-modindex.html b/py-modindex.html new file mode 100644 index 0000000000..42f2851ce9 --- /dev/null +++ b/py-modindex.html @@ -0,0 +1,596 @@ + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + + +

    Python Module Index

    + +
    + n +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
     
    + n
    + nibabel +
        + nibabel._compression +
        + nibabel.affines +
        + nibabel.analyze +
        + nibabel.arrayproxy +
        + nibabel.arraywriters +
        + nibabel.batteryrunners +
        + nibabel.benchmarks +
        + nibabel.benchmarks.bench_array_to_file +
        + nibabel.benchmarks.bench_arrayproxy_slicing +
        + nibabel.benchmarks.bench_fileslice +
        + nibabel.benchmarks.bench_finite_range +
        + nibabel.benchmarks.bench_load_save +
        + nibabel.benchmarks.butils +
        + nibabel.brikhead +
        + nibabel.caret +
        + nibabel.casting +
        + nibabel.cifti2 +
        + nibabel.cifti2.cifti2 +
        + nibabel.cifti2.cifti2_axes +
        + nibabel.cifti2.parse_cifti2 +
        + nibabel.cmdline +
        + nibabel.cmdline.conform +
        + nibabel.cmdline.convert +
        + nibabel.cmdline.dicomfs +
        + nibabel.cmdline.diff +
        + nibabel.cmdline.ls +
        + nibabel.cmdline.nifti_dx +
        + nibabel.cmdline.parrec2nii +
        + nibabel.cmdline.roi +
        + nibabel.cmdline.stats +
        + nibabel.cmdline.tck2trk +
        + nibabel.cmdline.trk2tck +
        + nibabel.cmdline.utils +
        + nibabel.data +
        + nibabel.dataobj_images +
        + nibabel.deprecated +
        + nibabel.deprecator +
        + nibabel.dft +
        + nibabel.ecat +
        + nibabel.environment +
        + nibabel.eulerangles +
        + nibabel.filebasedimages +
        + nibabel.fileholders +
        + nibabel.filename_parser +
        + nibabel.fileslice +
        + nibabel.fileutils +
        + nibabel.freesurfer +
        + nibabel.freesurfer.io +
        + nibabel.freesurfer.mghformat +
        + nibabel.funcs +
        + nibabel.gifti +
        + nibabel.gifti.gifti +
        + nibabel.gifti.parse_gifti_fast +
        + nibabel.gifti.util +
        + nibabel.imageclasses +
        + nibabel.imageglobals +
        + nibabel.imagestats +
        + nibabel.loadsave +
        + nibabel.minc1 +
        + nibabel.minc2 +
        + nibabel.mriutils +
        + nibabel.nicom +
        + nibabel.nicom.ascconv +
        + nibabel.nicom.csareader +
        + nibabel.nicom.dicomreaders +
        + nibabel.nicom.dicomwrappers +
        + nibabel.nicom.dwiparams +
        + nibabel.nicom.structreader +
        + nibabel.nicom.utils +
        + nibabel.nifti1 +
        + nibabel.nifti2 +
        + nibabel.onetime +
        + nibabel.openers +
        + nibabel.optpkg +
        + nibabel.orientations +
        + nibabel.parrec +
        + nibabel.pointset +
        + nibabel.processing +
        + nibabel.pydicom_compat +
        + nibabel.quaternions +
        + nibabel.rstutils +
        + nibabel.spaces +
        + nibabel.spatialimages +
        + nibabel.spm2analyze +
        + nibabel.spm99analyze +
        + nibabel.streamlines +
        + nibabel.streamlines.array_sequence +
        + nibabel.streamlines.header +
        + nibabel.streamlines.tck +
        + nibabel.streamlines.tractogram +
        + nibabel.streamlines.tractogram_file +
        + nibabel.streamlines.trk +
        + nibabel.streamlines.utils +
        + nibabel.tmpdirs +
        + nibabel.tripwire +
        + nibabel.viewers +
        + nibabel.volumeutils +
        + nibabel.wrapstruct +
        + nibabel.xmlutils +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index b6b420c79c..0000000000 --- a/pyproject.toml +++ /dev/null @@ -1,203 +0,0 @@ -[build-system] -requires = ["hatchling", "hatch-vcs"] -build-backend = "hatchling.build" - -[project] -name = "nibabel" -description = "Access a multitude of neuroimaging data formats" -authors = [{ name = "NiBabel developers", email = "neuroimaging@python.org" }] -maintainers = [{ name = "Christopher Markiewicz" }] -readme = "README.rst" -license = { text = "MIT License" } -requires-python = ">=3.9" -dependencies = [ - "numpy >=1.23", - "packaging >=20", - "importlib_resources >=5.12; python_version < '3.12'", - "typing_extensions >=4.6; python_version < '3.13'", -] -classifiers = [ - "Development Status :: 5 - Production/Stable", - "Environment :: Console", - "Intended Audience :: Science/Research", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - "Programming Language :: Python", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", - "Topic :: Scientific/Engineering", -] -# Version from setuptools_scm -dynamic = ["version"] - -[project.urls] -"Homepage" = "https://nipy.org/nibabel" -"Development" = "https://github.com/nipy/nibabel" - -[project.scripts] -nib-conform = "nibabel.cmdline.conform:main" -nib-convert = "nibabel.cmdline.convert:main" -nib-ls = "nibabel.cmdline.ls:main" -nib-dicomfs = "nibabel.cmdline.dicomfs:main" -nib-diff = "nibabel.cmdline.diff:main" -nib-stats = "nibabel.cmdline.stats:main" -nib-nifti-dx = "nibabel.cmdline.nifti_dx:main" -nib-tck2trk = "nibabel.cmdline.tck2trk:main" -nib-trk2tck = "nibabel.cmdline.trk2tck:main" -nib-roi = "nibabel.cmdline.roi:main" -parrec2nii = "nibabel.cmdline.parrec2nii:main" - -[project.optional-dependencies] -all = ["nibabel[dicomfs,indexed_gzip,minc2,spm,zstd]"] -# Features -indexed_gzip = ["indexed_gzip >=1.6"] -dicom = ["pydicom >=2.3"] -dicomfs = ["nibabel[dicom]", "pillow >=8.4"] -minc2 = ["h5py >=3.5"] -spm = ["scipy >=1.8"] -viewers = ["matplotlib >=3.5"] -zstd = ["pyzstd >=0.15.2"] -# For doc and test, make easy to use outside of tox -# tox should use these with extras instead of duplicating -doc = [ - "sphinx", - "matplotlib>=3.5", - "numpydoc", - "texext", - "tomli; python_version < '3.11'", -] -test = [ - "pytest >=6", - "pytest-doctestplus >=1", - "pytest-cov >=2.11", - "pytest-httpserver >=1.0.7", - "pytest-xdist >=3.5", - "coverage[toml]>=7.2", -] -# Remaining: Simpler to centralize in tox -dev = ["tox"] -doctest = ["tox"] -style = ["tox"] -typing = ["tox"] - -[tool.hatch.build.targets.sdist] -exclude = [ - ".git_archival.txt", - # Submodules with large files; if we don't want them in the repo... - "nibabel-data/", -] - -[tool.hatch.build.targets.wheel] -packages = ["nibabel"] -exclude = [ - # 56MB test file does not need to be installed everywhere - "nibabel/nicom/tests/data/4d_multiframe_test.dcm", -] - -[tool.hatch.version] -source = "vcs" -tag-pattern = '(?P\d+(?:\.\d+){0,2}[^+]*)(?:\+.*)?$' -raw-options = { version_scheme = "release-branch-semver" } - -[tool.hatch.build.hooks.vcs] -version-file = "nibabel/_version.py" -# Old default setuptools_scm template; hatch-vcs currently causes -# a noisy warning if template is missing. -template = ''' -# file generated by setuptools_scm -# don't change, don't track in version control -__version__ = version = {version!r} -__version_tuple__ = version_tuple = {version_tuple!r} -''' - -[tool.ruff] -line-length = 99 -exclude = ["doc", "nibabel/externals", "tools", "version.py", "versioneer.py"] - -[tool.ruff.lint] -select = [ - "B", - "C4", - "F", - "FLY", - "FURB", - "I", - "ISC", - "PERF", - "PGH", - "PIE", - "PLE", - "PT", - "PYI", - "Q", - "RSE", - "RUF", - "TCH", - "UP", -] -ignore = [ - "B006", # TODO: enable - "B008", # TODO: enable - "B007", - "B011", - "B017", # TODO: enable - "B018", - "B020", - "B023", # TODO: enable - "B028", - "B904", - "C401", - "C408", - "C416", - "PERF203", - "PIE790", - "PT007", - "PT011", - "PT012", - "PT017", - "PT018", - "PYI024", - "RUF005", - "RUF012", # TODO: enable - "RUF015", - "RUF017", # TODO: enable - "UP038", # https://github.com/astral-sh/ruff/issues/7871 - # https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules - "W191", - "E111", - "E114", - "E117", - "D206", - "D300", - "Q000", - "Q001", - "Q002", - "Q003", - "COM812", - "COM819", -] - -[tool.ruff.lint.per-file-ignores] -"__init__.py" = ["F401"] -"doc/source/conf.py" = ["F401"] - -[tool.ruff.format] -quote-style = "single" - -[tool.mypy] -python_version = "3.11" -exclude = [ - "/tests", -] -warn_unreachable = true -enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] - -[tool.codespell] -skip = "*/data/*,./nibabel-data" -ignore-words-list = "ans,te,ue,ist,nin,nd,ccompiler,ser" - -[tool.uv.pip] -only-binary = ["numpy", "scipy", "h5py"] diff --git a/reference/index.html b/reference/index.html new file mode 100644 index 0000000000..9b25d895cb --- /dev/null +++ b/reference/index.html @@ -0,0 +1,3374 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    API Reference

    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel._compression.html b/reference/nibabel._compression.html new file mode 100644 index 0000000000..993e7642b0 --- /dev/null +++ b/reference/nibabel._compression.html @@ -0,0 +1,116 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    _compression

    +

    Constants and types for dealing transparently with compression

    + + + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.affines.html b/reference/nibabel.affines.html new file mode 100644 index 0000000000..3243e7d6fc --- /dev/null +++ b/reference/nibabel.affines.html @@ -0,0 +1,554 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    affines

    +

    Utility routines for working with points and affine transforms

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    AffineError

    Errors in calculating or using affines

    append_diag(aff, steps[, starts])

    Add diagonal elements steps and translations starts to affine

    apply_affine(aff, pts[, inplace])

    Apply affine matrix aff to points pts

    dot_reduce(*args)

    Apply numpy dot product function from right to left on arrays

    from_matvec(matrix[, vector])

    Combine a matrix and vector into an homogeneous affine

    obliquity(affine)

    Estimate the obliquity an affine's axes represent

    rescale_affine(affine, shape, zooms[, new_shape])

    Return a new affine matrix with updated voxel sizes (zooms)

    to_matvec(transform)

    Split a transform into its matrix and vector components

    voxel_sizes(affine)

    Return voxel size for each input axis given affine

    +
    +

    AffineError

    +
    +
    +class nibabel.affines.AffineError
    +

    Bases: ValueError

    +

    Errors in calculating or using affines

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    + +
    +
    +

    append_diag

    +
    +
    +nibabel.affines.append_diag(aff, steps, starts=())
    +

    Add diagonal elements steps and translations starts to affine

    +

    Typical use is in expanding 4x4 affines to larger dimensions. Nipy is the +main consumer because it uses NxM affines, whereas we generally only use +4x4 affines; the routine is here for convenience.

    +
    +
    Parameters:
    +
    +
    aff2D array

    N by M affine matrix

    +
    +
    stepsscalar or sequence

    diagonal elements to append.

    +
    +
    startsscalar or sequence

    elements to append to last column of aff, representing translations +corresponding to the steps. If empty, expands to a vector of zeros +of the same length as steps

    +
    +
    +
    +
    Returns:
    +
    +
    aff_plus2D array

    Now P by Q where L = len(steps) and P == N+L, Q=N+L

    +
    +
    +
    +
    +

    Examples

    +
    >>> aff = np.eye(4)
    +>>> aff[:3,:3] = np.arange(9).reshape((3,3))
    +>>> append_diag(aff, [9, 10], [99,100])
    +array([[  0.,   1.,   2.,   0.,   0.,   0.],
    +       [  3.,   4.,   5.,   0.,   0.,   0.],
    +       [  6.,   7.,   8.,   0.,   0.,   0.],
    +       [  0.,   0.,   0.,   9.,   0.,  99.],
    +       [  0.,   0.,   0.,   0.,  10., 100.],
    +       [  0.,   0.,   0.,   0.,   0.,   1.]])
    +
    +
    +
    + +
    +
    +

    apply_affine

    +
    +
    +nibabel.affines.apply_affine(aff, pts, inplace=False)
    +

    Apply affine matrix aff to points pts

    +

    Returns result of application of aff to the right of pts. The +coordinate dimension of pts should be the last.

    +

    For the 3D case, aff will be shape (4,4) and pts will have final axis +length 3 - maybe it will just be N by 3. The return value is the +transformed points, in this case:

    +
    res = np.dot(aff[:3,:3], pts.T) + aff[:3,3:4]
    +transformed_pts = res.T
    +
    +
    +

    This routine is more general than 3D, in that aff can have any shape +(N,N), and pts can have any shape, as long as the last dimension is for +the coordinates, and is therefore length N-1.

    +
    +
    Parameters:
    +
    +
    aff(N, N) array-like

    Homogeneous affine, for 3D points, will be 4 by 4. Contrary to first +appearance, the affine will be applied on the left of pts.

    +
    +
    pts(…, N-1) array-like

    Points, where the last dimension contains the coordinates of each +point. For 3D, the last dimension will be length 3.

    +
    +
    inplacebool, optional

    If True, attempt to apply the affine directly to pts. +If False, or in-place application fails, a freshly allocated +array will be returned.

    +
    +
    +
    +
    Returns:
    +
    +
    transformed_pts(…, N-1) array

    transformed points

    +
    +
    +
    +
    +

    Examples

    +
    >>> aff = np.array([[0,2,0,10],[3,0,0,11],[0,0,4,12],[0,0,0,1]])
    +>>> pts = np.array([[1,2,3],[2,3,4],[4,5,6],[6,7,8]])
    +>>> apply_affine(aff, pts) 
    +array([[14, 14, 24],
    +       [16, 17, 28],
    +       [20, 23, 36],
    +       [24, 29, 44]]...)
    +
    +
    +

    Just to show that in the simple 3D case, it is equivalent to:

    +
    >>> (np.dot(aff[:3,:3], pts.T) + aff[:3,3:4]).T 
    +array([[14, 14, 24],
    +       [16, 17, 28],
    +       [20, 23, 36],
    +       [24, 29, 44]]...)
    +
    +
    +

    But pts can be a more complicated shape:

    +
    >>> pts = pts.reshape((2,2,3))
    +>>> apply_affine(aff, pts) 
    +array([[[14, 14, 24],
    +        [16, 17, 28]],
    +
    +       [[20, 23, 36],
    +        [24, 29, 44]]]...)
    +
    +
    +
    + +
    +
    +

    dot_reduce

    +
    +
    +nibabel.affines.dot_reduce(*args)
    +

    Apply numpy dot product function from right to left on arrays

    +

    For passed arrays \(A, B, C, ... Z\) returns \(A \dot B \dot C ... +\dot Z\) where “.” is the numpy array dot product.

    +
    +
    Parameters:
    +
    +
    **argsarrays

    Arrays that can be passed to numpy dot function

    +
    +
    +
    +
    Returns:
    +
    +
    dot_productarray

    If there are N arguments, result of arg[0].dot(arg[1].dot(arg[2].dot +...  arg[N-2].dot(arg[N-1])))...

    +
    +
    +
    +
    +
    + +
    +
    +

    from_matvec

    +
    +
    +nibabel.affines.from_matvec(matrix, vector=None)
    +

    Combine a matrix and vector into an homogeneous affine

    +

    Combine a rotation / scaling / shearing matrix and translation vector into +a transform in homogeneous coordinates.

    +
    +
    Parameters:
    +
    +
    matrixarray-like

    An NxM array representing the the linear part of the transform. +A transform from an M-dimensional space to an N-dimensional space.

    +
    +
    vectorNone or array-like, optional

    None or an (N,) array representing the translation. None corresponds to +an (N,) array of zeros.

    +
    +
    +
    +
    Returns:
    +
    +
    xformarray

    An (N+1, M+1) homogeneous transform matrix.

    +
    +
    +
    +
    +
    +

    See also

    +
    +
    to_matvec
    +
    +
    +

    Examples

    +
    >>> from_matvec(np.diag([2, 3, 4]), [9, 10, 11])
    +array([[ 2,  0,  0,  9],
    +       [ 0,  3,  0, 10],
    +       [ 0,  0,  4, 11],
    +       [ 0,  0,  0,  1]])
    +
    +
    +

    The vector argument is optional:

    +
    >>> from_matvec(np.diag([2, 3, 4]))
    +array([[2, 0, 0, 0],
    +       [0, 3, 0, 0],
    +       [0, 0, 4, 0],
    +       [0, 0, 0, 1]])
    +
    +
    +
    + +
    +
    +

    obliquity

    +
    +
    +nibabel.affines.obliquity(affine)
    +

    Estimate the obliquity an affine’s axes represent

    +

    The term obliquity is defined here as the rotation of those axes with +respect to the cardinal axes. +This implementation is inspired by AFNI’s implementation. +For further details about obliquity, check AFNI’s documentation.

    +
    +
    Parameters:
    +
    +
    affine2D array-like

    Affine transformation array. Usually shape (4, 4), but can be any 2D +array.

    +
    +
    +
    +
    Returns:
    +
    +
    angles1D array-like

    The obliquity of each axis with respect to the cardinal axes, in radians.

    +
    +
    +
    +
    +
    + +
    +
    +

    rescale_affine

    +
    +
    +nibabel.affines.rescale_affine(affine, shape, zooms, new_shape=None)
    +

    Return a new affine matrix with updated voxel sizes (zooms)

    +

    This function preserves the rotations and shears of the original +affine, as well as the RAS location of the central voxel of the +image.

    +
    +
    Parameters:
    +
    +
    affine(N, N) array-like

    NxN transform matrix in homogeneous coordinates representing an affine +transformation from an (N-1)-dimensional space to an (N-1)-dimensional +space. An example is a 4x4 transform representing rotations and +translations in 3 dimensions.

    +
    +
    shape(N-1,) array-like

    The extent of the (N-1) dimensions of the original space

    +
    +
    zooms(N-1,) array-like

    The size of voxels of the output affine

    +
    +
    new_shape(N-1,) array-like, optional

    The extent of the (N-1) dimensions of the space described by the +new affine. If None, use shape.

    +
    +
    +
    +
    Returns:
    +
    +
    affine(N, N) array

    A new affine transform with the specified voxel sizes

    +
    +
    +
    +
    +
    + +
    +
    +

    to_matvec

    +
    +
    +nibabel.affines.to_matvec(transform)
    +

    Split a transform into its matrix and vector components

    +

    The transformation must be represented in homogeneous coordinates and is +split into its rotation matrix and translation vector components.

    +
    +
    Parameters:
    +
    +
    transformarray-like

    NxM transform matrix in homogeneous coordinates representing an affine +transformation from an (N-1)-dimensional space to an (M-1)-dimensional +space. An example is a 4x4 transform representing rotations and +translations in 3 dimensions. A 4x3 matrix can represent a +2-dimensional plane embedded in 3 dimensional space.

    +
    +
    +
    +
    Returns:
    +
    +
    matrix(N-1, M-1) array

    Matrix component of transform

    +
    +
    vector(M-1,) array

    Vector component of transform

    +
    +
    +
    +
    +
    +

    See also

    +
    +
    from_matvec
    +
    +
    +

    Examples

    +
    >>> aff = np.diag([2, 3, 4, 1])
    +>>> aff[:3,3] = [9, 10, 11]
    +>>> to_matvec(aff)
    +(array([[2, 0, 0],
    +       [0, 3, 0],
    +       [0, 0, 4]]), array([ 9, 10, 11]))
    +
    +
    +
    + +
    +
    +

    voxel_sizes

    +
    +
    +nibabel.affines.voxel_sizes(affine)
    +

    Return voxel size for each input axis given affine

    +

    The affine is the mapping between array (voxel) coordinates and mm +(world) coordinates.

    +

    The voxel size for the first voxel (array) axis is the distance moved in +world coordinates when moving one unit along the first voxel (array) axis. +This is the distance between the world coordinate of voxel (0, 0, 0) and +the world coordinate of voxel (1, 0, 0). The world coordinate vector of +voxel coordinate vector (0, 0, 0) is given by v0 = affine.dot((0, 0, 0, +1)[:3]. The world coordinate vector of voxel vector (1, 0, 0) is +v1_ax1 = affine.dot((1, 0, 0, 1))[:3]. The final 1 in the voxel +vectors and the [:3] at the end are because the affine works on +homogeneous coordinates. The translations part of the affine is trans = +affine[:3, 3], and the rotations, zooms and shearing part of the affine +is rzs = affine[:3, :3]. Because of the final 1 in the input voxel +vector, v0 == rzs.dot((0, 0, 0)) + trans, and v1_ax1 == rzs.dot((1, +0, 0)) + trans, and the difference vector is rzs.dot((0, 0, 0)) - +rzs.dot((1, 0, 0)) == rzs.dot((1, 0, 0)) == rzs[:, 0]. The distance +vectors in world coordinates between (0, 0, 0) and (1, 0, 0), (0, 1, 0), +(0, 0, 1) are given by rzs.dot(np.eye(3)) = rzs. The voxel sizes are +the Euclidean lengths of the distance vectors. So, the voxel sizes are +the Euclidean lengths of the columns of the affine (excluding the last row +and column of the affine).

    +
    +
    Parameters:
    +
    +
    affine2D array-like

    Affine transformation array. Usually shape (4, 4), but can be any 2D +array.

    +
    +
    +
    +
    Returns:
    +
    +
    vox_sizes1D array

    Voxel sizes for each input axis of affine. Usually 1D array length 3, +but in general has length (N-1) where input affine is shape (M, N).

    +
    +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.analyze.html b/reference/nibabel.analyze.html new file mode 100644 index 0000000000..05013694db --- /dev/null +++ b/reference/nibabel.analyze.html @@ -0,0 +1,993 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    analyze

    +

    Read / write access to the basic Mayo Analyze format

    +
    +

    The Analyze header format

    +

    This is a binary header format and inherits from WrapStruct

    +

    Apart from the attributes and methods of WrapStruct:

    +

    Class attributes are:

    +
    .default_x_flip
    +
    +
    +

    with methods:

    +
    .get/set_data_shape
    +.get/set_data_dtype
    +.get/set_zooms
    +.get/set_data_offset
    +.get_base_affine()
    +.get_best_affine()
    +.data_to_fileobj
    +.data_from_fileobj
    +
    +
    +

    and class methods:

    +
    .from_header(hdr)
    +
    +
    +

    More sophisticated headers can add more methods and attributes.

    +
    +

    Notes

    +

    This - basic - analyze header cannot encode full affines (only +diagonal affines), and cannot do integer scaling.

    +

    The inability to store affines means that we have to guess what orientation the +image has. Most Analyze images are stored on disk in (fastest-changing to +slowest-changing) R->L, P->A and I->S order. That is, the first voxel is the +rightmost, most posterior and most inferior voxel location in the image, and +the next voxel is one voxel towards the left of the image.

    +

    Most people refer to this disk storage format as ‘radiological’, on the basis +that, if you load up the data as an array img_arr where the first axis is +the fastest changing, then take a slice in the I->S axis - img_arr[:,:,10] +- then the right part of the brain will be on the left of your displayed slice. +Radiologists like looking at images where the left of the brain is on the right +side of the image.

    +

    Conversely, if the image has the voxels stored with the left voxels first - +L->R, P->A, I->S, then this would be ‘neurological’ format. Neurologists like +looking at images where the left side of the brain is on the left of the image.

    +

    When we are guessing at an affine for Analyze, this translates to the problem +of whether the affine should consider proceeding within the data down an X line +as being from left to right, or right to left.

    +

    By default we assume that the image is stored in R->L format. We encode this +choice in the default_x_flip flag that can be True or False. True means +assume radiological.

    +

    If the image is 3D, and the X, Y and Z zooms are x, y, and z, then:

    +
    if default_x_flip is True::
    +    affine = np.diag((-x,y,z,1))
    +else:
    +    affine = np.diag((x,y,z,1))
    +
    +
    +

    In our implementation, there is no way of saving this assumed flip into the +header. One way of doing this, that we have not used, is to allow negative +zooms, in particular, negative X zooms. We did not do this because the image +can be loaded with and without a default flip, so the saved zoom will not +constrain the affine.

    +
    +
    + + + + + + + + + +

    AnalyzeHeader([binaryblock, endianness, check])

    Class for basic analyze header

    AnalyzeImage(dataobj, affine[, header, ...])

    Class for basic Analyze format image

    +
    +

    AnalyzeHeader

    +
    +
    +class nibabel.analyze.AnalyzeHeader(binaryblock=None, endianness=None, check=True)
    +

    Bases: LabeledWrapStruct, SpatialHeader

    +

    Class for basic analyze header

    +

    Implements zoom-only setting of affine transform, and no image +scaling

    +

    Initialize header from binary data block

    +
    +
    Parameters:
    +
    +
    binaryblock{None, string} optional

    binary block to set into header. By default, None, in +which case we insert the default empty header block

    +
    +
    endianness{None, ‘<’,’>’, other endian code} string, optional

    endianness of the binaryblock. If None, guess endianness +from the data.

    +
    +
    checkbool, optional

    Whether to check content of header in initialization. +Default is True.

    +
    +
    +
    +
    +

    Examples

    +
    >>> hdr1 = AnalyzeHeader() # an empty header
    +>>> hdr1.endianness == native_code
    +True
    +>>> hdr1.get_data_shape()
    +(0,)
    +>>> hdr1.set_data_shape((1,2,3)) # now with some content
    +>>> hdr1.get_data_shape()
    +(1, 2, 3)
    +
    +
    +

    We can set the binary block directly via this initialization. +Here we get it from the header we have just made

    +
    >>> binblock2 = hdr1.binaryblock
    +>>> hdr2 = AnalyzeHeader(binblock2)
    +>>> hdr2.get_data_shape()
    +(1, 2, 3)
    +
    +
    +

    Empty headers are native endian by default

    +
    >>> hdr2.endianness == native_code
    +True
    +
    +
    +

    You can pass valid opposite endian headers with the +endianness parameter. Even empty headers can have +endianness

    +
    >>> hdr3 = AnalyzeHeader(endianness=swapped_code)
    +>>> hdr3.endianness == swapped_code
    +True
    +
    +
    +

    If you do not pass an endianness, and you pass some data, we +will try to guess from the passed data.

    +
    >>> binblock3 = hdr3.binaryblock
    +>>> hdr4 = AnalyzeHeader(binblock3)
    +>>> hdr4.endianness == swapped_code
    +True
    +
    +
    +
    +
    +__init__(binaryblock=None, endianness=None, check=True)
    +

    Initialize header from binary data block

    +
    +
    Parameters:
    +
    +
    binaryblock{None, string} optional

    binary block to set into header. By default, None, in +which case we insert the default empty header block

    +
    +
    endianness{None, ‘<’,’>’, other endian code} string, optional

    endianness of the binaryblock. If None, guess endianness +from the data.

    +
    +
    checkbool, optional

    Whether to check content of header in initialization. +Default is True.

    +
    +
    +
    +
    +

    Examples

    +
    >>> hdr1 = AnalyzeHeader() # an empty header
    +>>> hdr1.endianness == native_code
    +True
    +>>> hdr1.get_data_shape()
    +(0,)
    +>>> hdr1.set_data_shape((1,2,3)) # now with some content
    +>>> hdr1.get_data_shape()
    +(1, 2, 3)
    +
    +
    +

    We can set the binary block directly via this initialization. +Here we get it from the header we have just made

    +
    >>> binblock2 = hdr1.binaryblock
    +>>> hdr2 = AnalyzeHeader(binblock2)
    +>>> hdr2.get_data_shape()
    +(1, 2, 3)
    +
    +
    +

    Empty headers are native endian by default

    +
    >>> hdr2.endianness == native_code
    +True
    +
    +
    +

    You can pass valid opposite endian headers with the +endianness parameter. Even empty headers can have +endianness

    +
    >>> hdr3 = AnalyzeHeader(endianness=swapped_code)
    +>>> hdr3.endianness == swapped_code
    +True
    +
    +
    +

    If you do not pass an endianness, and you pass some data, we +will try to guess from the passed data.

    +
    >>> binblock3 = hdr3.binaryblock
    +>>> hdr4 = AnalyzeHeader(binblock3)
    +>>> hdr4.endianness == swapped_code
    +True
    +
    +
    +
    + +
    +
    +as_analyze_map()
    +

    Return header as mapping for conversion to Analyze types

    +

    Collect data from custom header type to fill in fields for Analyze and +derived header types (such as Nifti1 and Nifti2).

    +

    When Analyze types convert another header type to their own type, they +call this this method to check if there are other Analyze / Nifti +fields that the source header would like to set.

    +
    +
    Returns:
    +
    +
    analyze_mapmapping

    Object that can be used as a mapping thus:

    +
    for key in analyze_map:
    +    value = analyze_map[key]
    +
    +
    +

    where key is the name of a field that can be set in an Analyze +header type, such as Nifti1, and value is a value for the +field. For example, analyze_map might be a something like +dict(regular='y', slice_duration=0.3) where regular is a +field present in both Analyze and Nifti1, and slice_duration is +a field restricted to Nifti1 and Nifti2. If a particular Analyze +header type does not recognize the field name, it will throw away +the value without error. See Analyze.from_header().

    +
    +
    +
    +
    +

    Notes

    +

    You can also return a Nifti header with the relevant fields set.

    +

    Your header still needs methods get_data_dtype, get_data_shape +and get_zooms, for the conversion, and these get called after +using the analyze map, so the methods will override values set in the +map.

    +
    + +
    +
    +data_from_fileobj(fileobj)
    +

    Read scaled data array from fileobj

    +

    Use this routine to get the scaled image data from an image file +fileobj, given a header self. “Scaled” means, with any header +scaling factors applied to the raw data in the file. Use +raw_data_from_fileobj to get the raw data.

    +
    +
    Parameters:
    +
    +
    fileobjfile-like

    Must be open, and implement read and seek methods

    +
    +
    +
    +
    Returns:
    +
    +
    arrndarray

    scaled data array

    +
    +
    +
    +
    +

    Notes

    +

    We use the header to get any scale or intercept values to apply to the +data. Raw Analyze files don’t have scale factors or intercepts, but +this routine also works with formats based on Analyze, that do have +scaling, such as SPM analyze formats and NIfTI.

    +
    + +
    +
    +data_to_fileobj(data, fileobj, rescale=True)
    +

    Write data to fileobj, maybe rescaling data, modifying self

    +

    In writing the data, we match the header to the written data, by +setting the header scaling factors, iff rescale is True. Thus we +modify self in the process of writing the data.

    +
    +
    Parameters:
    +
    +
    dataarray-like

    data to write; should match header defined shape

    +
    +
    fileobjfile-like object

    Object with file interface, implementing write and +seek

    +
    +
    rescale{True, False}, optional

    Whether to try and rescale data to match output dtype specified by +header. If True and scaling needed and header cannot scale, then +raise HeaderTypeError.

    +
    +
    +
    +
    +

    Examples

    +
    >>> from nibabel.analyze import AnalyzeHeader
    +>>> hdr = AnalyzeHeader()
    +>>> hdr.set_data_shape((1, 2, 3))
    +>>> hdr.set_data_dtype(np.float64)
    +>>> from io import BytesIO
    +>>> str_io = BytesIO()
    +>>> data = np.arange(6).reshape(1,2,3)
    +>>> hdr.data_to_fileobj(data, str_io)
    +>>> data.astype(np.float64).tobytes('F') == str_io.getvalue()
    +True
    +
    +
    +
    + +
    +
    +classmethod default_structarr(endianness=None)
    +

    Return header data for empty header with given endianness

    +
    + +
    +
    +default_x_flip: bool = True
    +
    + +
    +
    +classmethod from_header(header=None, check=True)
    +

    Class method to create header from another header

    +
    +
    Parameters:
    +
    +
    headerHeader instance or mapping

    a header of this class, or another class of header for +conversion to this type

    +
    +
    check{True, False}

    whether to check header for integrity

    +
    +
    +
    +
    Returns:
    +
    +
    hdrheader instance

    fresh header instance of our own class

    +
    +
    +
    +
    +
    + +
    +
    +get_base_affine()
    +

    Get affine from basic (shared) header fields

    +

    Note that we get the translations from the center of the +image.

    +

    Examples

    +
    >>> hdr = AnalyzeHeader()
    +>>> hdr.set_data_shape((3, 5, 7))
    +>>> hdr.set_zooms((3, 2, 1))
    +>>> hdr.default_x_flip
    +True
    +>>> hdr.get_base_affine() # from center of image
    +array([[-3.,  0.,  0.,  3.],
    +       [ 0.,  2.,  0., -4.],
    +       [ 0.,  0.,  1., -3.],
    +       [ 0.,  0.,  0.,  1.]])
    +
    +
    +
    + +
    +
    +get_best_affine()
    +

    Get affine from basic (shared) header fields

    +

    Note that we get the translations from the center of the +image.

    +

    Examples

    +
    >>> hdr = AnalyzeHeader()
    +>>> hdr.set_data_shape((3, 5, 7))
    +>>> hdr.set_zooms((3, 2, 1))
    +>>> hdr.default_x_flip
    +True
    +>>> hdr.get_base_affine() # from center of image
    +array([[-3.,  0.,  0.,  3.],
    +       [ 0.,  2.,  0., -4.],
    +       [ 0.,  0.,  1., -3.],
    +       [ 0.,  0.,  0.,  1.]])
    +
    +
    +
    + +
    +
    +get_data_dtype()
    +

    Get numpy dtype for data

    +

    For examples see set_data_dtype

    +
    + +
    +
    +get_data_offset()
    +

    Return offset into data file to read data

    +

    Examples

    +
    >>> hdr = AnalyzeHeader()
    +>>> hdr.get_data_offset()
    +0
    +>>> hdr['vox_offset'] = 12
    +>>> hdr.get_data_offset()
    +12
    +
    +
    +
    + +
    +
    +get_data_shape()
    +

    Get shape of data

    +

    Examples

    +
    >>> hdr = AnalyzeHeader()
    +>>> hdr.get_data_shape()
    +(0,)
    +>>> hdr.set_data_shape((1,2,3))
    +>>> hdr.get_data_shape()
    +(1, 2, 3)
    +
    +
    +

    Expanding number of dimensions gets default zooms

    +
    >>> hdr.get_zooms()
    +(1.0, 1.0, 1.0)
    +
    +
    +
    + +
    +
    +get_slope_inter()
    +

    Get scalefactor and intercept

    +

    These are not implemented for basic Analyze

    +
    + +
    +
    +get_zooms()
    +

    Get zooms from header

    +
    +
    Returns:
    +
    +
    ztuple

    tuple of header zoom values

    +
    +
    +
    +
    +

    Examples

    +
    >>> hdr = AnalyzeHeader()
    +>>> hdr.get_zooms()
    +(1.0,)
    +>>> hdr.set_data_shape((1,2))
    +>>> hdr.get_zooms()
    +(1.0, 1.0)
    +>>> hdr.set_zooms((3, 4))
    +>>> hdr.get_zooms()
    +(3.0, 4.0)
    +
    +
    +
    + +
    +
    +classmethod guessed_endian(hdr)
    +

    Guess intended endianness from mapping-like hdr

    +
    +
    Parameters:
    +
    +
    hdrmapping-like

    hdr for which to guess endianness

    +
    +
    +
    +
    Returns:
    +
    +
    endianness{‘<’, ‘>’}

    Guessed endianness of header

    +
    +
    +
    +
    +

    Examples

    +

    Zeros header, no information, guess native

    +
    >>> hdr = AnalyzeHeader()
    +>>> hdr_data = np.zeros((), dtype=header_dtype)
    +>>> AnalyzeHeader.guessed_endian(hdr_data) == native_code
    +True
    +
    +
    +

    A valid native header is guessed native

    +
    >>> hdr_data = hdr.structarr.copy()
    +>>> AnalyzeHeader.guessed_endian(hdr_data) == native_code
    +True
    +
    +
    +

    And, when swapped, is guessed as swapped

    +
    >>> sw_hdr_data = hdr_data.byteswap(swapped_code)
    +>>> AnalyzeHeader.guessed_endian(sw_hdr_data) == swapped_code
    +True
    +
    +
    +

    The algorithm is as follows:

    +

    First, look at the first value in the dim field; this +should be between 0 and 7. If it is between 1 and 7, then +this must be a native endian header.

    +
    >>> hdr_data = np.zeros((), dtype=header_dtype) # blank binary data
    +>>> hdr_data['dim'][0] = 1
    +>>> AnalyzeHeader.guessed_endian(hdr_data) == native_code
    +True
    +>>> hdr_data['dim'][0] = 6
    +>>> AnalyzeHeader.guessed_endian(hdr_data) == native_code
    +True
    +>>> hdr_data['dim'][0] = -1
    +>>> AnalyzeHeader.guessed_endian(hdr_data) == swapped_code
    +True
    +
    +
    +

    If the first dim value is zeros, we need a tie breaker. +In that case we check the sizeof_hdr field. This should +be 348. If it looks like the byteswapped value of 348, +assumed swapped. Otherwise assume native.

    +
    >>> hdr_data = np.zeros((), dtype=header_dtype) # blank binary data
    +>>> AnalyzeHeader.guessed_endian(hdr_data) == native_code
    +True
    +>>> hdr_data['sizeof_hdr'] = 1543569408
    +>>> AnalyzeHeader.guessed_endian(hdr_data) == swapped_code
    +True
    +>>> hdr_data['sizeof_hdr'] = -1
    +>>> AnalyzeHeader.guessed_endian(hdr_data) == native_code
    +True
    +
    +
    +

    This is overridden by the dim[0] value though:

    +
    >>> hdr_data['sizeof_hdr'] = 1543569408
    +>>> hdr_data['dim'][0] = 1
    +>>> AnalyzeHeader.guessed_endian(hdr_data) == native_code
    +True
    +
    +
    +
    + +
    +
    +has_data_intercept = False
    +
    + +
    +
    +has_data_slope = False
    +
    + +
    +
    +classmethod may_contain_header(binaryblock)
    +
    + +
    +
    +raw_data_from_fileobj(fileobj)
    +

    Read unscaled data array from fileobj

    +
    +
    Parameters:
    +
    +
    fileobjfile-like

    Must be open, and implement read and seek methods

    +
    +
    +
    +
    Returns:
    +
    +
    arrndarray

    unscaled data array

    +
    +
    +
    +
    +
    + +
    +
    +set_data_dtype(datatype)
    +

    Set numpy dtype for data from code or dtype or type

    +

    Examples

    +
    >>> hdr = AnalyzeHeader()
    +>>> hdr.set_data_dtype(np.uint8)
    +>>> hdr.get_data_dtype()
    +dtype('uint8')
    +>>> hdr.set_data_dtype(np.dtype(np.uint8))
    +>>> hdr.get_data_dtype()
    +dtype('uint8')
    +>>> hdr.set_data_dtype('implausible') 
    +Traceback (most recent call last):
    +   ...
    +HeaderDataError: data dtype "implausible" not recognized
    +>>> hdr.set_data_dtype('none') 
    +Traceback (most recent call last):
    +   ...
    +HeaderDataError: data dtype "none" known but not supported
    +>>> hdr.set_data_dtype(np.void) 
    +Traceback (most recent call last):
    +   ...
    +HeaderDataError: data dtype "<type 'numpy.void'>" known but not supported
    +
    +
    +
    + +
    +
    +set_data_offset(offset)
    +

    Set offset into data file to read data

    +
    + +
    +
    +set_data_shape(shape)
    +

    Set shape of data

    +

    If ndims == len(shape) then we set zooms for dimensions higher than +ndims to 1.0

    +
    +
    Parameters:
    +
    +
    shapesequence

    sequence of integers specifying data array shape

    +
    +
    +
    +
    +
    + +
    +
    +set_slope_inter(slope, inter=None)
    +

    Set slope and / or intercept into header

    +

    Set slope and intercept for image data, such that, if the image +data is arr, then the scaled image data will be (arr * +slope) + inter

    +

    In this case, for Analyze images, we can’t store the slope or the +intercept, so this method only checks that slope is None or NaN or +1.0, and that inter is None or NaN or 0.

    +
    +
    Parameters:
    +
    +
    slopeNone or float

    If float, value must be NaN or 1.0 or we raise a HeaderTypeError

    +
    +
    interNone or float, optional

    If float, value must be 0.0 or we raise a HeaderTypeError

    +
    +
    +
    +
    +
    + +
    +
    +set_zooms(zooms)
    +

    Set zooms into header fields

    +

    See docstring for get_zooms for examples

    +
    + +
    +
    +sizeof_hdr = 348
    +
    + +
    +
    +template_dtype = dtype([('sizeof_hdr', '<i4'), ('data_type', 'S10'), ('db_name', 'S18'), ('extents', '<i4'), ('session_error', '<i2'), ('regular', 'S1'), ('hkey_un0', 'S1'), ('dim', '<i2', (8,)), ('vox_units', 'S4'), ('cal_units', 'S8'), ('unused1', '<i2'), ('datatype', '<i2'), ('bitpix', '<i2'), ('dim_un0', '<i2'), ('pixdim', '<f4', (8,)), ('vox_offset', '<f4'), ('funused1', '<f4'), ('funused2', '<f4'), ('funused3', '<f4'), ('cal_max', '<f4'), ('cal_min', '<f4'), ('compressed', '<i4'), ('verified', '<i4'), ('glmax', '<i4'), ('glmin', '<i4'), ('descrip', 'S80'), ('aux_file', 'S24'), ('orient', 'S1'), ('originator', 'S10'), ('generated', 'S10'), ('scannum', 'S10'), ('patient_id', 'S10'), ('exp_date', 'S10'), ('exp_time', 'S10'), ('hist_un0', 'S3'), ('views', '<i4'), ('vols_added', '<i4'), ('start_field', '<i4'), ('field_skip', '<i4'), ('omax', '<i4'), ('omin', '<i4'), ('smax', '<i4'), ('smin', '<i4')])
    +
    + +
    + +
    +
    +

    AnalyzeImage

    +
    +
    +class nibabel.analyze.AnalyzeImage(dataobj, affine, header=None, extra=None, file_map=None, dtype=None)
    +

    Bases: SpatialImage

    +

    Class for basic Analyze format image

    +

    Initialize image

    +

    The image is a combination of (array-like, affine matrix, header), with +optional metadata in extra, and filename / file-like objects +contained in the file_map mapping.

    +
    +
    Parameters:
    +
    +
    dataobjobject

    Object containing image data. It should be some object that returns an +array from np.asanyarray. It should have a shape attribute +or property

    +
    +
    affineNone or (4,4) array-like

    homogeneous affine giving relationship between voxel coordinates and +world coordinates. Affine can also be None. In this case, +obj.affine also returns None, and the affine as written to disk +will depend on the file format.

    +
    +
    headerNone or mapping or header instance, optional

    metadata for this image format

    +
    +
    extraNone or mapping, optional

    metadata to associate with image that cannot be stored in the +metadata of this image type

    +
    +
    file_mapmapping, optional

    mapping giving file information for this image format

    +
    +
    +
    +
    +
    +
    +__init__(dataobj, affine, header=None, extra=None, file_map=None, dtype=None)
    +

    Initialize image

    +

    The image is a combination of (array-like, affine matrix, header), with +optional metadata in extra, and filename / file-like objects +contained in the file_map mapping.

    +
    +
    Parameters:
    +
    +
    dataobjobject

    Object containing image data. It should be some object that returns an +array from np.asanyarray. It should have a shape attribute +or property

    +
    +
    affineNone or (4,4) array-like

    homogeneous affine giving relationship between voxel coordinates and +world coordinates. Affine can also be None. In this case, +obj.affine also returns None, and the affine as written to disk +will depend on the file format.

    +
    +
    headerNone or mapping or header instance, optional

    metadata for this image format

    +
    +
    extraNone or mapping, optional

    metadata to associate with image that cannot be stored in the +metadata of this image type

    +
    +
    file_mapmapping, optional

    mapping giving file information for this image format

    +
    +
    +
    +
    +
    + +
    +
    +ImageArrayProxy
    +

    alias of ArrayProxy

    +
    + +
    +
    +files_types: tuple[tuple[str, str], ...] = (('image', '.img'), ('header', '.hdr'))
    +
    + +
    +
    +classmethod from_file_map(file_map, *, mmap=True, keep_file_open=None)
    +

    Class method to create image from mapping in file_map

    +
    +
    Parameters:
    +
    +
    file_mapdict

    Mapping with (key, value) pairs of (file_type, FileHolder +instance giving file-likes for each file needed for this image +type.

    +
    +
    mmap{True, False, ‘c’, ‘r’}, optional, keyword only

    mmap controls the use of numpy memory mapping for reading image +array data. If False, do not try numpy memmap for data array. +If one of {‘c’, ‘r’}, try numpy memmap with mode=mmap. A +mmap value of True gives the same behavior as mmap='c'. If +image data file cannot be memory-mapped, ignore mmap value and +read array from file.

    +
    +
    keep_file_open{ None, True, False }, optional, keyword only

    keep_file_open controls whether a new file handle is created +every time the image is accessed, or a single file handle is +created and used for the lifetime of this ArrayProxy. If +True, a single file handle is created and used. If False, +a new file handle is created every time the image is accessed. +If file_map refers to an open file handle, this setting has no +effect. The default value (None) will result in the value of +nibabel.arrayproxy.KEEP_FILE_OPEN_DEFAULT being used.

    +
    +
    +
    +
    Returns:
    +
    +
    imgAnalyzeImage instance
    +
    +
    +
    +
    + +
    +
    +get_data_dtype()
    +
    + +
    +
    +header_class
    +

    alias of AnalyzeHeader

    +
    + +
    +
    +makeable: bool = True
    +
    + +
    +
    +rw: bool = True
    +
    + +
    +
    +set_data_dtype(dtype)
    +
    + +
    +
    +to_file_map(file_map=None, dtype=None)
    +

    Write image to file_map or contained self.file_map

    +
    +
    Parameters:
    +
    +
    file_mapNone or mapping, optional

    files mapping. If None (default) use object’s file_map +attribute instead

    +
    +
    dtypedtype-like, optional

    The on-disk data type to coerce the data array.

    +
    +
    +
    +
    +
    + +
    +
    +valid_exts: tuple[str, ...] = ('.img', '.hdr')
    +
    + +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.arrayproxy.html b/reference/nibabel.arrayproxy.html new file mode 100644 index 0000000000..6bf032d2d9 --- /dev/null +++ b/reference/nibabel.arrayproxy.html @@ -0,0 +1,432 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    arrayproxy

    +

    Array proxy base class

    +

    The proxy API is - at minimum:

    +
      +
    • The object has a read-only attribute shape

    • +
    • read only is_proxy attribute / property set to True

    • +
    • the object returns the data array from np.asarray(prox)

    • +
    • returns array slice from prox[<slice_spec>] where <slice_spec> is any +ndarray slice specification that does not use numpy ‘advanced indexing’.

    • +
    • modifying no object outside obj will affect the result of +np.asarray(obj). Specifically:

      +
        +
      • Changes in position (obj.tell()) of passed file-like objects will +not affect the output of from np.asarray(proxy).

      • +
      • if you pass a header into the __init__, then modifying the original +header will not affect the result of the array return.

      • +
      +
    • +
    +

    See nibabel.tests.test_proxy_api for proxy API conformance checks.

    + + + + + + + + + + + + + + + + + + +

    ArrayLike(*args, **kwargs)

    Protocol for numpy ndarray-like objects

    ArrayProxy(file_like, spec, *[, mmap, ...])

    Class to act as proxy for the array that can be read from a file

    get_obj_dtype(obj)

    Get the effective dtype of an array-like object

    is_proxy(obj)

    Return True if obj is an array proxy

    reshape_dataobj(obj, shape)

    Use obj reshape method if possible, else numpy reshape function

    +
    +

    ArrayLike

    +
    +
    +class nibabel.arrayproxy.ArrayLike(*args, **kwargs)
    +

    Bases: Protocol

    +

    Protocol for numpy ndarray-like objects

    +

    This is more stringent than numpy.typing.ArrayLike, but guarantees +access to shape, ndim and slicing.

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    +
    +property ndim: int
    +
    + +
    +
    +shape: tuple[int, ...]
    +
    + +
    + +
    +
    +

    ArrayProxy

    +
    +
    +class nibabel.arrayproxy.ArrayProxy(file_like, spec, *, mmap=True, order=None, keep_file_open=None)
    +

    Bases: ArrayLike

    +

    Class to act as proxy for the array that can be read from a file

    +

    The array proxy allows us to freeze the passed fileobj and header such that +it returns the expected data array.

    +

    This implementation assumes a contiguous array in the file object, with one +of the numpy dtypes, starting at a given file position offset with +single slope and intercept scaling to produce output values.

    +

    The class __init__ requires a spec which defines how the data will be +read and rescaled. The spec may be a tuple of length 2 - 5, containing the +shape, storage dtype, offset, slope and intercept, or a header object +with methods:

    +
      +
    • get_data_shape

    • +
    • get_data_dtype

    • +
    • get_data_offset

    • +
    • get_slope_inter

    • +
    +

    A header should also have a ‘copy’ method. This requirement will go away +when the deprecated ‘header’ property goes away.

    +

    This implementation allows us to deal with Analyze and its variants, +including Nifti1, and with the MGH format.

    +

    Other image types might need more specific classes to implement the API. +See nibabel.minc1, nibabel.ecat and nibabel.parrec for +examples.

    +

    Initialize array proxy instance

    +
    +
    Parameters:
    +
    +
    file_likeobject

    File-like object or filename. If file-like object, should implement +at least read and seek.

    +
    +
    specobject or tuple

    Tuple must have length 2-5, with the following values:

    +
      +
    1. shape: tuple - tuple of ints describing shape of data;

    2. +
    3. storage_dtype: dtype specifier - dtype of array inside proxied +file, or input to numpy.dtype to specify array dtype;

    4. +
    5. offset: int - offset, in bytes, of data array from start of file +(default: 0);

    6. +
    7. slope: float - scaling factor for resulting data (default: 1.0);

    8. +
    9. inter: float - intercept for rescaled data (default: 0.0).

    10. +
    +

    OR

    +

    Header object implementing get_data_shape, get_data_dtype, +get_data_offset, get_slope_inter

    +
    +
    mmap{True, False, ‘c’, ‘r’}, optional, keyword only

    mmap controls the use of numpy memory mapping for reading data. +If False, do not try numpy memmap for data array. If one of +{‘c’, ‘r’}, try numpy memmap with mode=mmap. A mmap value of +True gives the same behavior as mmap='c'. If file_like +cannot be memory-mapped, ignore mmap value and read array from +file.

    +
    +
    order{None, ‘F’, ‘C’}, optional, keyword only

    order controls the order of the data array layout. Fortran-style, +column-major order may be indicated with ‘F’, and C-style, row-major +order may be indicated with ‘C’. None gives the default order, that +comes from the _default_order class variable.

    +
    +
    keep_file_open{ None, True, False }, optional, keyword only

    keep_file_open controls whether a new file handle is created +every time the image is accessed, or a single file handle is +created and used for the lifetime of this ArrayProxy. If +True, a single file handle is created and used. If False, +a new file handle is created every time the image is accessed. +If file_like is an open file handle, this setting has no +effect. The default value (None) will result in the value of +KEEP_FILE_OPEN_DEFAULT being used.

    +
    +
    +
    +
    +
    +
    +__init__(file_like, spec, *, mmap=True, order=None, keep_file_open=None)
    +

    Initialize array proxy instance

    +
    +
    Parameters:
    +
    +
    file_likeobject

    File-like object or filename. If file-like object, should implement +at least read and seek.

    +
    +
    specobject or tuple

    Tuple must have length 2-5, with the following values:

    +
      +
    1. shape: tuple - tuple of ints describing shape of data;

    2. +
    3. storage_dtype: dtype specifier - dtype of array inside proxied +file, or input to numpy.dtype to specify array dtype;

    4. +
    5. offset: int - offset, in bytes, of data array from start of file +(default: 0);

    6. +
    7. slope: float - scaling factor for resulting data (default: 1.0);

    8. +
    9. inter: float - intercept for rescaled data (default: 0.0).

    10. +
    +

    OR

    +

    Header object implementing get_data_shape, get_data_dtype, +get_data_offset, get_slope_inter

    +
    +
    mmap{True, False, ‘c’, ‘r’}, optional, keyword only

    mmap controls the use of numpy memory mapping for reading data. +If False, do not try numpy memmap for data array. If one of +{‘c’, ‘r’}, try numpy memmap with mode=mmap. A mmap value of +True gives the same behavior as mmap='c'. If file_like +cannot be memory-mapped, ignore mmap value and read array from +file.

    +
    +
    order{None, ‘F’, ‘C’}, optional, keyword only

    order controls the order of the data array layout. Fortran-style, +column-major order may be indicated with ‘F’, and C-style, row-major +order may be indicated with ‘C’. None gives the default order, that +comes from the _default_order class variable.

    +
    +
    keep_file_open{ None, True, False }, optional, keyword only

    keep_file_open controls whether a new file handle is created +every time the image is accessed, or a single file handle is +created and used for the lifetime of this ArrayProxy. If +True, a single file handle is created and used. If False, +a new file handle is created every time the image is accessed. +If file_like is an open file handle, this setting has no +effect. The default value (None) will result in the value of +KEEP_FILE_OPEN_DEFAULT being used.

    +
    +
    +
    +
    +
    + +
    +
    +copy() Self
    +

    Create a new ArrayProxy for the same file and parameters

    +

    If the proxied file is an open file handle, the new ArrayProxy +will share a lock with the old one.

    +
    + +
    +
    +property dtype
    +
    + +
    +
    +get_unscaled()
    +

    Read data from file

    +

    This is an optional part of the proxy API

    +
    + +
    +
    +property inter
    +
    + +
    +
    +property is_proxy
    +
    + +
    +
    +property ndim
    +
    + +
    +
    +property offset
    +
    + +
    +
    +reshape(shape)
    +

    Return an ArrayProxy with a new shape, without modifying data

    +
    + +
    +
    +property shape
    +
    + +
    +
    +property slope
    +
    + +
    + +
    +
    +

    get_obj_dtype

    +
    +
    +nibabel.arrayproxy.get_obj_dtype(obj)
    +

    Get the effective dtype of an array-like object

    +
    + +
    +
    +

    is_proxy

    +
    +
    +nibabel.arrayproxy.is_proxy(obj)
    +

    Return True if obj is an array proxy

    +
    + +
    +
    +

    reshape_dataobj

    +
    +
    +nibabel.arrayproxy.reshape_dataobj(obj, shape)
    +

    Use obj reshape method if possible, else numpy reshape function

    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.arraywriters.html b/reference/nibabel.arraywriters.html new file mode 100644 index 0000000000..a153d7cd34 --- /dev/null +++ b/reference/nibabel.arraywriters.html @@ -0,0 +1,790 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    arraywriters

    +

    Array writer objects

    +

    Array writers have init signature:

    +
    def __init__(self, array, out_dtype=None)
    +
    +
    +

    and methods

    +
      +
    • scaling_needed() - returns True if array requires scaling for write

    • +
    • finite_range() - returns min, max of self.array

    • +
    • to_fileobj(fileobj, offset=None, order=’F’)

    • +
    +

    They must have attributes / properties of:

    +
      +
    • array

    • +
    • out_dtype

    • +
    • has_nan

    • +
    +

    They may have attributes:

    +
      +
    • slope

    • +
    • inter

    • +
    +

    They are designed to write arrays to a fileobj with reasonable memory +efficiency.

    +

    Array writers may be able to scale the array or apply an intercept, or do +something else to make sense of conversions between float and int, or between +larger ints and smaller.

    + + + + + + + + + + + + + + + + + + + + + + + + +

    ArrayWriter(array[, out_dtype])

    Initialize array writer

    ScalingError

    SlopeArrayWriter(array[, out_dtype, ...])

    ArrayWriter that can use scalefactor for writing arrays

    SlopeInterArrayWriter(array[, out_dtype, ...])

    Array writer that can use slope and intercept to scale array

    WriterError

    get_slope_inter(writer)

    Return slope, intercept from array writer object

    make_array_writer(data, out_type[, ...])

    Make array writer instance for array data and output type out_type

    +
    +

    ArrayWriter

    +
    +
    +class nibabel.arraywriters.ArrayWriter(array, out_dtype=None, **kwargs)
    +

    Bases: object

    +

    Initialize array writer

    +
    +
    Parameters:
    +
    +
    arrayarray-like

    array-like object

    +
    +
    out_dtypeNone or dtype

    dtype with which array will be written. For this class, +out_dtype` needs to be the same as the dtype of the input array +or a swapped version of the same.

    +
    +
    **kwargskeyword arguments

    This class processes only:

    +
      +
    • nan2zero : bool, optional +Whether to set NaN values to 0 when writing integer output. +Defaults to True. If False, NaNs get converted with numpy +astype, and the behavior is undefined. Ignored for floating +point output.

    • +
    • check_scaling : bool, optional +If True, check if scaling needed and raise error if so. Default +is True

    • +
    +
    +
    +
    +
    +

    Examples

    +
    >>> arr = np.array([0, 255], np.uint8)
    +>>> aw = ArrayWriter(arr)
    +>>> aw = ArrayWriter(arr, np.int8) 
    +Traceback (most recent call last):
    +    ...
    +WriterError: Scaling needed but cannot scale
    +>>> aw = ArrayWriter(arr, np.int8, check_scaling=False)
    +
    +
    +
    +
    +__init__(array, out_dtype=None, **kwargs)
    +

    Initialize array writer

    +
    +
    Parameters:
    +
    +
    arrayarray-like

    array-like object

    +
    +
    out_dtypeNone or dtype

    dtype with which array will be written. For this class, +out_dtype` needs to be the same as the dtype of the input array +or a swapped version of the same.

    +
    +
    **kwargskeyword arguments

    This class processes only:

    +
      +
    • nan2zero : bool, optional +Whether to set NaN values to 0 when writing integer output. +Defaults to True. If False, NaNs get converted with numpy +astype, and the behavior is undefined. Ignored for floating +point output.

    • +
    • check_scaling : bool, optional +If True, check if scaling needed and raise error if so. Default +is True

    • +
    +
    +
    +
    +
    +

    Examples

    +
    >>> arr = np.array([0, 255], np.uint8)
    +>>> aw = ArrayWriter(arr)
    +>>> aw = ArrayWriter(arr, np.int8) 
    +Traceback (most recent call last):
    +    ...
    +WriterError: Scaling needed but cannot scale
    +>>> aw = ArrayWriter(arr, np.int8, check_scaling=False)
    +
    +
    +
    + +
    +
    +property array
    +

    Return array from arraywriter

    +
    + +
    +
    +finite_range()
    +

    Return (maybe cached) finite range of data array

    +
    + +
    +
    +property has_nan
    +

    True if array has NaNs

    +
    + +
    +
    +property out_dtype
    +

    Return out_dtype from arraywriter

    +
    + +
    +
    +scaling_needed()
    +

    Checks if scaling is needed for input array

    +

    Raises WriterError if no scaling possible.

    +

    The rules are in the code, but:

    +
      +
    • If numpy will cast, return False (no scaling needed)

    • +
    • If input or output is an object or structured type, raise

    • +
    • If input is complex, raise

    • +
    • If the output is float, return False

    • +
    • If the input array is all zero, return False

    • +
    • By now we are casting to (u)int. If the input type is a float, return +True (we do need scaling)

    • +
    • Now input and output types are (u)ints. If the min and max in the +data are within range of the output type, return False

    • +
    • Otherwise return True

    • +
    +
    + +
    +
    +to_fileobj(fileobj, order='F')
    +

    Write array into fileobj

    +
    +
    Parameters:
    +
    +
    fileobjfile-like object
    +
    order{‘F’, ‘C’}

    order (Fortran or C) to which to write array

    +
    +
    +
    +
    +
    + +
    + +
    +
    +

    ScalingError

    +
    +
    +class nibabel.arraywriters.ScalingError
    +

    Bases: WriterError

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    + +
    +
    +

    SlopeArrayWriter

    +
    +
    +class nibabel.arraywriters.SlopeArrayWriter(array, out_dtype=None, calc_scale=True, scaler_dtype=<class 'numpy.float32'>, **kwargs)
    +

    Bases: ArrayWriter

    +

    ArrayWriter that can use scalefactor for writing arrays

    +

    The scalefactor allows the array writer to write floats to int output +types, and rescale larger ints to smaller. It can therefore lose +precision.

    +

    It extends the ArrayWriter class with attribute:

    +
      +
    • slope

    • +
    +

    and methods:

    +
      +
    • reset() - reset slope to default (not adapted to self.array)

    • +
    • calc_scale() - calculate slope to best write self.array

    • +
    +

    Initialize array writer

    +
    +
    Parameters:
    +
    +
    arrayarray-like

    array-like object

    +
    +
    out_dtypeNone or dtype

    dtype with which array will be written. For this class, +out_dtype` needs to be the same as the dtype of the input array +or a swapped version of the same.

    +
    +
    calc_scale{True, False}, optional

    Whether to calculate scaling for writing array on initialization. +If False, then you can calculate this scaling with +obj.calc_scale() - see examples

    +
    +
    scaler_dtypedtype-like, optional

    specifier for numpy dtype for scaling

    +
    +
    **kwargskeyword arguments

    This class processes only:

    +
      +
    • nan2zero : bool, optional +Whether to set NaN values to 0 when writing integer output. +Defaults to True. If False, NaNs get converted with numpy +astype, and the behavior is undefined. Ignored for floating +point output.

    • +
    +
    +
    +
    +
    +

    Examples

    +
    >>> arr = np.array([0, 254], np.uint8)
    +>>> aw = SlopeArrayWriter(arr)
    +>>> aw.slope
    +1.0
    +>>> aw = SlopeArrayWriter(arr, np.int8)
    +>>> aw.slope
    +2.0
    +>>> aw = SlopeArrayWriter(arr, np.int8, calc_scale=False)
    +>>> aw.slope
    +1.0
    +>>> aw.calc_scale()
    +>>> aw.slope
    +2.0
    +
    +
    +
    +
    +__init__(array, out_dtype=None, calc_scale=True, scaler_dtype=<class 'numpy.float32'>, **kwargs)
    +

    Initialize array writer

    +
    +
    Parameters:
    +
    +
    arrayarray-like

    array-like object

    +
    +
    out_dtypeNone or dtype

    dtype with which array will be written. For this class, +out_dtype` needs to be the same as the dtype of the input array +or a swapped version of the same.

    +
    +
    calc_scale{True, False}, optional

    Whether to calculate scaling for writing array on initialization. +If False, then you can calculate this scaling with +obj.calc_scale() - see examples

    +
    +
    scaler_dtypedtype-like, optional

    specifier for numpy dtype for scaling

    +
    +
    **kwargskeyword arguments

    This class processes only:

    +
      +
    • nan2zero : bool, optional +Whether to set NaN values to 0 when writing integer output. +Defaults to True. If False, NaNs get converted with numpy +astype, and the behavior is undefined. Ignored for floating +point output.

    • +
    +
    +
    +
    +
    +

    Examples

    +
    >>> arr = np.array([0, 254], np.uint8)
    +>>> aw = SlopeArrayWriter(arr)
    +>>> aw.slope
    +1.0
    +>>> aw = SlopeArrayWriter(arr, np.int8)
    +>>> aw.slope
    +2.0
    +>>> aw = SlopeArrayWriter(arr, np.int8, calc_scale=False)
    +>>> aw.slope
    +1.0
    +>>> aw.calc_scale()
    +>>> aw.slope
    +2.0
    +
    +
    +
    + +
    +
    +calc_scale(force=False)
    +

    Calculate / set scaling for floats/(u)ints to (u)ints

    +
    + +
    +
    +reset()
    +

    Set object to values before any scaling calculation

    +
    + +
    +
    +scaling_needed()
    +

    Checks if scaling is needed for input array

    +

    Raises WriterError if no scaling possible.

    +

    The rules are in the code, but:

    +
      +
    • If numpy will cast, return False (no scaling needed)

    • +
    • If input or output is an object or structured type, raise

    • +
    • If input is complex, raise

    • +
    • If the output is float, return False

    • +
    • If the input array is all zero, return False

    • +
    • If there is no finite value, return False (the writer will strip the +non-finite values)

    • +
    • By now we are casting to (u)int. If the input type is a float, return +True (we do need scaling)

    • +
    • Now input and output types are (u)ints. If the min and max in the +data are within range of the output type, return False

    • +
    • Otherwise return True

    • +
    +
    + +
    +
    +property slope
    +

    get/set slope

    +
    + +
    +
    +to_fileobj(fileobj, order='F')
    +

    Write array into fileobj

    +
    +
    Parameters:
    +
    +
    fileobjfile-like object
    +
    order{‘F’, ‘C’}

    order (Fortran or C) to which to write array

    +
    +
    +
    +
    +
    + +
    + +
    +
    +

    SlopeInterArrayWriter

    +
    +
    +class nibabel.arraywriters.SlopeInterArrayWriter(array, out_dtype=None, calc_scale=True, scaler_dtype=<class 'numpy.float32'>, **kwargs)
    +

    Bases: SlopeArrayWriter

    +

    Array writer that can use slope and intercept to scale array

    +

    The writer can subtract an intercept, and divided by a slope, in order to +be able to convert floating point values into a (u)int range, or to convert +larger (u)ints to smaller.

    +

    It extends the ArrayWriter class with attributes:

    +
      +
    • inter

    • +
    • slope

    • +
    +

    and methods:

    +
      +
    • reset() - reset inter, slope to default (not adapted to self.array)

    • +
    • calc_scale() - calculate inter, slope to best write self.array

    • +
    +

    Initialize array writer

    +
    +
    Parameters:
    +
    +
    arrayarray-like

    array-like object

    +
    +
    out_dtypeNone or dtype

    dtype with which array will be written. For this class, +out_dtype` needs to be the same as the dtype of the input array +or a swapped version of the same.

    +
    +
    calc_scale{True, False}, optional

    Whether to calculate scaling for writing array on initialization. +If False, then you can calculate this scaling with +obj.calc_scale() - see examples

    +
    +
    scaler_dtypedtype-like, optional

    specifier for numpy dtype for slope, intercept

    +
    +
    **kwargskeyword arguments

    This class processes only:

    +
      +
    • nan2zero : bool, optional +Whether to set NaN values to 0 when writing integer output. +Defaults to True. If False, NaNs get converted with numpy +astype, and the behavior is undefined. Ignored for floating +point output.

    • +
    +
    +
    +
    +
    +

    Examples

    +
    >>> arr = np.array([0, 255], np.uint8)
    +>>> aw = SlopeInterArrayWriter(arr)
    +>>> aw.slope, aw.inter
    +(1.0, 0.0)
    +>>> aw = SlopeInterArrayWriter(arr, np.int8)
    +>>> (aw.slope, aw.inter) == (1.0, 128)
    +True
    +>>> aw = SlopeInterArrayWriter(arr, np.int8, calc_scale=False)
    +>>> aw.slope, aw.inter
    +(1.0, 0.0)
    +>>> aw.calc_scale()
    +>>> (aw.slope, aw.inter) == (1.0, 128)
    +True
    +
    +
    +
    +
    +__init__(array, out_dtype=None, calc_scale=True, scaler_dtype=<class 'numpy.float32'>, **kwargs)
    +

    Initialize array writer

    +
    +
    Parameters:
    +
    +
    arrayarray-like

    array-like object

    +
    +
    out_dtypeNone or dtype

    dtype with which array will be written. For this class, +out_dtype` needs to be the same as the dtype of the input array +or a swapped version of the same.

    +
    +
    calc_scale{True, False}, optional

    Whether to calculate scaling for writing array on initialization. +If False, then you can calculate this scaling with +obj.calc_scale() - see examples

    +
    +
    scaler_dtypedtype-like, optional

    specifier for numpy dtype for slope, intercept

    +
    +
    **kwargskeyword arguments

    This class processes only:

    +
      +
    • nan2zero : bool, optional +Whether to set NaN values to 0 when writing integer output. +Defaults to True. If False, NaNs get converted with numpy +astype, and the behavior is undefined. Ignored for floating +point output.

    • +
    +
    +
    +
    +
    +

    Examples

    +
    >>> arr = np.array([0, 255], np.uint8)
    +>>> aw = SlopeInterArrayWriter(arr)
    +>>> aw.slope, aw.inter
    +(1.0, 0.0)
    +>>> aw = SlopeInterArrayWriter(arr, np.int8)
    +>>> (aw.slope, aw.inter) == (1.0, 128)
    +True
    +>>> aw = SlopeInterArrayWriter(arr, np.int8, calc_scale=False)
    +>>> aw.slope, aw.inter
    +(1.0, 0.0)
    +>>> aw.calc_scale()
    +>>> (aw.slope, aw.inter) == (1.0, 128)
    +True
    +
    +
    +
    + +
    +
    +property inter
    +

    get/set inter

    +
    + +
    +
    +reset()
    +

    Set object to values before any scaling calculation

    +
    + +
    +
    +to_fileobj(fileobj, order='F')
    +

    Write array into fileobj

    +
    +
    Parameters:
    +
    +
    fileobjfile-like object
    +
    order{‘F’, ‘C’}

    order (Fortran or C) to which to write array

    +
    +
    +
    +
    +
    + +
    + +
    +
    +

    WriterError

    +
    +
    +class nibabel.arraywriters.WriterError
    +

    Bases: Exception

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    + +
    +
    +

    get_slope_inter

    +
    +
    +nibabel.arraywriters.get_slope_inter(writer)
    +

    Return slope, intercept from array writer object

    +
    +
    Parameters:
    +
    +
    writerArrayWriter instance
    +
    +
    +
    Returns:
    +
    +
    slopescalar

    slope in writer or 1.0 if not present

    +
    +
    interscalar

    intercept in writer or 0.0 if not present

    +
    +
    +
    +
    +

    Examples

    +
    >>> arr = np.arange(10)
    +>>> get_slope_inter(ArrayWriter(arr))
    +(1.0, 0.0)
    +>>> get_slope_inter(SlopeArrayWriter(arr))
    +(1.0, 0.0)
    +>>> get_slope_inter(SlopeInterArrayWriter(arr))
    +(1.0, 0.0)
    +
    +
    +
    + +
    +
    +

    make_array_writer

    +
    +
    +nibabel.arraywriters.make_array_writer(data, out_type, has_slope=True, has_intercept=True, **kwargs)
    +

    Make array writer instance for array data and output type out_type

    +
    +
    Parameters:
    +
    +
    dataarray-like

    array for which to create array writer

    +
    +
    out_typedtype-like

    input to numpy dtype to specify array writer output type

    +
    +
    has_slope{True, False}

    If True, array write can use scaling to adapt the array to out_type

    +
    +
    has_intercept{True, False}

    If True, array write can use intercept to adapt the array to out_type

    +
    +
    **kwargsother keyword arguments

    to pass to the arraywriter class

    +
    +
    +
    +
    Returns:
    +
    +
    writerarraywriter instance

    Instance of array writer, with class adapted to has_intercept and +has_slope.

    +
    +
    +
    +
    +

    Examples

    +
    >>> aw = make_array_writer(np.arange(10), np.uint8, True, True)
    +>>> type(aw) == SlopeInterArrayWriter
    +True
    +>>> aw = make_array_writer(np.arange(10), np.uint8, True, False)
    +>>> type(aw) == SlopeArrayWriter
    +True
    +>>> aw = make_array_writer(np.arange(10), np.uint8, False, False)
    +>>> type(aw) == ArrayWriter
    +True
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.batteryrunners.html b/reference/nibabel.batteryrunners.html new file mode 100644 index 0000000000..c744a4e2d9 --- /dev/null +++ b/reference/nibabel.batteryrunners.html @@ -0,0 +1,449 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    batteryrunners

    +

    Battery runner classes and Report classes

    +

    These classes / objects are for generic checking / fixing batteries

    +

    The BatteryRunner class will run a series of checks on a single +object.

    +

    A check is a callable, of signature func(obj, fix=False) which +returns a tuple (obj, Report) for func(obj, False) or +func(obj, True), where the obj may be a modified object, or a +different object, if fix==True.

    +

    To run checks only, and return problem report objects:

    +
    >>> from nibabel.batteryrunners import BatteryRunner, Report
    +>>> def chk(obj, fix=False): # minimal check
    +...     return obj, Report()
    +>>> btrun = BatteryRunner((chk,))
    +>>> reports = btrun.check_only('a string')
    +
    +
    +

    To run checks and fixes, returning fixed object and problem report +sequence, with possible fix messages:

    +
    >>> fixed_obj, report_seq = btrun.check_fix('a string')
    +
    +
    +

    Reports are iterable things, where the elements in the iterations are +Problems, with attributes error, problem_level, +problem_msg, and possibly empty fix_msg. The problem_level +is an integer, giving the level of problem, from 0 (no problem) to 50 +(very bad problem). The levels follow the log levels from the logging +module (e.g 40 equivalent to “error” level, 50 to “critical”). The +error can be one of None if no error to suggest, or an Exception +class that the user might consider raising for this situation. The +problem_msg and fix_msg are human readable strings that should +explain what happened.

    +
    +

    More about checks

    +

    Checks are callables returning objects and reports, like chk below, +such that:

    +
    obj, report = chk(obj, fix=False)
    +obj, report = chk(obj, fix=True)
    +
    +
    +

    For example, for the Analyze header, we need to check the datatype:

    +
    def chk_datatype(hdr, fix=True):
    +    rep = Report(hdr, HeaderDataError)
    +    code = int(hdr['datatype'])
    +    try:
    +        dtype = AnalyzeHeader._data_type_codes.dtype[code]
    +    except KeyError:
    +        rep.problem_level = 40
    +        rep.problem_msg = 'data code not recognized'
    +    else:
    +        if dtype.type is np.void:
    +            rep.problem_level = 40
    +            rep.problem_msg = 'data code not supported'
    +        else:
    +            return hdr, rep
    +    if fix:
    +        rep.fix_problem_msg = 'not attempting fix'
    +    return hdr, rep
    +
    +
    +

    or the bitpix:

    +
    def chk_bitpix(hdr, fix=True):
    +    rep = Report(HeaderDataError)
    +    code = int(hdr['datatype'])
    +    try:
    +        dt = AnalyzeHeader._data_type_codes.dtype[code]
    +    except KeyError:
    +        rep.problem_level = 10
    +        rep.problem_msg = 'no valid datatype to fix bitpix'
    +        return hdr, rep
    +    bitpix = dt.itemsize * 8
    +    if bitpix == hdr['bitpix']:
    +        return hdr, rep
    +    rep.problem_level = 10
    +    rep.problem_msg = 'bitpix does not match datatype')
    +    if fix:
    +        hdr['bitpix'] = bitpix # inplace modification
    +        rep.fix_msg = 'setting bitpix to match datatype'
    +    return hdr, ret
    +
    +
    +

    or the pixdims:

    +
    def chk_pixdims(hdr, fix=True):
    +    rep = Report(hdr, HeaderDataError)
    +    if not np.any(hdr['pixdim'][1:4] < 0):
    +        return hdr, rep
    +    rep.problem_level = 40
    +    rep.problem_msg = 'pixdim[1,2,3] should be positive'
    +    if fix:
    +        hdr['pixdim'][1:4] = np.abs(hdr['pixdim'][1:4])
    +        rep.fix_msg = 'setting to abs of pixdim values'
    +    return hdr, rep
    +
    +
    +
    + + + + + + + + + +

    BatteryRunner(checks)

    Class to run set of checks

    Report([error, problem_level, problem_msg, ...])

    Initialize report with values

    +
    +

    BatteryRunner

    +
    +
    +class nibabel.batteryrunners.BatteryRunner(checks)
    +

    Bases: object

    +

    Class to run set of checks

    +

    Initialize instance from sequence of checks

    +
    +
    Parameters:
    +
    +
    checkssequence

    sequence of checks, where checks are callables matching +signature obj, rep = chk(obj, fix=False). Checks are run +in the order they are passed.

    +
    +
    +
    +
    +

    Examples

    +
    >>> def chk(obj, fix=False): # minimal check
    +...     return obj, Report()
    +>>> btrun = BatteryRunner((chk,))
    +
    +
    +
    +
    +__init__(checks)
    +

    Initialize instance from sequence of checks

    +
    +
    Parameters:
    +
    +
    checkssequence

    sequence of checks, where checks are callables matching +signature obj, rep = chk(obj, fix=False). Checks are run +in the order they are passed.

    +
    +
    +
    +
    +

    Examples

    +
    >>> def chk(obj, fix=False): # minimal check
    +...     return obj, Report()
    +>>> btrun = BatteryRunner((chk,))
    +
    +
    +
    + +
    +
    +check_fix(obj)
    +

    Run checks, with fixes, on obj returning obj, reports

    +
    +
    Parameters:
    +
    +
    objanything

    object on which to run checks, fixes

    +
    +
    +
    +
    Returns:
    +
    +
    objanything

    possibly modified or replaced obj, after fixes

    +
    +
    reportssequence

    sequence of reports on checks, fixes

    +
    +
    +
    +
    +
    + +
    +
    +check_only(obj)
    +

    Run checks on obj returning reports

    +
    +
    Parameters:
    +
    +
    objanything

    object on which to run checks

    +
    +
    +
    +
    Returns:
    +
    +
    reportssequence

    sequence of report objects reporting on result of running +checks (without fixes) on obj

    +
    +
    +
    +
    +
    + +
    + +
    +
    +

    Report

    +
    +
    +class nibabel.batteryrunners.Report(error=<class 'Exception'>, problem_level=0, problem_msg='', fix_msg='')
    +

    Bases: object

    +

    Initialize report with values

    +
    +
    Parameters:
    +
    +
    errorNone or Exception

    Error to raise if raising error for this check. If None, +no error can be raised for this check (it was probably +normal).

    +
    +
    problem_levelint

    level of problem. From 0 (no problem) to 50 (severe +problem). If the report originates from a fix, then this +is the level of the problem remaining after the fix. +Default is 0

    +
    +
    problem_msgstring

    String describing problem detected. Default is ‘’

    +
    +
    fix_msgstring

    String describing any fix applied. Default is ‘’.

    +
    +
    +
    +
    +

    Examples

    +
    >>> rep = Report()
    +>>> rep.problem_level
    +0
    +>>> rep = Report(TypeError, 10)
    +>>> rep.problem_level
    +10
    +
    +
    +
    +
    +__init__(error=<class 'Exception'>, problem_level=0, problem_msg='', fix_msg='')
    +

    Initialize report with values

    +
    +
    Parameters:
    +
    +
    errorNone or Exception

    Error to raise if raising error for this check. If None, +no error can be raised for this check (it was probably +normal).

    +
    +
    problem_levelint

    level of problem. From 0 (no problem) to 50 (severe +problem). If the report originates from a fix, then this +is the level of the problem remaining after the fix. +Default is 0

    +
    +
    problem_msgstring

    String describing problem detected. Default is ‘’

    +
    +
    fix_msgstring

    String describing any fix applied. Default is ‘’.

    +
    +
    +
    +
    +

    Examples

    +
    >>> rep = Report()
    +>>> rep.problem_level
    +0
    +>>> rep = Report(TypeError, 10)
    +>>> rep.problem_level
    +10
    +
    +
    +
    + +
    +
    +log_raise(logger, error_level=40)
    +

    Log problem, raise error if problem >= error_level

    +
    +
    Parameters:
    +
    +
    loggerlog

    log object, implementing log method

    +
    +
    error_levelint, optional

    If self.problem_level >= error_level, raise error

    +
    +
    +
    +
    +
    + +
    +
    +property message
    +

    formatted message string, including fix message if present

    +
    + +
    +
    +write_raise(stream, error_level=40, log_level=30)
    +

    Write report to stream

    +
    +
    Parameters:
    +
    +
    streamfile-like

    implementing write method

    +
    +
    error_levelint, optional

    level at which to raise error for problem detected in +self

    +
    +
    log_levelint, optional

    Such that if log_level is >= self.problem_level we +write the report to stream, otherwise we write nothing.

    +
    +
    +
    +
    +
    + +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.benchmarks.html b/reference/nibabel.benchmarks.html new file mode 100644 index 0000000000..a571bc09ce --- /dev/null +++ b/reference/nibabel.benchmarks.html @@ -0,0 +1,330 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    benchmarks

    + + + +
    +
    +

    Module: benchmarks.bench_array_to_file

    +

    Benchmarks for array_to_file routine

    +

    Run benchmarks with:

    +
    import nibabel as nib
    +nib.bench()
    +
    +
    +

    Run this benchmark with:

    +
    pytest -c <path>/benchmarks/pytest.benchmark.ini <path>/benchmarks/bench_array_to_file.py
    +
    +
    + + + + + + +

    bench_array_to_file()

    +
    +
    +

    Module: benchmarks.bench_arrayproxy_slicing

    +

    Benchmarks for ArrayProxy slicing of gzipped and non-gzipped files

    +

    Run benchmarks with:

    +
    import nibabel as nib
    +nib.bench()
    +
    +
    +

    Run this benchmark with:

    +
    pytest -c <path>/benchmarks/pytest.benchmark.ini <path>/benchmarks/bench_arrayproxy_slicing.py
    +
    +
    + + + + + + +

    bench_arrayproxy_slicing()

    +
    +
    +

    Module: benchmarks.bench_fileslice

    +

    Benchmarks for fileslicing

    +
    +

    import nibabel as nib +nib.bench()

    +
    +

    Run this benchmark with:

    +
    pytest -c <path>/benchmarks/pytest.benchmark.ini <path>/benchmarks/bench_fileslice.py
    +
    +
    + + + + + + + + + +

    bench_fileslice([bytes, file_, gz, bz2, zst])

    run_slices(file_like[, repeat, offset, order])

    +
    +
    +

    Module: benchmarks.bench_finite_range

    +

    Benchmarks for finite_range routine

    +

    Run benchmarks with:

    +
    import nibabel as nib
    +nib.bench()
    +
    +
    +

    Run this benchmark with:

    +
    pytest -c <path>/benchmarks/pytest.benchmark.ini <path>/benchmarks/bench_finite_range.py
    +
    +
    + + + + + + +

    bench_finite_range()

    +
    +
    +

    Module: benchmarks.bench_load_save

    +

    Benchmarks for load and save of image arrays

    +

    Run benchmarks with:

    +
    import nibabel as nib
    +nib.bench()
    +
    +
    +

    Run this benchmark with:

    +
    pytest -c <path>/benchmarks/pytest.benchmark.ini <path>/benchmarks/bench_load_save.py
    +
    +
    + + + + + + +

    bench_load_save()

    +
    +
    +

    Module: benchmarks.butils

    +

    Benchmarking utilities

    + + + + + + +

    print_git_title(title)

    Prints title string with git hash if possible, and underline

    +
    +

    bench_array_to_file

    +
    +
    +nibabel.benchmarks.bench_array_to_file.bench_array_to_file()
    +
    + +
    +
    +

    bench_arrayproxy_slicing

    +
    +
    +nibabel.benchmarks.bench_arrayproxy_slicing.bench_arrayproxy_slicing()
    +
    + +
    +
    +

    bench_fileslice

    +
    +
    +nibabel.benchmarks.bench_fileslice.bench_fileslice(bytes=True, file_=True, gz=True, bz2=False, zst=True)
    +
    + +
    +
    +

    run_slices

    +
    +
    +nibabel.benchmarks.bench_fileslice.run_slices(file_like, repeat=3, offset=0, order='F')
    +
    + +
    +
    +

    bench_finite_range

    +
    +
    +nibabel.benchmarks.bench_finite_range.bench_finite_range()
    +
    + +
    +
    +

    bench_load_save

    +
    +
    +nibabel.benchmarks.bench_load_save.bench_load_save()
    +
    + +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.brikhead.html b/reference/nibabel.brikhead.html new file mode 100644 index 0000000000..c4fdf95b57 --- /dev/null +++ b/reference/nibabel.brikhead.html @@ -0,0 +1,701 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    brikhead

    +

    Class for reading AFNI BRIK/HEAD datasets

    +

    See https://afni.nimh.nih.gov/pub/dist/doc/program_help/README.attributes.html +for information on what is required to have a valid BRIK/HEAD dataset.

    +

    Unless otherwise noted, descriptions AFNI attributes in the code refer to this +document.

    +
    +

    Notes

    +

    In the AFNI HEAD file, the first two values of the attribute DATASET_RANK +determine the shape of the data array stored in the corresponding BRIK file. +The first value, DATASET_RANK[0], must be set to 3 denoting a 3D image. The +second value, DATASET_RANK[1], determines how many “sub-bricks” (in AFNI +parlance) / volumes there are along the fourth (traditionally, but not +exclusively) time axis. Thus, DATASET_RANK[1] will (at least as far as I (RM) +am aware) always be >= 1. This permits sub-brick indexing common in AFNI +programs (e.g., example4d+orig’[0]’).

    +
    + + + + + + + + + + + + + + + + + + + + + +

    AFNIArrayProxy(file_like, header, *[, mmap, ...])

    Proxy object for AFNI image array.

    AFNIHeader(info)

    Class for AFNI header

    AFNIHeaderError

    Error when reading AFNI HEAD file

    AFNIImage(dataobj, affine[, header, extra, ...])

    AFNI Image file

    AFNIImageError

    Error when reading AFNI BRIK files

    parse_AFNI_header(fobj)

    Parses fobj to extract information from HEAD file

    +
    +

    AFNIArrayProxy

    +
    +
    +class nibabel.brikhead.AFNIArrayProxy(file_like, header, *, mmap=True, keep_file_open=None)
    +

    Bases: ArrayProxy

    +

    Proxy object for AFNI image array.

    +
    +
    Attributes:
    +
    +
    scalingnp.ndarray

    Scaling factor (one factor per volume/sub-brick) for data. Default is +None

    +
    +
    +
    +
    +

    Initialize AFNI array proxy

    +
    +
    Parameters:
    +
    +
    file_likefile-like object

    File-like object or filename. If file-like object, should implement +at least read and seek.

    +
    +
    headerAFNIHeader object
    +
    mmap{True, False, ‘c’, ‘r’}, optional, keyword only

    mmap controls the use of numpy memory mapping for reading data. +If False, do not try numpy memmap for data array. If one of +{‘c’, ‘r’}, try numpy memmap with mode=mmap. A mmap value of +True gives the same behavior as mmap='c'. If file_like +cannot be memory-mapped, ignore mmap value and read array from +file.

    +
    +
    keep_file_open{ None, True, False }, optional, keyword only

    keep_file_open controls whether a new file handle is created +every time the image is accessed, or a single file handle is +created and used for the lifetime of this ArrayProxy. If +True, a single file handle is created and used. If False, +a new file handle is created every time the image is accessed. +If file_like refers to an open file handle, this setting has no +effect. The default value (None) will result in the value of +nibabel.arrayproxy.KEEP_FILE_OPEN_DEFAULT being used.

    +
    +
    +
    +
    +
    +
    +__init__(file_like, header, *, mmap=True, keep_file_open=None)
    +

    Initialize AFNI array proxy

    +
    +
    Parameters:
    +
    +
    file_likefile-like object

    File-like object or filename. If file-like object, should implement +at least read and seek.

    +
    +
    headerAFNIHeader object
    +
    mmap{True, False, ‘c’, ‘r’}, optional, keyword only

    mmap controls the use of numpy memory mapping for reading data. +If False, do not try numpy memmap for data array. If one of +{‘c’, ‘r’}, try numpy memmap with mode=mmap. A mmap value of +True gives the same behavior as mmap='c'. If file_like +cannot be memory-mapped, ignore mmap value and read array from +file.

    +
    +
    keep_file_open{ None, True, False }, optional, keyword only

    keep_file_open controls whether a new file handle is created +every time the image is accessed, or a single file handle is +created and used for the lifetime of this ArrayProxy. If +True, a single file handle is created and used. If False, +a new file handle is created every time the image is accessed. +If file_like refers to an open file handle, this setting has no +effect. The default value (None) will result in the value of +nibabel.arrayproxy.KEEP_FILE_OPEN_DEFAULT being used.

    +
    +
    +
    +
    +
    + +
    +
    +property scaling
    +
    + +
    + +
    +
    +

    AFNIHeader

    +
    +
    +class nibabel.brikhead.AFNIHeader(info)
    +

    Bases: SpatialHeader

    +

    Class for AFNI header

    +

    Initialize AFNI header object

    +
    +
    Parameters:
    +
    +
    infodict

    Information from HEAD file as obtained by parse_AFNI_header()

    +
    +
    +
    +
    +

    Examples

    +
    >>> fname = os.path.join(datadir, 'example4d+orig.HEAD')
    +>>> header = AFNIHeader(parse_AFNI_header(fname))
    +>>> header.get_data_dtype().str
    +'<i2'
    +>>> header.get_zooms()
    +(3.0, 3.0, 3.0, 3.0)
    +>>> header.get_data_shape()
    +(33, 41, 25, 3)
    +
    +
    +
    +
    +__init__(info)
    +

    Initialize AFNI header object

    +
    +
    Parameters:
    +
    +
    infodict

    Information from HEAD file as obtained by parse_AFNI_header()

    +
    +
    +
    +
    +

    Examples

    +
    >>> fname = os.path.join(datadir, 'example4d+orig.HEAD')
    +>>> header = AFNIHeader(parse_AFNI_header(fname))
    +>>> header.get_data_dtype().str
    +'<i2'
    +>>> header.get_zooms()
    +(3.0, 3.0, 3.0, 3.0)
    +>>> header.get_data_shape()
    +(33, 41, 25, 3)
    +
    +
    +
    + +
    +
    +copy()
    +

    Copy object to independent representation

    +

    The copy should not be affected by any changes to the original +object.

    +
    + +
    +
    +classmethod from_fileobj(fileobj)
    +
    + +
    +
    +classmethod from_header(header=None)
    +
    + +
    +
    +get_affine()
    +

    Returns affine of dataset

    +

    Examples

    +
    >>> fname = os.path.join(datadir, 'example4d+orig.HEAD')
    +>>> header = AFNIHeader(parse_AFNI_header(fname))
    +>>> header.get_affine()
    +array([[ -3.    ,  -0.    ,  -0.    ,  49.5   ],
    +       [ -0.    ,  -3.    ,  -0.    ,  82.312 ],
    +       [  0.    ,   0.    ,   3.    , -52.3511],
    +       [  0.    ,   0.    ,   0.    ,   1.    ]])
    +
    +
    +
    + +
    +
    +get_data_offset()
    +

    Data offset in BRIK file

    +

    Offset is always 0.

    +
    + +
    +
    +get_data_scaling()
    +

    AFNI applies volume-specific data scaling

    +

    Examples

    +
    >>> fname = os.path.join(datadir, 'scaled+tlrc.HEAD')
    +>>> header = AFNIHeader(parse_AFNI_header(fname))
    +>>> header.get_data_scaling()
    +array([3.883363e-08])
    +
    +
    +
    + +
    +
    +get_slope_inter()
    +

    Use self.get_data_scaling() instead

    +

    Holdover because AFNIArrayProxy (inheriting from ArrayProxy) +requires this functionality so as to not error.

    +
    + +
    +
    +get_space()
    +

    Return label for anatomical space to which this dataset is aligned.

    +
    +
    Returns:
    +
    +
    spacestr

    AFNI “space” designation; one of [ORIG, ANAT, TLRC, MNI]

    +
    +
    +
    +
    +

    Notes

    +

    There appears to be documentation for these spaces at +https://afni.nimh.nih.gov/pub/dist/atlases/elsedemo/AFNI_atlas_spaces.niml

    +
    + +
    +
    +get_volume_labels()
    +

    Returns volume labels

    +
    +
    Returns:
    +
    +
    labelslist of str

    Labels for volumes along fourth dimension

    +
    +
    +
    +
    +

    Examples

    +
    >>> header = AFNIHeader(parse_AFNI_header(os.path.join(datadir, 'example4d+orig.HEAD')))
    +>>> header.get_volume_labels()
    +['#0', '#1', '#2']
    +
    +
    +
    + +
    + +
    +
    +

    AFNIHeaderError

    +
    +
    +class nibabel.brikhead.AFNIHeaderError
    +

    Bases: HeaderDataError

    +

    Error when reading AFNI HEAD file

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    + +
    +
    +

    AFNIImage

    +
    +
    +class nibabel.brikhead.AFNIImage(dataobj: ArrayLike, affine: np.ndarray | None, header: FileBasedHeader | ty.Mapping | None = None, extra: ty.Mapping | None = None, file_map: FileMap | None = None)
    +

    Bases: SpatialImage

    +

    AFNI Image file

    +

    Can be loaded from either the BRIK or HEAD file (but MUST specify one!)

    +

    Examples

    +
    >>> import nibabel as nib
    +>>> brik = nib.load(os.path.join(datadir, 'example4d+orig.BRIK.gz'))
    +>>> brik.shape
    +(33, 41, 25, 3)
    +>>> brik.affine
    +array([[ -3.    ,  -0.    ,  -0.    ,  49.5   ],
    +       [ -0.    ,  -3.    ,  -0.    ,  82.312 ],
    +       [  0.    ,   0.    ,   3.    , -52.3511],
    +       [  0.    ,   0.    ,   0.    ,   1.    ]])
    +>>> head = load(os.path.join(datadir, 'example4d+orig.HEAD'))
    +>>> np.array_equal(head.get_fdata(), brik.get_fdata())
    +True
    +
    +
    +

    Initialize image

    +

    The image is a combination of (array-like, affine matrix, header), with +optional metadata in extra, and filename / file-like objects +contained in the file_map mapping.

    +
    +
    Parameters:
    +
    +
    dataobjobject

    Object containing image data. It should be some object that returns an +array from np.asanyarray. It should have a shape attribute +or property

    +
    +
    affineNone or (4,4) array-like

    homogeneous affine giving relationship between voxel coordinates and +world coordinates. Affine can also be None. In this case, +obj.affine also returns None, and the affine as written to disk +will depend on the file format.

    +
    +
    headerNone or mapping or header instance, optional

    metadata for this image format

    +
    +
    extraNone or mapping, optional

    metadata to associate with image that cannot be stored in the +metadata of this image type

    +
    +
    file_mapmapping, optional

    mapping giving file information for this image format

    +
    +
    +
    +
    +
    +
    +__init__(dataobj: ArrayLike, affine: np.ndarray | None, header: FileBasedHeader | ty.Mapping | None = None, extra: ty.Mapping | None = None, file_map: FileMap | None = None)
    +

    Initialize image

    +

    The image is a combination of (array-like, affine matrix, header), with +optional metadata in extra, and filename / file-like objects +contained in the file_map mapping.

    +
    +
    Parameters:
    +
    +
    dataobjobject

    Object containing image data. It should be some object that returns an +array from np.asanyarray. It should have a shape attribute +or property

    +
    +
    affineNone or (4,4) array-like

    homogeneous affine giving relationship between voxel coordinates and +world coordinates. Affine can also be None. In this case, +obj.affine also returns None, and the affine as written to disk +will depend on the file format.

    +
    +
    headerNone or mapping or header instance, optional

    metadata for this image format

    +
    +
    extraNone or mapping, optional

    metadata to associate with image that cannot be stored in the +metadata of this image type

    +
    +
    file_mapmapping, optional

    mapping giving file information for this image format

    +
    +
    +
    +
    +
    + +
    +
    +ImageArrayProxy
    +

    alias of AFNIArrayProxy

    +
    + +
    +
    +files_types: tuple[ExtensionSpec, ...] = (('image', '.brik'), ('header', '.head'))
    +
    + +
    +
    +classmethod filespec_to_file_map(filespec)
    +

    Make file_map from filename filespec

    +

    AFNI BRIK files can be compressed, but HEAD files cannot - see +afni.nimh.nih.gov/pub/dist/doc/program_help/README.compression.html. +Thus, if you have AFNI files my_image.HEAD and my_image.BRIK.gz and you +want to load the AFNI BRIK / HEAD pair, you can specify:

    +
    +
      +
    • The HEAD filename - e.g., my_image.HEAD

    • +
    • The BRIK filename w/o compressed extension - e.g., my_image.BRIK

    • +
    • The full BRIK filename - e.g., my_image.BRIK.gz

    • +
    +
    +
    +
    Parameters:
    +
    +
    filespecstr

    Filename that might be for this image file type.

    +
    +
    +
    +
    Returns:
    +
    +
    file_mapdict

    dict with keys image and header where values are fileholder +objects for the respective BRIK and HEAD files

    +
    +
    +
    +
    Raises:
    +
    +
    ImageFileError

    If filespec is not recognizable as being a filename for this +image type.

    +
    +
    +
    +
    +
    + +
    +
    +classmethod from_file_map(file_map, *, mmap=True, keep_file_open=None)
    +

    Creates an AFNIImage instance from file_map

    +
    +
    Parameters:
    +
    +
    file_mapdict

    dict with keys image, header and values being fileholder +objects for the respective BRIK and HEAD files

    +
    +
    mmap{True, False, ‘c’, ‘r’}, optional, keyword only

    mmap controls the use of numpy memory mapping for reading image +array data. If False, do not try numpy memmap for data array. +If one of {‘c’, ‘r’}, try numpy memmap with mode=mmap. A +mmap value of True gives the same behavior as mmap='c'. If +image data file cannot be memory-mapped, ignore mmap value and +read array from file.

    +
    +
    keep_file_open{None, True, False}, optional, keyword only

    keep_file_open controls whether a new file handle is created +every time the image is accessed, or a single file handle is +created and used for the lifetime of this ArrayProxy. If +True, a single file handle is created and used. If False, +a new file handle is created every time the image is accessed. +If file_like refers to an open file handle, this setting has no +effect. The default value (None) will result in the value of +nibabel.arrayproxy.KEEP_FILE_OPEN_DEFAULT being used.

    +
    +
    +
    +
    +
    + +
    +
    +header_class
    +

    alias of AFNIHeader

    +
    + +
    +
    +makeable: bool = False
    +
    + +
    +
    +rw: bool = False
    +
    + +
    +
    +valid_exts: tuple[str, ...] = ('.brik', '.head')
    +
    + +
    + +
    +
    +

    AFNIImageError

    +
    +
    +class nibabel.brikhead.AFNIImageError
    +

    Bases: ImageDataError

    +

    Error when reading AFNI BRIK files

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    + +
    +
    +

    parse_AFNI_header

    +
    +
    +nibabel.brikhead.parse_AFNI_header(fobj)
    +

    Parses fobj to extract information from HEAD file

    +
    +
    Parameters:
    +
    +
    fobjfile-like object

    AFNI HEAD file object or filename. If file object, should +implement at least read

    +
    +
    +
    +
    Returns:
    +
    +
    infodict

    Dictionary containing AFNI-style key:value pairs from HEAD file

    +
    +
    +
    +
    +

    Examples

    +
    >>> fname = os.path.join(datadir, 'example4d+orig.HEAD')
    +>>> info = parse_AFNI_header(fname)
    +>>> print(info['BYTEORDER_STRING'])
    +LSB_FIRST
    +>>> print(info['BRICK_TYPES'])
    +[1, 1, 1]
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.caret.html b/reference/nibabel.caret.html new file mode 100644 index 0000000000..e37d1295f8 --- /dev/null +++ b/reference/nibabel.caret.html @@ -0,0 +1,182 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    caret

    + + + + + + +

    CaretMetaData(*args, **kwargs)

    A list of name-value pairs used in various Caret-based XML formats

    +
    +

    CaretMetaData

    +
    +
    +class nibabel.caret.CaretMetaData(*args, **kwargs)
    +

    Bases: XmlSerializable, MutableMapping

    +

    A list of name-value pairs used in various Caret-based XML formats

    +
      +
    • Description - Provides a simple method for user-supplied metadata that +associates names with values.

    • +
    • Attributes: [NA]

    • +
    • Child Elements

      +
      +
        +
      • MD (0…N)

      • +
      +
      +
    • +
    • Text Content: [NA]

    • +
    +

    MD elements are a single metadata entry consisting of a name and a value.

    +
    +
    Attributes:
    +
    +
    datamapping of {name: value} pairs
    +
    >>> md = CaretMetaData()
    +
    >>> md[‘key’] = ‘val’
    +
    >>> md
    +
    <CaretMetaData {‘key’: ‘val’}>
    +
    >>> dict(md)
    +
    {‘key’: ‘val’}
    +
    >>> md.to_xml()
    +
    b’<MetaData><MD><Name>key</Name><Value>val</Value></MD></MetaData>’
    +
    Objects may be constructed like any ``dict``:
    +
    >>> md = CaretMetaData(key=’val’)
    +
    >>> md.to_xml()
    +
    b’<MetaData><MD><Name>key</Name><Value>val</Value></MD></MetaData>’
    +
    +
    +
    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.casting.html b/reference/nibabel.casting.html new file mode 100644 index 0000000000..759883060a --- /dev/null +++ b/reference/nibabel.casting.html @@ -0,0 +1,838 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    casting

    +

    Utilities for casting numpy values in various ways

    +

    Most routines work round some numpy oddities in floating point precision and +casting. Others work round numpy casting to and from python ints

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    CastingError

    FloatingError

    able_int_type(values)

    Find the smallest integer numpy type to contain sequence values

    as_int(x[, check])

    Return python integer representation of number

    best_float()

    Floating point type with best precision

    ceil_exact(val, flt_type)

    Return nearest exact integer >= val in float type flt_type

    float_to_int(arr, int_type[, nan2zero, infmax])

    Convert floating point array arr to type int_type

    floor_exact(val, flt_type)

    Return nearest exact integer <= val in float type flt_type

    floor_log2(x)

    floor of log2 of abs(x)

    have_binary128()

    True if we have a binary128 IEEE longdouble

    int_abs(arr)

    Absolute values of array taking care of max negative int values

    int_to_float(val, flt_type)

    Convert integer val to floating point type flt_type

    longdouble_lte_float64()

    Return True if longdouble appears to have the same precision as float64

    longdouble_precision_improved()

    True if longdouble precision increased since initial import

    ok_floats()

    Return floating point types sorted by precision

    on_powerpc()

    True if we are running on a Power PC platform

    shared_range(flt_type, int_type)

    Min and max in float type that are >=min, <=max in integer type

    type_info(np_type)

    Return dict with min, max, nexp, nmant, width for numpy type np_type

    ulp([val])

    Return gap between val and nearest representable number of same type

    +
    +

    CastingError

    +
    +
    +class nibabel.casting.CastingError
    +

    Bases: Exception

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    + +
    +
    +

    FloatingError

    +
    +
    +class nibabel.casting.FloatingError
    +

    Bases: Exception

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    + +
    +
    +

    able_int_type

    +
    +
    +nibabel.casting.able_int_type(values)
    +

    Find the smallest integer numpy type to contain sequence values

    +

    Prefers uint to int if minimum is >= 0

    +
    +
    Parameters:
    +
    +
    valuessequence

    sequence of integer values

    +
    +
    +
    +
    Returns:
    +
    +
    itypeNone or numpy type

    numpy integer type or None if no integer type holds all values

    +
    +
    +
    +
    +

    Examples

    +
    >>> able_int_type([0, 1]) == np.uint8
    +True
    +>>> able_int_type([-1, 1]) == np.int8
    +True
    +
    +
    +
    + +
    +
    +

    as_int

    +
    +
    +nibabel.casting.as_int(x, check=True)
    +

    Return python integer representation of number

    +

    as_int() is deprecated. Use int() instead.

    +
      +
    • deprecated from version: 5.2.0

    • +
    • Will raise <class ‘nibabel.deprecator.ExpiredDeprecationError’> as of version: 7.0.0

    • +
    +

    This is useful because the numpy int(val) mechanism is broken for large +values in np.longdouble.

    +

    It is also useful to work around a numpy 1.4.1 bug in conversion of uints +to python ints.

    +
    +
    Parameters:
    +
    +
    xobject

    integer, unsigned integer or floating point value

    +
    +
    check{True, False}

    If True, raise error for values that are not integers

    +
    +
    +
    +
    Returns:
    +
    +
    iint

    Python integer

    +
    +
    +
    +
    +

    Examples

    +
    >>> as_int(2.0)
    +2
    +>>> as_int(-2.0)
    +-2
    +>>> as_int(2.1) 
    +Traceback (most recent call last):
    +    ...
    +FloatingError: Not an integer: 2.1
    +>>> as_int(2.1, check=False)
    +2
    +
    +
    +
    + +
    +
    +

    best_float

    +
    +
    +nibabel.casting.best_float()
    +

    Floating point type with best precision

    +

    This is nearly always np.longdouble, except on Windows, where np.longdouble +is Intel80 storage, but with float64 precision for calculations. In that +case we return float64 on the basis it’s the fastest and smallest at the +highest precision.

    +

    SPARC float128 also proved so slow that we prefer float64.

    +
    +
    Returns:
    +
    +
    best_typenumpy type

    floating point type with highest precision

    +
    +
    +
    +
    +

    Notes

    +

    Needs to run without error for module import, because it is called in +ok_floats below, and therefore in setting module global OK_FLOATS.

    +
    + +
    +
    +

    ceil_exact

    +
    +
    +nibabel.casting.ceil_exact(val, flt_type)
    +

    Return nearest exact integer >= val in float type flt_type

    +
    +
    Parameters:
    +
    +
    valint

    We have to pass val as an int rather than the floating point type +because large integers cast as floating point may be rounded by the +casting process.

    +
    +
    flt_typenumpy type

    numpy float type.

    +
    +
    +
    +
    Returns:
    +
    +
    ceil_valobject

    value of same floating point type as val, that is the nearest exact +integer in this type such that floor_val >= val. Thus if val is +exact in flt_type, ceil_val == val.

    +
    +
    +
    +
    +

    Examples

    +

    Obviously 2 is within the range of representable integers for float32

    +
    >>> ceil_exact(2, np.float32)
    +2.0
    +
    +
    +

    As is 2**24-1 (the number of significand digits is 23 + 1 implicit)

    +
    >>> ceil_exact(2**24-1, np.float32) == 2**24-1
    +True
    +
    +
    +

    But 2**24+1 gives a number that float32 can’t represent exactly

    +
    >>> ceil_exact(2**24+1, np.float32) == 2**24+2
    +True
    +
    +
    +

    As for the numpy ceil function, negatives ceil towards inf

    +
    >>> ceil_exact(-2**24-1, np.float32) == -2**24
    +True
    +
    +
    +
    + +
    +
    +

    float_to_int

    +
    +
    +nibabel.casting.float_to_int(arr, int_type, nan2zero=True, infmax=False)
    +

    Convert floating point array arr to type int_type

    +
      +
    • Rounds numbers to nearest integer

    • +
    • Clips values to prevent overflows when casting

    • +
    • Converts NaN to 0 (for nan2zero == True)

    • +
    +

    Casting floats to integers is delicate because the result is undefined +and platform specific for float values outside the range of int_type. +Define shared_min to be the minimum value that can be exactly +represented in both the float type of arr and int_type. Define +shared_max to be the equivalent maximum value. To avoid undefined +results we threshold arr at shared_min and shared_max.

    +
    +
    Parameters:
    +
    +
    arrarray-like

    Array of floating point type

    +
    +
    int_typeobject

    Numpy integer type

    +
    +
    nan2zero{True, False, None}

    Whether to convert NaN value to zero. Default is True. If False, and +NaNs are present, raise CastingError. If None, do not check for NaN +values and pass through directly to the astype casting mechanism. +In this last case, the resulting value is undefined.

    +
    +
    infmax{False, True}

    If True, set np.inf values in arr to be int_type integer maximum +value, -np.inf as int_type integer minimum. If False, set +/- infs +to be shared_min, shared_max as defined above. Therefore False +gives faster conversion at the expense of infs that are further from +infinity.

    +
    +
    +
    +
    Returns:
    +
    +
    iarrndarray

    of type int_type

    +
    +
    +
    +
    +

    Notes

    +

    Numpy relies on the C library to cast from float to int using the standard +astype method of the array.

    +

    Quoting from section F4 of the C99 standard:

    +
    +

    If the floating value is infinite or NaN or if the integral part of the +floating value exceeds the range of the integer type, then the +“invalid” floating-point exception is raised and the resulting value +is unspecified.

    +
    +

    Hence we threshold at shared_min and shared_max to avoid casting to +values that are undefined.

    +

    See: https://en.wikipedia.org/wiki/C99 . There are links to the C99 +standard from that page.

    +

    Examples

    +
    >>> float_to_int([np.nan, np.inf, -np.inf, 1.1, 6.6], np.int16)
    +array([     0,  32767, -32768,      1,      7], dtype=int16)
    +
    +
    +
    + +
    +
    +

    floor_exact

    +
    +
    +nibabel.casting.floor_exact(val, flt_type)
    +

    Return nearest exact integer <= val in float type flt_type

    +
    +
    Parameters:
    +
    +
    valint

    We have to pass val as an int rather than the floating point type +because large integers cast as floating point may be rounded by the +casting process.

    +
    +
    flt_typenumpy type

    numpy float type.

    +
    +
    +
    +
    Returns:
    +
    +
    floor_valobject

    value of same floating point type as val, that is the nearest exact +integer in this type such that floor_val <= val. Thus if val is +exact in flt_type, floor_val == val.

    +
    +
    +
    +
    +

    Examples

    +

    Obviously 2 is within the range of representable integers for float32

    +
    >>> floor_exact(2, np.float32)
    +2.0
    +
    +
    +

    As is 2**24-1 (the number of significand digits is 23 + 1 implicit)

    +
    >>> floor_exact(2**24-1, np.float32) == 2**24-1
    +True
    +
    +
    +

    But 2**24+1 gives a number that float32 can’t represent exactly

    +
    >>> floor_exact(2**24+1, np.float32) == 2**24
    +True
    +
    +
    +

    As for the numpy floor function, negatives floor towards -inf

    +
    >>> floor_exact(-2**24-1, np.float32) == -2**24-2
    +True
    +
    +
    +
    + +
    +
    +

    floor_log2

    +
    +
    +nibabel.casting.floor_log2(x)
    +

    floor of log2 of abs(x)

    +

    Embarrassingly, from https://en.wikipedia.org/wiki/Binary_logarithm

    +
    +
    Parameters:
    +
    +
    xint
    +
    +
    +
    Returns:
    +
    +
    LNone or int

    floor of base 2 log of x. None if x == 0.

    +
    +
    +
    +
    +

    Examples

    +
    >>> floor_log2(2**9+1)
    +9
    +>>> floor_log2(-2**9+1)
    +8
    +>>> floor_log2(0.5)
    +-1
    +>>> floor_log2(0) is None
    +True
    +
    +
    +
    + +
    +
    +

    have_binary128

    +
    +
    +nibabel.casting.have_binary128()
    +

    True if we have a binary128 IEEE longdouble

    +
    + +
    +
    +

    int_abs

    +
    +
    +nibabel.casting.int_abs(arr)
    +

    Absolute values of array taking care of max negative int values

    +
    +
    Parameters:
    +
    +
    arrarray-like
    +
    +
    +
    Returns:
    +
    +
    abs_arrarray

    array the same shape as arr in which all negative numbers have been +changed to positive numbers with the magnitude.

    +
    +
    +
    +
    +

    Examples

    +

    This kind of thing is confusing in base numpy:

    +
    >>> import numpy as np
    +>>> np.abs(np.int8(-128))
    +-128
    +
    +
    +

    int_abs fixes that:

    +
    >>> int_abs(np.int8(-128))
    +128
    +>>> int_abs(np.array([-128, 127], dtype=np.int8))
    +array([128, 127], dtype=uint8)
    +>>> int_abs(np.array([-128, 127], dtype=np.float32))
    +array([128., 127.], dtype=float32)
    +
    +
    +
    + +
    +
    +

    int_to_float

    +
    +
    +nibabel.casting.int_to_float(val, flt_type)
    +

    Convert integer val to floating point type flt_type

    +

    int_to_float(…, dt) is deprecated. Use dt() instead.

    +
      +
    • deprecated from version: 5.2.0

    • +
    • Will raise <class ‘nibabel.deprecator.ExpiredDeprecationError’> as of version: 7.0.0

    • +
    +

    Why is this so complicated?

    +

    At least in numpy <= 1.6.1, numpy longdoubles do not correctly convert to +ints, and ints do not correctly convert to longdoubles. Specifically, in +both cases, the values seem to go through float64 conversion on the way, so +to convert better, we need to split into float64s and sum up the result.

    +
    +
    Parameters:
    +
    +
    valint

    Integer value

    +
    +
    flt_typeobject

    numpy floating point type

    +
    +
    +
    +
    Returns:
    +
    +
    fnumpy scalar

    of type flt_type

    +
    +
    +
    +
    +

    Examples

    +
    >>> int_to_float(1, np.float32)
    +1.0
    +
    +
    +
    + +
    +
    +

    longdouble_lte_float64

    +
    +
    +nibabel.casting.longdouble_lte_float64()
    +

    Return True if longdouble appears to have the same precision as float64

    +
    + +
    +
    +

    longdouble_precision_improved

    +
    +
    +nibabel.casting.longdouble_precision_improved()
    +

    True if longdouble precision increased since initial import

    +

    This can happen on Windows compiled with MSVC. It may be because libraries +compiled with mingw (longdouble is Intel80) get linked to numpy compiled +with MSVC (longdouble is Float64)

    +
    + +
    +
    +

    ok_floats

    +
    +
    +nibabel.casting.ok_floats()
    +

    Return floating point types sorted by precision

    +

    Remove longdouble if it has no higher precision than float64

    +
    + +
    +
    +

    on_powerpc

    +
    +
    +nibabel.casting.on_powerpc()
    +

    True if we are running on a Power PC platform

    +

    Has to deal with older Macs and IBM POWER7 series among others

    +
    + +
    +
    +

    shared_range

    +
    +
    +nibabel.casting.shared_range(flt_type, int_type)
    +

    Min and max in float type that are >=min, <=max in integer type

    +

    This is not as easy as it sounds, because the float type may not be able to +exactly represent the max or min integer values, so we have to find the +next exactly representable floating point value to do the thresholding.

    +
    +
    Parameters:
    +
    +
    flt_typedtype specifier

    A dtype specifier referring to a numpy floating point type. For +example, f4, np.dtype('f4'), np.float32 are equivalent.

    +
    +
    int_typedtype specifier

    A dtype specifier referring to a numpy integer type. For example, +i4, np.dtype('i4'), np.int32 are equivalent

    +
    +
    +
    +
    Returns:
    +
    +
    mnobject

    Number of type flt_type that is the minimum value in the range of +int_type, such that mn.astype(int_type) >= min of int_type

    +
    +
    mxobject

    Number of type flt_type that is the maximum value in the range of +int_type, such that mx.astype(int_type) <= max of int_type

    +
    +
    +
    +
    +

    Examples

    +
    >>> shared_range(np.float32, np.int32) == (-2147483648.0, 2147483520.0)
    +True
    +>>> shared_range('f4', 'i4') == (-2147483648.0, 2147483520.0)
    +True
    +
    +
    +
    + +
    +
    +

    type_info

    +
    +
    +nibabel.casting.type_info(np_type)
    +

    Return dict with min, max, nexp, nmant, width for numpy type np_type

    +

    Type can be integer in which case nexp and nmant are None.

    +
    +
    Parameters:
    +
    +
    np_typenumpy type specifier

    Any specifier for a numpy dtype

    +
    +
    +
    +
    Returns:
    +
    +
    infodict

    with fields min (minimum value), max (maximum value), nexp +(exponent width), nmant (significand precision not including +implicit first digit), minexp (minimum exponent), maxexp +(maximum exponent), width (width in bytes). (nexp, nmant, +minexp, maxexp) are None for integer types. Both min and +max are of type np_type.

    +
    +
    +
    +
    Raises:
    +
    +
    FloatingError

    for floating point types we don’t recognize

    +
    +
    +
    +
    +

    Notes

    +

    You might be thinking that np.finfo does this job, and it does, except +for PPC long doubles (https://github.com/numpy/numpy/issues/2669) and +float96 on Windows compiled with Mingw. This routine protects against such +errors in np.finfo by only accepting values that we know are likely to +be correct.

    +
    + +
    +
    +

    ulp

    +
    +
    +nibabel.casting.ulp(val=1.0)
    +

    Return gap between val and nearest representable number of same type

    +

    This is the value of a unit in the last place (ULP), and is similar in +meaning to the MATLAB eps function.

    +
    +
    Parameters:
    +
    +
    valscalar, optional

    scalar value of any numpy type. Default is 1.0 (float64)

    +
    +
    +
    +
    Returns:
    +
    +
    ulp_valscalar

    gap between val and nearest representable number of same type

    +
    +
    +
    +
    +

    Notes

    +

    The wikipedia article on machine epsilon points out that the term epsilon +can be used in the sense of a unit in the last place (ULP), or as the +maximum relative rounding error. The MATLAB eps function uses the ULP +meaning, but this function is ulp rather than eps to avoid +confusion between different meanings of eps.

    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.cifti2.html b/reference/nibabel.cifti2.html new file mode 100644 index 0000000000..8b68d2a217 --- /dev/null +++ b/reference/nibabel.cifti2.html @@ -0,0 +1,2783 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    cifti2

    +

    CIFTI-2 format IO

    + + + + + + + + + +

    cifti2

    Read / write access to CIFTI-2 image format

    cifti2_axes

    Defines Axis objects to create, read, and manipulate CIFTI-2 files

    + + + +
    +
    +

    Module: cifti2.cifti2

    +

    Read / write access to CIFTI-2 image format

    +

    Format of the NIFTI2 container format described here:

    +
    +
    +

    Definition of the CIFTI-2 header format and file extensions can be found at:

    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Cifti2BrainModel([index_offset, ...])

    Element representing a mapping of the dimension to vertex or voxels.

    Cifti2Header([matrix, version])

    Class for CIFTI-2 header extension

    Cifti2HeaderError

    Error in CIFTI-2 header

    Cifti2Image([dataobj, header, nifti_header, ...])

    Class for single file CIFTI-2 format image

    Cifti2Label([key, label, red, green, blue, ...])

    CIFTI-2 label: association of integer key with a name and RGBA values

    Cifti2LabelTable()

    CIFTI-2 label table: a sequence of Cifti2Labels

    Cifti2Matrix()

    CIFTI-2 Matrix object

    Cifti2MatrixIndicesMap(...[, ...])

    Class for Matrix Indices Map

    Cifti2MetaData(*args, **kwargs)

    A list of name-value pairs

    Cifti2NamedMap([map_name, metadata, label_table])

    CIFTI-2 named map: association of name and optional data with a map index

    Cifti2Parcel([name, voxel_indices_ijk, vertices])

    CIFTI-2 parcel: association of a name with vertices and/or voxels

    Cifti2Surface([brain_structure, ...])

    Cifti surface: association of brain structure and number of vertices

    Cifti2TransformationMatrixVoxelIndicesIJKtoXYZ([...])

    Matrix that translates voxel indices to spatial coordinates

    Cifti2VertexIndices([indices])

    CIFTI-2 vertex indices: vertex indices for an associated brain model

    Cifti2Vertices([brain_structure, vertices])

    CIFTI-2 vertices - association of brain structure and a list of vertices

    Cifti2Volume([volume_dimensions, ...])

    CIFTI-2 volume: information about a volume for mappings that use voxels

    Cifti2VoxelIndicesIJK([indices])

    CIFTI-2 VoxelIndicesIJK: Set of voxel indices contained in a structure

    LimitedNifti2Header([binaryblock, ...])

    Initialize header from binary data block and extensions

    +
    +
    +

    Module: cifti2.cifti2_axes

    +

    Defines Axis objects to create, read, and manipulate CIFTI-2 files

    +

    These axes provide an alternative interface to the information in the CIFTI-2 header. +Each type of CIFTI-2 axes describing the rows/columns in a CIFTI-2 matrix is given a unique class:

    +
      +
    • BrainModelAxis: each row/column is a voxel or vertex

    • +
    • ParcelsAxis: each row/column is a group of voxels and/or vertices

    • +
    • ScalarAxis: each row/column has a unique name (with optional meta-data)

    • +
    • LabelAxis: each row/column has a unique name and label table (with optional meta-data)

    • +
    • SeriesAxis: each row/column is a timepoint, which increases monotonically

    • +
    +

    All of these classes are derived from the Axis class.

    +

    After loading a CIFTI-2 file a tuple of axes describing the rows and columns can be obtained +from the cifti2.Cifti2Header.get_axis() method on the header object +(e.g. nibabel.load(<filename>).header.get_axis()). Inversely, a new +cifti2.Cifti2Header object can be created from existing Axis objects +using the cifti2.Cifti2Header.from_axes() factory method.

    +

    CIFTI-2 Axis objects of the same type can be concatenated using the ‘+’-operator. +Numpy indexing also works on axes +(except for SeriesAxis objects, which have to remain monotonically increasing or decreasing).

    +
    +

    Creating new CIFTI-2 axes

    +

    New Axis objects can be constructed by providing a description for what is contained +in each row/column of the described tensor. For each Axis sub-class this descriptor is:

    +
      +
    • BrainModelAxis: a CIFTI-2 structure name and a voxel or vertex index

    • +
    • ParcelsAxis: a name and a sequence of voxel and vertex indices

    • +
    • ScalarAxis: a name and optionally a dict of meta-data

    • +
    • LabelAxis: a name, dict of label index to name and colour, +and optionally a dict of meta-data

    • +
    • SeriesAxis: the time-point of each row/column is set by setting the start, stop, size, +and unit of the time-series

    • +
    +

    Several helper functions exist to create new BrainModelAxis axes:

    + +

    A ParcelsAxis axis can be created from a sequence of BrainModelAxis axes using +ParcelsAxis.from_brain_models().

    +
    +
    +

    Examples

    +

    We can create brain models covering the left cortex and left thalamus using:

    +
    >>> from nibabel import cifti2
    +>>> import numpy as np
    +>>> bm_cortex = cifti2.BrainModelAxis.from_mask([True, False, True, True],
    +...                                             name='cortex_left')
    +>>> bm_thal = cifti2.BrainModelAxis.from_mask(np.ones((2, 2, 2)), affine=np.eye(4),
    +...                                           name='thalamus_left')
    +
    +
    +

    In this very simple case bm_cortex describes a left cortical surface skipping the second +out of four vertices. bm_thal contains all voxels in a 2x2x2 volume.

    +

    Brain structure names automatically get converted to valid CIFTI-2 identifiers using +BrainModelAxis.to_cifti_brain_structure_name(). +A 1-dimensional mask will be automatically interpreted as a surface element and a 3-dimensional +mask as a volume element.

    +

    These can be concatenated in a single brain model covering the left cortex and thalamus by +simply adding them together

    +
    >>> bm_full = bm_cortex + bm_thal
    +
    +
    +

    Brain models covering the full HCP grayordinate space can be constructed by adding all the +volumetric and surface brain models together like this (or by reading one from an already +existing HCP file).

    +

    Getting a specific brain region from the full brain model is as simple as:

    +
    >>> assert bm_full[bm_full.name == 'CIFTI_STRUCTURE_CORTEX_LEFT'] == bm_cortex
    +>>> assert bm_full[bm_full.name == 'CIFTI_STRUCTURE_THALAMUS_LEFT'] == bm_thal
    +
    +
    +

    You can also iterate over all brain structures in a brain model:

    +
    >>> for idx, (name, slc, bm) in enumerate(bm_full.iter_structures()):
    +...     print((str(name), slc))
    +...     assert bm == bm_full[slc]
    +...     assert bm == bm_cortex if idx == 0 else bm_thal
    +('CIFTI_STRUCTURE_CORTEX_LEFT', slice(0, 3, None))
    +('CIFTI_STRUCTURE_THALAMUS_LEFT', slice(3, None, None))
    +
    +
    +

    In this case there will be two iterations, namely: +(‘CIFTI_STRUCTURE_CORTEX_LEFT’, slice(0, <size of cortex mask>), bm_cortex) +and +(‘CIFTI_STRUCTURE_THALAMUS_LEFT’, slice(<size of cortex mask>, None), bm_thal)

    +

    ParcelsAxis can be constructed from selections of these brain models:

    +
    >>> parcel = cifti2.ParcelsAxis.from_brain_models([
    +...        ('surface_parcel', bm_cortex[:2]),  # contains first 2 cortical vertices
    +...        ('volume_parcel', bm_thal),  # contains thalamus
    +...        ('combined_parcel', bm_full[[1, 8, 10]]),  # contains selected voxels/vertices
    +...    ])
    +
    +
    +

    Time series are represented by their starting time (typically 0), step size +(i.e. sampling time or TR), and number of elements:

    +
    >>> series = cifti2.SeriesAxis(start=0, step=100, size=5000)
    +
    +
    +

    So a header for fMRI data with a TR of 100 ms covering the left cortex and thalamus with +5000 timepoints could be created with

    +
    >>> type(cifti2.Cifti2Header.from_axes((series, bm_cortex + bm_thal)))
    +<class 'nibabel.cifti2.cifti2.Cifti2Header'>
    +
    +
    +

    Similarly the curvature and cortical thickness on the left cortex could be stored using a header +like:

    +
    >>> type(cifti2.Cifti2Header.from_axes((cifti2.ScalarAxis(['curvature', 'thickness']),
    +...                                     bm_cortex)))
    +<class 'nibabel.cifti2.cifti2.Cifti2Header'>
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Axis()

    Abstract class for any object describing the rows or columns of a CIFTI-2 vector/matrix

    BrainModelAxis(name[, voxel, vertex, ...])

    Each row/column in the CIFTI-2 vector/matrix represents a single vertex or voxel

    LabelAxis(name, label[, meta])

    Defines CIFTI-2 axis for label array.

    ParcelsAxis(name, voxels, vertices[, ...])

    Each row/column in the CIFTI-2 vector/matrix represents a parcel of voxels/vertices

    ScalarAxis(name[, meta])

    Along this axis of the CIFTI-2 vector/matrix each row/column has been given a unique name and optionally metadata

    SeriesAxis(start, step, size[, unit])

    Along this axis of the CIFTI-2 vector/matrix the rows/columns increase monotonously in time

    from_index_mapping(mim)

    Parses the MatrixIndicesMap to find the appropriate CIFTI-2 axis describing the rows or columns

    to_header(axes)

    Converts the axes describing the rows/columns of a CIFTI-2 vector/matrix to a Cifti2Header

    +
    +
    +

    Module: cifti2.parse_cifti2

    + + + + + + + + + +

    Cifti2Extension(code[, content, object])

    Cifti2Parser([encoding, buffer_size, verbose])

    Class to parse an XML string into a CIFTI-2 header object

    +
    +

    Cifti2BrainModel

    +
    +
    +class nibabel.cifti2.cifti2.Cifti2BrainModel(index_offset=None, index_count=None, model_type=None, brain_structure=None, n_surface_vertices=None, voxel_indices_ijk=None, vertex_indices=None)
    +

    Bases: XmlSerializable

    +

    Element representing a mapping of the dimension to vertex or voxels.

    +

    Mapping to vertices of voxels must be specified.

    +
      +
    • Description - Maps a range of indices to surface vertices or voxels when +IndicesMapToDataType is “CIFTI_INDEX_TYPE_BRAIN_MODELS.”

    • +
    • Attributes

      +
      +
        +
      • IndexOffset - The matrix index of the first brainordinate of this +BrainModel. Note that matrix indices are zero-based.

      • +
      • IndexCount - Number of surface vertices or voxels in this brain +model, must be positive.

      • +
      • ModelType - Type of model representing the brain structure (surface +or voxels). Valid values are listed in the table below.

      • +
      • BrainStructure - Identifies the brain structure. Valid values for +BrainStructure are listed in the table below. However, if the needed +structure is not listed in the table, a message should be posted to +the CIFTI Forum so that a standardized name can be created for the +structure and added to the table.

      • +
      • SurfaceNumberOfVertices - When ModelType is CIFTI_MODEL_TYPE_SURFACE +this attribute contains the actual (or true) number of vertices in +the surface that is associated with this BrainModel. When this +BrainModel represents all vertices in the surface, this value is the +same as IndexCount. When this BrainModel represents only a subset of +the surface’s vertices, IndexCount will be less than this value.

      • +
      +
      +
    • +
    • Child Elements

      +
      +
        +
      • VertexIndices (0…1)

      • +
      • VoxelIndicesIJK (0…1)

      • +
      +
      +
    • +
    • Text Content: [NA]

    • +
    • Parent Element - MatrixIndicesMap

    • +
    +

    For ModelType values, see CIFTI_MODEL_TYPES module attribute.

    +

    For BrainStructure values, see CIFTI_BRAIN_STRUCTURES model attribute.

    +
    +
    Attributes:
    +
    +
    index_offsetint

    Start of the mapping

    +
    +
    index_countint

    Number of elements in the array to be mapped

    +
    +
    model_typestr

    One of CIFTI_MODEL_TYPES

    +
    +
    brain_structurestr

    One of CIFTI_BRAIN_STRUCTURES

    +
    +
    surface_number_of_verticesint

    Number of vertices in the surface. Use only for surface-type structure

    +
    +
    voxel_indices_ijkCifti2VoxelIndicesIJK, optional

    Indices on the image towards where the array indices are mapped

    +
    +
    vertex_indicesCifti2VertexIndices, optional

    Indices of the vertices towards where the array indices are mapped

    +
    +
    +
    +
    +
    +
    +__init__(index_offset=None, index_count=None, model_type=None, brain_structure=None, n_surface_vertices=None, voxel_indices_ijk=None, vertex_indices=None)
    +
    + +
    +
    +property vertex_indices
    +
    + +
    +
    +property voxel_indices_ijk
    +
    + +
    + +
    +
    +

    Cifti2Header

    +
    +
    +class nibabel.cifti2.cifti2.Cifti2Header(matrix=None, version='2.0')
    +

    Bases: FileBasedHeader, XmlSerializable

    +

    Class for CIFTI-2 header extension

    +
    +
    +__init__(matrix=None, version='2.0')
    +
    + +
    +
    +classmethod from_axes(axes)
    +

    Creates a new Cifti2 header based on the Cifti2 axes

    +
    +
    Parameters:
    +
    +
    axestuple of :class`.cifti2_axes.Axis`

    sequence of Cifti2 axes describing each row/column of the matrix to be stored

    +
    +
    +
    +
    Returns:
    +
    +
    headerCifti2Header

    new header describing the rows/columns in a format consistent with Cifti2

    +
    +
    +
    +
    +
    + +
    +
    +get_axis(index)
    +

    Generates the Cifti2 axis for a given dimension

    +
    +
    Parameters:
    +
    +
    indexint

    Dimension for which we want to obtain the mapping.

    +
    +
    +
    +
    Returns:
    +
    +
    axiscifti2_axes.Axis
    +
    +
    +
    +
    + +
    +
    +get_index_map(index)
    +

    Cifti2 Mapping class for a given index

    +
    +
    Parameters:
    +
    +
    indexint

    Index for which we want to obtain the mapping. +Must be in the mapped_indices sequence.

    +
    +
    +
    +
    Returns:
    +
    +
    cifti2_mapCifti2MatrixIndicesMap

    Returns the Cifti2MatrixIndicesMap corresponding to +the given index.

    +
    +
    +
    +
    +
    + +
    +
    +property mapped_indices
    +

    List of matrix indices that are mapped

    +
    + +
    +
    +classmethod may_contain_header(binaryblock)
    +
    + +
    +
    +property number_of_mapped_indices
    +

    Number of mapped indices

    +
    + +
    + +
    +
    +

    Cifti2HeaderError

    +
    +
    +class nibabel.cifti2.cifti2.Cifti2HeaderError
    +

    Bases: Exception

    +

    Error in CIFTI-2 header

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    + +
    +
    +

    Cifti2Image

    +
    +
    +class nibabel.cifti2.cifti2.Cifti2Image(dataobj=None, header=None, nifti_header=None, extra=None, file_map=None, dtype=None)
    +

    Bases: DataobjImage, SerializableImage

    +

    Class for single file CIFTI-2 format image

    +

    Initialize image

    +

    The image is a combination of (dataobj, header), with optional metadata +in nifti_header (a NIfTI2 header). There may be more metadata in the +mapping extra. Filename / file-like objects can also go in the +file_map mapping.

    +
    +
    Parameters:
    +
    +
    dataobjobject

    Object containing image data. It should be some object that +returns an array from np.asanyarray. It should have a +shape attribute or property.

    +
    +
    headerCifti2Header instance or sequence of cifti2_axes.Axis

    Header with data for / from XML part of CIFTI-2 format. +Alternatively a sequence of cifti2_axes.Axis objects can be provided +describing each dimension of the array.

    +
    +
    nifti_headerNone or mapping or NIfTI2 header instance, optional

    Metadata for NIfTI2 component of this format.

    +
    +
    extraNone or mapping

    Extra metadata not captured by header or nifti_header.

    +
    +
    file_mapmapping, optional

    Mapping giving file information for this image format.

    +
    +
    +
    +
    +
    +
    +__init__(dataobj=None, header=None, nifti_header=None, extra=None, file_map=None, dtype=None)
    +

    Initialize image

    +

    The image is a combination of (dataobj, header), with optional metadata +in nifti_header (a NIfTI2 header). There may be more metadata in the +mapping extra. Filename / file-like objects can also go in the +file_map mapping.

    +
    +
    Parameters:
    +
    +
    dataobjobject

    Object containing image data. It should be some object that +returns an array from np.asanyarray. It should have a +shape attribute or property.

    +
    +
    headerCifti2Header instance or sequence of cifti2_axes.Axis

    Header with data for / from XML part of CIFTI-2 format. +Alternatively a sequence of cifti2_axes.Axis objects can be provided +describing each dimension of the array.

    +
    +
    nifti_headerNone or mapping or NIfTI2 header instance, optional

    Metadata for NIfTI2 component of this format.

    +
    +
    extraNone or mapping

    Extra metadata not captured by header or nifti_header.

    +
    +
    file_mapmapping, optional

    Mapping giving file information for this image format.

    +
    +
    +
    +
    +
    + +
    +
    +files_types: tuple[ExtensionSpec, ...] = (('image', '.nii'),)
    +
    + +
    +
    +classmethod from_file_map(file_map, *, mmap=True, keep_file_open=None)
    +

    Load a CIFTI-2 image from a file_map

    +
    +
    Parameters:
    +
    +
    file_mapfile_map
    +
    +
    +
    Returns:
    +
    +
    imgCifti2Image

    Returns a Cifti2Image

    +
    +
    +
    +
    +
    + +
    +
    +classmethod from_image(img)
    +

    Class method to create new instance of own class from img

    +
    +
    Parameters:
    +
    +
    imginstance

    In fact, an object with the API of DataobjImage.

    +
    +
    +
    +
    Returns:
    +
    +
    cimginstance

    Image, of our own class

    +
    +
    +
    +
    +
    + +
    +
    +get_data_dtype()
    +
    + +
    +
    +header_class
    +

    alias of Cifti2Header

    +
    + +
    +
    +makeable: bool = False
    +
    + +
    +
    +property nifti_header
    +
    + +
    +
    +rw: bool = True
    +
    + +
    +
    +set_data_dtype(dtype)
    +
    + +
    +
    +to_file_map(file_map=None, dtype=None)
    +

    Write image to file_map or contained self.file_map

    +
    +
    Parameters:
    +
    +
    file_mapNone or mapping, optional

    files mapping. If None (default) use object’s file_map +attribute instead.

    +
    +
    +
    +
    Returns:
    +
    +
    None
    +
    +
    +
    +
    + +
    +
    +update_headers()
    +

    Harmonize NIfTI headers with image data

    +

    Ensures that the NIfTI-2 header records the data shape in the last three +dim fields. Per the spec:

    +
    +

    Because the first four dimensions in NIfTI are reserved for space and time, the CIFTI +dimensions are stored in the NIfTI header in dim[5] and up, where dim[5] is the length +of the first CIFTI dimension (number of values in a row), dim[6] is the length of the +second CIFTI dimension, and dim[7] is the length of the third CIFTI dimension, if +applicable. The fields dim[1] through dim[4] will be 1; dim[0] will be 6 or 7, +depending on whether a third matrix dimension exists.

    +
    +
    >>> import numpy as np
    +>>> data = np.zeros((2,3,4))
    +>>> img = Cifti2Image(data)  
    +>>> img.shape == (2, 3, 4)
    +True
    +>>> img.update_headers()
    +>>> img.nifti_header.get_data_shape() == (1, 1, 1, 1, 2, 3, 4)
    +True
    +>>> img.shape == (2, 3, 4)
    +True
    +
    +
    +
    + +
    +
    +valid_exts: tuple[str, ...] = ('.nii',)
    +
    + +
    + +
    +
    +

    Cifti2Label

    +
    +
    +class nibabel.cifti2.cifti2.Cifti2Label(key=0, label='', red=0.0, green=0.0, blue=0.0, alpha=0.0)
    +

    Bases: XmlSerializable

    +

    CIFTI-2 label: association of integer key with a name and RGBA values

    +

    For all color components, value is floating point with range 0.0 to 1.0.

    +
      +
    • Description - Associates a label key value with a name and a display +color.

    • +
    • Attributes

      +
      +
        +
      • Key - Integer, data value which is assigned this name and color.

      • +
      • Red - Red color component for label. Value is floating point with +range 0.0 to 1.0.

      • +
      • Green - Green color component for label. Value is floating point with +range 0.0 to 1.0.

      • +
      • Blue - Blue color component for label. Value is floating point with +range 0.0 to 1.0.

      • +
      • Alpha - Alpha color component for label. Value is floating point with +range 0.0 to 1.0.

      • +
      +
      +
    • +
    • Child Elements: [NA]

    • +
    • Text Content - Name of the label.

    • +
    • Parent Element - LabelTable

    • +
    +
    +
    Attributes:
    +
    +
    keyint, optional

    Integer, data value which is assigned this name and color.

    +
    +
    labelstr, optional

    Name of the label.

    +
    +
    redfloat, optional

    Red color component for label (between 0 and 1).

    +
    +
    greenfloat, optional

    Green color component for label (between 0 and 1).

    +
    +
    bluefloat, optional

    Blue color component for label (between 0 and 1).

    +
    +
    alphafloat, optional

    Alpha color component for label (between 0 and 1).

    +
    +
    +
    +
    +
    +
    +__init__(key=0, label='', red=0.0, green=0.0, blue=0.0, alpha=0.0)
    +
    + +
    +
    +property rgba
    +

    Returns RGBA as tuple

    +
    + +
    + +
    +
    +

    Cifti2LabelTable

    +
    +
    +class nibabel.cifti2.cifti2.Cifti2LabelTable
    +

    Bases: XmlSerializable, MutableMapping

    +

    CIFTI-2 label table: a sequence of Cifti2Labels

    +
      +
    • Description - Used by NamedMap when IndicesMapToDataType is +“CIFTI_INDEX_TYPE_LABELS” in order to associate names and display colors +with label keys. Note that LABELS is the only mapping type that uses a +LabelTable. Display coloring of continuous-valued data is not specified +by CIFTI-2.

    • +
    • Attributes: [NA]

    • +
    • Child Elements

      +
      +
        +
      • Label (0…N)

      • +
      +
      +
    • +
    • Text Content: [NA]

    • +
    • Parent Element - NamedMap

    • +
    +
    +
    +__init__()
    +
    + +
    +
    +append(label)
    +
    + +
    + +
    +
    +

    Cifti2Matrix

    +
    +
    +class nibabel.cifti2.cifti2.Cifti2Matrix
    +

    Bases: XmlSerializable, MutableSequence

    +

    CIFTI-2 Matrix object

    +

    This is a list-like container where the elements are instances of +Cifti2MatrixIndicesMap.

    +
      +
    • Description: contains child elements that describe the meaning of the +values in the matrix.

    • +
    • Attributes: [NA]

    • +
    • Child Elements

      +
      +
        +
      • MetaData (0 .. 1)

      • +
      • MatrixIndicesMap (1 .. N)

      • +
      +
      +
    • +
    • Text Content: [NA]

    • +
    • Parent Element: CIFTI

    • +
    +

    For each matrix (data) dimension, exactly one MatrixIndicesMap element must +list it in the AppliesToMatrixDimension attribute.

    +
    +
    +__init__()
    +
    + +
    +
    +get_axis(index)
    +

    Generates the Cifti2 axis for a given dimension

    +
    +
    Parameters:
    +
    +
    indexint

    Dimension for which we want to obtain the mapping.

    +
    +
    +
    +
    Returns:
    +
    +
    axiscifti2_axes.Axis
    +
    +
    +
    +
    + +
    +
    +get_data_shape()
    +

    Returns data shape expected based on the CIFTI-2 header

    +

    Any dimensions omitted in the CIFTI-2 header will be given a default size of None.

    +
    + +
    +
    +get_index_map(index)
    +

    Cifti2 Mapping class for a given index

    +
    +
    Parameters:
    +
    +
    indexint

    Index for which we want to obtain the mapping. +Must be in the mapped_indices sequence.

    +
    +
    +
    +
    Returns:
    +
    +
    cifti2_mapCifti2MatrixIndicesMap

    Returns the Cifti2MatrixIndicesMap corresponding to +the given index.

    +
    +
    +
    +
    +
    + +
    +
    +insert(index, value)
    +

    S.insert(index, value) – insert value before index

    +
    + +
    +
    +property mapped_indices
    +

    List of matrix indices that are mapped

    +
    + +
    +
    +property metadata
    +
    + +
    + +
    +
    +

    Cifti2MatrixIndicesMap

    +
    +
    +class nibabel.cifti2.cifti2.Cifti2MatrixIndicesMap(applies_to_matrix_dimension, indices_map_to_data_type, number_of_series_points=None, series_exponent=None, series_start=None, series_step=None, series_unit=None, maps=[])
    +

    Bases: XmlSerializable, MutableSequence

    +

    Class for Matrix Indices Map

    +
      +
    • Description - Provides a mapping between matrix indices and their +interpretation.

    • +
    • Attributes

      +
      +
        +
      • AppliesToMatrixDimension - Lists the dimension(s) of the matrix to +which this MatrixIndicesMap applies. The dimensions of the matrix +start at zero (dimension 0 describes the indices along the first +dimension, dimension 1 describes the indices along the second +dimension, etc.). If this MatrixIndicesMap applies to more than one +matrix dimension, the values are separated by a comma.

      • +
      • IndicesMapToDataType - Type of data to which the MatrixIndicesMap +applies.

      • +
      • NumberOfSeriesPoints - Indicates how many samples there are in a +series mapping type. For example, this could be the number of +timepoints in a timeseries.

      • +
      • SeriesExponent - Integer, SeriesStart and SeriesStep must be +multiplied by 10 raised to the power of the value of this attribute +to give the actual values assigned to indices (e.g., if SeriesStart +is “5” and SeriesExponent is “-3”, the value of the first series +point is 0.005).

      • +
      • SeriesStart - Indicates what quantity should be assigned to the first +series point.

      • +
      • SeriesStep - Indicates amount of change between each series point.

      • +
      • SeriesUnit - Indicates the unit of the result of multiplying +SeriesStart and SeriesStep by 10 to the power of SeriesExponent.

      • +
      +
      +
    • +
    • Child Elements

      +
      +
        +
      • BrainModel (0…N)

      • +
      • NamedMap (0…N)

      • +
      • Parcel (0…N)

      • +
      • Surface (0…N)

      • +
      • Volume (0…1)

      • +
      +
      +
    • +
    • Text Content: [NA]

    • +
    • Parent Element - Matrix

    • +
    +
    +
    Attributes:
    +
    +
    applies_to_matrix_dimensionlist of ints

    Dimensions of this matrix that follow this mapping

    +
    +
    indices_map_to_data_typestr one of CIFTI_MAP_TYPES

    Type of mapping to the matrix indices

    +
    +
    number_of_series_pointsint, optional

    If it is a series, number of points in the series

    +
    +
    series_exponentint, optional

    If it is a series the exponent of the increment

    +
    +
    series_startfloat, optional

    If it is a series, starting time

    +
    +
    series_stepfloat, optional

    If it is a series, step per element

    +
    +
    series_unitstr, optional

    If it is a series, units

    +
    +
    +
    +
    +
    +
    +__init__(applies_to_matrix_dimension, indices_map_to_data_type, number_of_series_points=None, series_exponent=None, series_start=None, series_step=None, series_unit=None, maps=[])
    +
    + +
    +
    +property brain_models
    +
    + +
    +
    +insert(index, value)
    +

    S.insert(index, value) – insert value before index

    +
    + +
    +
    +property named_maps
    +
    + +
    +
    +property parcels
    +
    + +
    +
    +property surfaces
    +
    + +
    +
    +property volume
    +
    + +
    + +
    +
    +

    Cifti2MetaData

    +
    +
    +class nibabel.cifti2.cifti2.Cifti2MetaData(*args, **kwargs)
    +

    Bases: CaretMetaData

    +

    A list of name-value pairs

    +
      +
    • Description - Provides a simple method for user-supplied metadata that +associates names with values.

    • +
    • Attributes: [NA]

    • +
    • Child Elements

      +
      +
        +
      • MD (0…N)

      • +
      +
      +
    • +
    • Text Content: [NA]

    • +
    • Parent Elements - Matrix, NamedMap

    • +
    +

    MD elements are a single metadata entry consisting of a name and a value.

    +
    +
    Attributes:
    +
    +
    datalist of (name, value) tuples
    +
    +
    +
    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    +
    +property data
    +
    + +
    +
    +difference_update(metadata)
    +

    Remove metadata key-value pairs

    +
    +
    Parameters:
    +
    +
    metadatadict-like datatype
    +
    +
    +
    Returns:
    +
    +
    None
    +
    +
    +
    +
    + +
    + +
    +
    +

    Cifti2NamedMap

    +
    +
    +class nibabel.cifti2.cifti2.Cifti2NamedMap(map_name=None, metadata=None, label_table=None)
    +

    Bases: XmlSerializable

    +

    CIFTI-2 named map: association of name and optional data with a map index

    +

    Associates a name, optional metadata, and possibly a LabelTable with an +index in a map.

    +
      +
    • Description - Associates a name, optional metadata, and possibly a +LabelTable with an index in a map.

    • +
    • Attributes: [NA]

    • +
    • Child Elements

      +
      +
        +
      • MapName (1)

      • +
      • LabelTable (0…1)

      • +
      • MetaData (0…1)

      • +
      +
      +
    • +
    • Text Content: [NA]

    • +
    • Parent Element - MatrixIndicesMap

    • +
    +
    +
    Attributes:
    +
    +
    map_namestr

    Name of map

    +
    +
    metadataNone or Cifti2MetaData

    Metadata associated with named map

    +
    +
    label_tableNone or Cifti2LabelTable

    Label table associated with named map

    +
    +
    +
    +
    +
    +
    +__init__(map_name=None, metadata=None, label_table=None)
    +
    + +
    +
    +property label_table
    +
    + +
    +
    +property metadata
    +
    + +
    + +
    +
    +

    Cifti2Parcel

    +
    +
    +class nibabel.cifti2.cifti2.Cifti2Parcel(name=None, voxel_indices_ijk=None, vertices=None)
    +

    Bases: XmlSerializable

    +

    CIFTI-2 parcel: association of a name with vertices and/or voxels

    +
      +
    • Description - Associates a name, plus vertices and/or voxels, with an +index.

    • +
    • Attributes

      +
      +
        +
      • Name - The name of the parcel

      • +
      +
      +
    • +
    • Child Elements

      +
      +
        +
      • Vertices (0…N)

      • +
      • VoxelIndicesIJK (0…1)

      • +
      +
      +
    • +
    • Text Content: [NA]

    • +
    • Parent Element - MatrixIndicesMap

    • +
    +
    +
    Attributes:
    +
    +
    namestr

    Name of parcel

    +
    +
    voxel_indices_ijkNone or Cifti2VoxelIndicesIJK

    Voxel indices associated with parcel

    +
    +
    verticeslist of Cifti2Vertices

    Vertices associated with parcel

    +
    +
    +
    +
    +
    +
    +__init__(name=None, voxel_indices_ijk=None, vertices=None)
    +
    + +
    +
    +append_cifti_vertices(vertices)
    +

    Appends a Cifti2Vertices element to the Cifti2Parcel

    +
    +
    Parameters:
    +
    +
    verticesCifti2Vertices
    +
    +
    +
    +
    + +
    +
    +pop_cifti2_vertices(ith)
    +

    Pops the ith vertices element from the Cifti2Parcel

    +
    + +
    +
    +property voxel_indices_ijk
    +
    + +
    + +
    +
    +

    Cifti2Surface

    +
    +
    +class nibabel.cifti2.cifti2.Cifti2Surface(brain_structure=None, surface_number_of_vertices=None)
    +

    Bases: XmlSerializable

    +

    Cifti surface: association of brain structure and number of vertices

    +
      +
    • Description - Specifies the number of vertices for a surface, when +IndicesMapToDataType is “CIFTI_INDEX_TYPE_PARCELS.” This is separate from +the Parcel element because there can be multiple parcels on one surface, +and one parcel may involve multiple surfaces.

    • +
    • Attributes

      +
      +
        +
      • BrainStructure - A string from the BrainStructure list to identify +what surface structure this element refers to (usually left cortex, +right cortex, or cerebellum).

      • +
      • SurfaceNumberOfVertices - The number of vertices that this +structure’s surface contains.

      • +
      +
      +
    • +
    • Child Elements: [NA]

    • +
    • Text Content: [NA]

    • +
    • Parent Element - MatrixIndicesMap

    • +
    +
    +
    Attributes:
    +
    +
    brain_structurestr

    Name of brain structure

    +
    +
    surface_number_of_verticesint

    Number of vertices on surface

    +
    +
    +
    +
    +
    +
    +__init__(brain_structure=None, surface_number_of_vertices=None)
    +
    + +
    + +
    +
    +

    Cifti2TransformationMatrixVoxelIndicesIJKtoXYZ

    +
    +
    +class nibabel.cifti2.cifti2.Cifti2TransformationMatrixVoxelIndicesIJKtoXYZ(meter_exponent=None, matrix=None)
    +

    Bases: XmlSerializable

    +

    Matrix that translates voxel indices to spatial coordinates

    +
      +
    • Description - Contains a matrix that translates Voxel IJK Indices to +spatial XYZ coordinates (+X=>right, +Y=>anterior, +Z=> superior). The +resulting coordinate is the center of the voxel.

    • +
    • Attributes

      +
      +
        +
      • MeterExponent - Integer, specifies that the coordinate result from +the transformation matrix should be multiplied by 10 to this power to +get the spatial coordinates in meters (e.g., if this is “-3”, then +the transformation matrix is in millimeters).

      • +
      +
      +
    • +
    • Child Elements: [NA]

    • +
    • Text Content - Sixteen floating-point values, in row-major order, that +form a 4x4 homogeneous transformation matrix.

    • +
    • Parent Element - Volume

    • +
    +
    +
    Attributes:
    +
    +
    meter_exponentint

    See attribute description above.

    +
    +
    matrixarray-like shape (4, 4)

    Affine transformation matrix from voxel indices to RAS space.

    +
    +
    +
    +
    +
    +
    +__init__(meter_exponent=None, matrix=None)
    +
    + +
    + +
    +
    +

    Cifti2VertexIndices

    +
    +
    +class nibabel.cifti2.cifti2.Cifti2VertexIndices(indices=None)
    +

    Bases: XmlSerializable, MutableSequence

    +

    CIFTI-2 vertex indices: vertex indices for an associated brain model

    +

    The vertex indices (which are independent for each surface, and +zero-based) that are used in this brain model[.] The parent +BrainModel’s index_count indicates the number of indices.

    +
      +
    • Description - Contains a list of vertex indices for a BrainModel with +ModelType equal to CIFTI_MODEL_TYPE_SURFACE.

    • +
    • Attributes: [NA]

    • +
    • Child Elements: [NA]

    • +
    • Text Content - The vertex indices (which are independent for each +surface, and zero-based) that are used in this brain model, with each +index separated by a whitespace character. The parent BrainModel’s +IndexCount attribute indicates the number of indices in this element’s +content.

    • +
    • Parent Element - BrainModel

    • +
    +
    +
    +__init__(indices=None)
    +
    + +
    +
    +insert(index, value)
    +

    S.insert(index, value) – insert value before index

    +
    + +
    + +
    +
    +

    Cifti2Vertices

    +
    +
    +class nibabel.cifti2.cifti2.Cifti2Vertices(brain_structure=None, vertices=None)
    +

    Bases: XmlSerializable, MutableSequence

    +

    CIFTI-2 vertices - association of brain structure and a list of vertices

    +
      +
    • Description - Contains a BrainStructure type and a list of vertex indices +within a Parcel.

    • +
    • Attributes

      +
      +
        +
      • BrainStructure - A string from the BrainStructure list to identify +what surface this vertex list is from (usually left cortex, right +cortex, or cerebellum).

      • +
      +
      +
    • +
    • Child Elements: [NA]

    • +
    • Text Content - Vertex indices (which are independent for each surface, +and zero-based) separated by whitespace characters.

    • +
    • Parent Element - Parcel

    • +
    +

    The class behaves like a list of Vertex indices (which are independent for +each surface, and zero-based)

    +
    +
    Attributes:
    +
    +
    brain_structurestr

    A string from the BrainStructure list to identify what surface this +vertex list is from (usually left cortex, right cortex, or cerebellum).

    +
    +
    +
    +
    +
    +
    +__init__(brain_structure=None, vertices=None)
    +
    + +
    +
    +insert(index, value)
    +

    S.insert(index, value) – insert value before index

    +
    + +
    + +
    +
    +

    Cifti2Volume

    +
    +
    +class nibabel.cifti2.cifti2.Cifti2Volume(volume_dimensions=None, transform_matrix=None)
    +

    Bases: XmlSerializable

    +

    CIFTI-2 volume: information about a volume for mappings that use voxels

    +
      +
    • Description - Provides information about the volume for any mappings that +use voxels.

    • +
    • Attributes

      +
      +
        +
      • VolumeDimensions - Three integer values separated by commas, the +lengths of the three volume file dimensions that are related to +spatial coordinates, in number of voxels. Voxel indices (which are +zero-based) that are used in the mapping that this element applies to +must be within these dimensions.

      • +
      +
      +
    • +
    • Child Elements

      +
      +
        +
      • TransformationMatrixVoxelIndicesIJKtoXYZ (1)

      • +
      +
      +
    • +
    • Text Content: [NA]

    • +
    • Parent Element - MatrixIndicesMap

    • +
    +
    +
    Attributes:
    +
    +
    volume_dimensionsarray-like shape (3,)

    See attribute description above.

    +
    +
    transformation_matrix_voxel_indices_ijk_to_xyzCifti2TransformationMatrixVoxelIndicesIJKtoXYZ

    Matrix that translates voxel indices to spatial coordinates

    +
    +
    +
    +
    +
    +
    +__init__(volume_dimensions=None, transform_matrix=None)
    +
    + +
    + +
    +
    +

    Cifti2VoxelIndicesIJK

    +
    +
    +class nibabel.cifti2.cifti2.Cifti2VoxelIndicesIJK(indices=None)
    +

    Bases: XmlSerializable, MutableSequence

    +

    CIFTI-2 VoxelIndicesIJK: Set of voxel indices contained in a structure

    +
      +
    • Description - Identifies the voxels that model a brain structure, or +participate in a parcel. Note that when this is a child of BrainModel, +the IndexCount attribute of the BrainModel indicates the number of voxels +contained in this element.

    • +
    • Attributes: [NA]

    • +
    • Child Elements: [NA]

    • +
    • Text Content - IJK indices (which are zero-based) of each voxel in this +brain model or parcel, with each index separated by a whitespace +character. There are three indices per voxel. If the parent element is +BrainModel, then the BrainModel element’s IndexCount attribute indicates +the number of triplets (IJK indices) in this element’s content.

    • +
    • Parent Elements - BrainModel, Parcel

    • +
    +

    Each element of this sequence is a triple of integers.

    +
    +
    +__init__(indices=None)
    +
    + +
    +
    +insert(index, value)
    +

    S.insert(index, value) – insert value before index

    +
    + +
    + +
    +
    +

    LimitedNifti2Header

    +
    +
    +class nibabel.cifti2.cifti2.LimitedNifti2Header(binaryblock=None, endianness=None, check=True, extensions=())
    +

    Bases: Nifti2Header

    +

    Initialize header from binary data block and extensions

    +
    +
    +__init__(binaryblock=None, endianness=None, check=True, extensions=())
    +

    Initialize header from binary data block and extensions

    +
    + +
    + +
    +
    +

    Axis

    +
    +
    +class nibabel.cifti2.cifti2_axes.Axis
    +

    Bases: ABC

    +

    Abstract class for any object describing the rows or columns of a CIFTI-2 vector/matrix

    +

    Mainly used for type checking.

    +

    Base class for the following concrete CIFTI-2 axes:

    +
      +
    • BrainModelAxis: each row/column is a voxel or vertex

    • +
    • ParcelsAxis: each row/column is a group of voxels and/or vertices

    • +
    • ScalarAxis: each row/column has a unique name with optional meta-data

    • +
    • LabelAxis: each row/column has a unique name and label table with optional meta-data

    • +
    • SeriesAxis: each row/column is a timepoint, which increases monotonically

    • +
    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    +
    +property size
    +
    + +
    + +
    +
    +

    BrainModelAxis

    +
    +
    +class nibabel.cifti2.cifti2_axes.BrainModelAxis(name, voxel=None, vertex=None, affine=None, volume_shape=None, nvertices=None)
    +

    Bases: Axis

    +

    Each row/column in the CIFTI-2 vector/matrix represents a single vertex or voxel

    +

    This Axis describes which vertex/voxel is represented by each row/column.

    +

    New BrainModelAxis axes can be constructed by passing on the greyordinate brain-structure +names and voxel/vertex indices to the constructor or by one of the +factory methods:

    +
      +
    • from_mask(): creates surface or volumetric BrainModelAxis axis +from respectively 1D or 3D masks

    • +
    • from_surface(): creates a surface BrainModelAxis axis

    • +
    +

    The resulting BrainModelAxis axes can be concatenated by adding them together.

    +
    +
    Parameters:
    +
    +
    namearray_like

    brain structure name or (N, ) string array with the brain structure names

    +
    +
    voxelarray_like, optional

    (N, 3) array with the voxel indices (can be omitted for CIFTI-2 files only +covering the surface)

    +
    +
    vertexarray_like, optional

    (N, ) array with the vertex indices (can be omitted for volumetric CIFTI-2 files)

    +
    +
    affinearray_like, optional

    (4, 4) array mapping voxel indices to mm space (not needed for CIFTI-2 files only +covering the surface)

    +
    +
    volume_shapetuple of three integers, optional

    shape of the volume in which the voxels were defined (not needed for CIFTI-2 files only +covering the surface)

    +
    +
    nverticesdict from string to integer, optional

    maps names of surface elements to integers (not needed for volumetric CIFTI-2 files)

    +
    +
    +
    +
    +
    +
    +__init__(name, voxel=None, vertex=None, affine=None, volume_shape=None, nvertices=None)
    +

    New BrainModelAxis axes can be constructed by passing on the greyordinate brain-structure +names and voxel/vertex indices to the constructor or by one of the +factory methods:

    +
      +
    • from_mask(): creates surface or volumetric BrainModelAxis axis +from respectively 1D or 3D masks

    • +
    • from_surface(): creates a surface BrainModelAxis axis

    • +
    +

    The resulting BrainModelAxis axes can be concatenated by adding them together.

    +
    +
    Parameters:
    +
    +
    namearray_like

    brain structure name or (N, ) string array with the brain structure names

    +
    +
    voxelarray_like, optional

    (N, 3) array with the voxel indices (can be omitted for CIFTI-2 files only +covering the surface)

    +
    +
    vertexarray_like, optional

    (N, ) array with the vertex indices (can be omitted for volumetric CIFTI-2 files)

    +
    +
    affinearray_like, optional

    (4, 4) array mapping voxel indices to mm space (not needed for CIFTI-2 files only +covering the surface)

    +
    +
    volume_shapetuple of three integers, optional

    shape of the volume in which the voxels were defined (not needed for CIFTI-2 files only +covering the surface)

    +
    +
    nverticesdict from string to integer, optional

    maps names of surface elements to integers (not needed for volumetric CIFTI-2 files)

    +
    +
    +
    +
    +
    + +
    +
    +property affine
    +

    Affine of the volumetric image in which the greyordinate voxels were defined

    +
    + +
    +
    +classmethod from_index_mapping(mim)
    +

    Creates a new BrainModel axis based on a CIFTI-2 dataset

    +
    +
    Parameters:
    +
    +
    mimcifti2.Cifti2MatrixIndicesMap
    +
    +
    +
    Returns:
    +
    +
    BrainModelAxis
    +
    +
    +
    +
    + +
    +
    +classmethod from_mask(mask, name='other', affine=None)
    +

    Creates a new BrainModelAxis axis describing the provided mask

    +
    +
    Parameters:
    +
    +
    maskarray_like

    all non-zero voxels will be included in the BrainModelAxis axis +should be (Nx, Ny, Nz) array for volume mask or (Nvertex, ) array for surface mask

    +
    +
    namestr, optional

    Name of the brain structure (e.g. ‘CortexRight’, ‘thalamus_left’ or ‘brain_stem’)

    +
    +
    affinearray_like, optional

    (4, 4) array with the voxel to mm transformation (defaults to identity matrix) +Argument will be ignored for surface masks

    +
    +
    +
    +
    Returns:
    +
    +
    BrainModelAxis which covers the provided mask
    +
    +
    +
    +
    + +
    +
    +classmethod from_surface(vertices, nvertex, name='Other')
    +

    Creates a new BrainModelAxis axis describing the vertices on a surface

    +
    +
    Parameters:
    +
    +
    verticesarray_like

    indices of the vertices on the surface

    +
    +
    nvertexint

    total number of vertices on the surface

    +
    +
    namestr

    Name of the brain structure (e.g. ‘CortexLeft’ or ‘CortexRight’)

    +
    +
    +
    +
    Returns:
    +
    +
    BrainModelAxis which covers (part of) the surface
    +
    +
    +
    +
    + +
    +
    +get_element(index)
    +

    Describes a single element from the axis

    +
    +
    Parameters:
    +
    +
    indexint

    Indexes the row/column of interest

    +
    +
    +
    +
    Returns:
    +
    +
    tuple with 3 elements
    +
    +
      +
    • +
      str, ‘CIFTI_MODEL_TYPE_SURFACE’ for vertex or ‘CIFTI_MODEL_TYPE_VOXELS’ for voxel
      +
      +
    • +
    • +
      vertex index if it is a surface element, otherwise array with 3 voxel indices
      +
      +
    • +
    • +
      structure.BrainStructure object describing the brain structure the element was taken from
      +
      +
    • +
    +
    +
    +
    + +
    +
    +iter_structures()
    +

    Iterates over all brain structures in the order that they appear along the axis

    +
    +
    Yields:
    +
    +
    tuple with 3 elements:
    +
    +
      +
    • +
      CIFTI-2 brain structure name
      +
      +
    • +
    • +
      slice to select the data associated with the brain structure from the tensor
      +
      +
    • +
    • +
      brain model covering that specific brain structure
      +
      +
    • +
    +
    +
    +
    + +
    +
    +property name
    +

    The brain structure to which the voxel/vertices of belong

    +
    + +
    +
    +property surface_mask
    +

    (N, ) boolean array which is true for any element on the surface

    +
    + +
    +
    +static to_cifti_brain_structure_name(name)
    +

    Attempts to convert the name of an anatomical region in a format recognized by CIFTI-2

    +

    This function returns:

    +
      +
    • the name if it is in the CIFTI-2 format already

    • +
    • if the name is a tuple the first element is assumed to be the structure name while +the second is assumed to be the hemisphere (left, right or both). The latter will default +to both.

    • +
    • names like left_cortex, cortex_left, LeftCortex, or CortexLeft will be converted to +CIFTI_STRUCTURE_CORTEX_LEFT

    • +
    +

    see nibabel.cifti2.tests.test_name() for examples of +which conversions are possible

    +
    +
    Parameters:
    +
    +
    name: iterable of 2-element tuples of integer and string

    input name of an anatomical region

    +
    +
    +
    +
    Returns:
    +
    +
    CIFTI-2 compatible name
    +
    +
    +
    Raises:
    +
    +
    ValueError: raised if the input name does not match a known anatomical structure in CIFTI-2
    +
    +
    +
    +
    + +
    +
    +to_mapping(dim)
    +

    Converts the brain model axis to a MatrixIndicesMap for storage in CIFTI-2 format

    +
    +
    Parameters:
    +
    +
    dimint

    which dimension of the CIFTI-2 vector/matrix is described by this dataset (zero-based)

    +
    +
    +
    +
    Returns:
    +
    +
    cifti2.Cifti2MatrixIndicesMap
    +
    +
    +
    +
    + +
    +
    +property volume_mask
    +

    (N, ) boolean array which is true for any element on the surface

    +
    + +
    +
    +property volume_shape
    +

    Shape of the volumetric image in which the greyordinate voxels were defined

    +
    + +
    + +
    +
    +

    LabelAxis

    +
    +
    +class nibabel.cifti2.cifti2_axes.LabelAxis(name, label, meta=None)
    +

    Bases: Axis

    +

    Defines CIFTI-2 axis for label array.

    +

    Along this axis of the CIFTI-2 vector/matrix each row/column has been given a unique name, +label table, and optionally metadata

    +
    +
    Parameters:
    +
    +
    namearray_like

    (N, ) string array with the parcel names

    +
    +
    labelarray_like

    single dictionary or (N, ) object array with dictionaries mapping +from integers to (name, (R, G, B, A)), where name is a string and R, G, B, and A are +floats between 0 and 1 giving the colour and alpha (i.e., transparency)

    +
    +
    metaarray_like, optional

    (N, ) object array with a dictionary of metadata for each row/column

    +
    +
    +
    +
    +
    +
    +__init__(name, label, meta=None)
    +
    +
    Parameters:
    +
    +
    namearray_like

    (N, ) string array with the parcel names

    +
    +
    labelarray_like

    single dictionary or (N, ) object array with dictionaries mapping +from integers to (name, (R, G, B, A)), where name is a string and R, G, B, and A are +floats between 0 and 1 giving the colour and alpha (i.e., transparency)

    +
    +
    metaarray_like, optional

    (N, ) object array with a dictionary of metadata for each row/column

    +
    +
    +
    +
    +
    + +
    +
    +classmethod from_index_mapping(mim)
    +

    Creates a new Label axis based on a CIFTI-2 dataset

    +
    +
    Parameters:
    +
    +
    mimcifti2.Cifti2MatrixIndicesMap
    +
    +
    +
    Returns:
    +
    +
    LabelAxis
    +
    +
    +
    +
    + +
    +
    +get_element(index)
    +

    Describes a single element from the axis

    +
    +
    Parameters:
    +
    +
    indexint

    Indexes the row/column of interest

    +
    +
    +
    +
    Returns:
    +
    +
    tuple with 2 elements
    +
    +
      +
    • +
      unicode name of the row/column
      +
      +
    • +
    • +
      dictionary with the label table
      +
      +
    • +
    • +
      dictionary with the element metadata
      +
      +
    • +
    +
    +
    +
    + +
    +
    +to_mapping(dim)
    +

    Converts the hcp_labels to a MatrixIndicesMap for storage in CIFTI-2 format

    +
    +
    Parameters:
    +
    +
    dimint

    which dimension of the CIFTI-2 vector/matrix is described by this dataset (zero-based)

    +
    +
    +
    +
    Returns:
    +
    +
    cifti2.Cifti2MatrixIndicesMap
    +
    +
    +
    +
    + +
    + +
    +
    +

    ParcelsAxis

    +
    +
    +class nibabel.cifti2.cifti2_axes.ParcelsAxis(name, voxels, vertices, affine=None, volume_shape=None, nvertices=None)
    +

    Bases: Axis

    +

    Each row/column in the CIFTI-2 vector/matrix represents a parcel of voxels/vertices

    +

    This Axis describes which parcel is represented by each row/column.

    +

    Individual parcels can be accessed based on their name, using +parcel = parcel_axis[name]

    +

    Use of this constructor is not recommended. New ParcelsAxis axes can be constructed more +easily from a sequence of BrainModelAxis axes using +from_brain_models()

    +
    +
    Parameters:
    +
    +
    namearray_like

    (N, ) string array with the parcel names

    +
    +
    voxelsarray_like

    (N, ) object array each containing a sequence of voxels. +For each parcel the voxels are represented by a (M, 3) index array

    +
    +
    verticesarray_like

    (N, ) object array each containing a sequence of vertices. +For each parcel the vertices are represented by a mapping from brain structure name to +(M, ) index array

    +
    +
    affinearray_like, optional

    (4, 4) array mapping voxel indices to mm space (not needed for CIFTI-2 files only +covering the surface)

    +
    +
    volume_shapetuple of three integers, optional

    shape of the volume in which the voxels were defined (not needed for CIFTI-2 files only +covering the surface)

    +
    +
    nverticesdict from string to integer, optional

    maps names of surface elements to integers (not needed for volumetric CIFTI-2 files)

    +
    +
    +
    +
    +
    +
    +__init__(name, voxels, vertices, affine=None, volume_shape=None, nvertices=None)
    +

    Use of this constructor is not recommended. New ParcelsAxis axes can be constructed more +easily from a sequence of BrainModelAxis axes using +from_brain_models()

    +
    +
    Parameters:
    +
    +
    namearray_like

    (N, ) string array with the parcel names

    +
    +
    voxelsarray_like

    (N, ) object array each containing a sequence of voxels. +For each parcel the voxels are represented by a (M, 3) index array

    +
    +
    verticesarray_like

    (N, ) object array each containing a sequence of vertices. +For each parcel the vertices are represented by a mapping from brain structure name to +(M, ) index array

    +
    +
    affinearray_like, optional

    (4, 4) array mapping voxel indices to mm space (not needed for CIFTI-2 files only +covering the surface)

    +
    +
    volume_shapetuple of three integers, optional

    shape of the volume in which the voxels were defined (not needed for CIFTI-2 files only +covering the surface)

    +
    +
    nverticesdict from string to integer, optional

    maps names of surface elements to integers (not needed for volumetric CIFTI-2 files)

    +
    +
    +
    +
    +
    + +
    +
    +property affine
    +

    Affine of the volumetric image in which the greyordinate voxels were defined

    +
    + +
    +
    +classmethod from_brain_models(named_brain_models)
    +

    Creates a Parcel axis from a list of BrainModelAxis axes with names

    +
    +
    Parameters:
    +
    +
    named_brain_modelsiterable of 2-element tuples of string and BrainModelAxis

    list of (parcel name, brain model representation) pairs defining each parcel

    +
    +
    +
    +
    Returns:
    +
    +
    ParcelsAxis
    +
    +
    +
    +
    + +
    +
    +classmethod from_index_mapping(mim)
    +

    Creates a new Parcels axis based on a CIFTI-2 dataset

    +
    +
    Parameters:
    +
    +
    mimcifti2.Cifti2MatrixIndicesMap
    +
    +
    +
    Returns:
    +
    +
    ParcelsAxis
    +
    +
    +
    +
    + +
    +
    +get_element(index)
    +

    Describes a single element from the axis

    +
    +
    Parameters:
    +
    +
    indexint

    Indexes the row/column of interest

    +
    +
    +
    +
    Returns:
    +
    +
    tuple with 3 elements
    +
    +
      +
    • +
      unicode name of the parcel
      +
      +
    • +
    • +
      (M, 3) int array with voxel indices
      +
      +
    • +
    • +
      dict from string to (K, ) int array with vertex indices

      for a specific surface brain structure

      +
      +
      +
    • +
    +
    +
    +
    + +
    +
    +to_mapping(dim)
    +

    Converts the Parcel to a MatrixIndicesMap for storage in CIFTI-2 format

    +
    +
    Parameters:
    +
    +
    dimint

    which dimension of the CIFTI-2 vector/matrix is described by this dataset (zero-based)

    +
    +
    +
    +
    Returns:
    +
    +
    cifti2.Cifti2MatrixIndicesMap
    +
    +
    +
    +
    + +
    +
    +property volume_shape
    +

    Shape of the volumetric image in which the greyordinate voxels were defined

    +
    + +
    + +
    +
    +

    ScalarAxis

    +
    +
    +class nibabel.cifti2.cifti2_axes.ScalarAxis(name, meta=None)
    +

    Bases: Axis

    +

    Along this axis of the CIFTI-2 vector/matrix each row/column has been given +a unique name and optionally metadata

    +
    +
    Parameters:
    +
    +
    namearray_like

    (N, ) string array with the parcel names

    +
    +
    metaarray_like

    (N, ) object array with a dictionary of metadata for each row/column. +Defaults to empty dictionary

    +
    +
    +
    +
    +
    +
    +__init__(name, meta=None)
    +
    +
    Parameters:
    +
    +
    namearray_like

    (N, ) string array with the parcel names

    +
    +
    metaarray_like

    (N, ) object array with a dictionary of metadata for each row/column. +Defaults to empty dictionary

    +
    +
    +
    +
    +
    + +
    +
    +classmethod from_index_mapping(mim)
    +

    Creates a new Scalar axis based on a CIFTI-2 dataset

    +
    +
    Parameters:
    +
    +
    mimcifti2.Cifti2MatrixIndicesMap
    +
    +
    +
    Returns:
    +
    +
    ScalarAxis
    +
    +
    +
    +
    + +
    +
    +get_element(index)
    +

    Describes a single element from the axis

    +
    +
    Parameters:
    +
    +
    indexint

    Indexes the row/column of interest

    +
    +
    +
    +
    Returns:
    +
    +
    tuple with 2 elements
    +
    +
      +
    • +
      unicode name of the row/column
      +
      +
    • +
    • +
      dictionary with the element metadata
      +
      +
    • +
    +
    +
    +
    + +
    +
    +to_mapping(dim)
    +

    Converts the hcp_labels to a MatrixIndicesMap for storage in CIFTI-2 format

    +
    +
    Parameters:
    +
    +
    dimint

    which dimension of the CIFTI-2 vector/matrix is described by this dataset (zero-based)

    +
    +
    +
    +
    Returns:
    +
    +
    cifti2.Cifti2MatrixIndicesMap
    +
    +
    +
    +
    + +
    + +
    +
    +

    SeriesAxis

    +
    +
    +class nibabel.cifti2.cifti2_axes.SeriesAxis(start, step, size, unit='SECOND')
    +

    Bases: Axis

    +

    Along this axis of the CIFTI-2 vector/matrix the rows/columns increase monotonously in time

    +

    This Axis describes the time point of each row/column.

    +

    Creates a new SeriesAxis axis

    +
    +
    Parameters:
    +
    +
    startfloat

    starting time point

    +
    +
    stepfloat

    sampling time (TR)

    +
    +
    sizeint

    number of time points

    +
    +
    unitstr

    Unit of the step size (one of ‘second’, ‘hertz’, ‘meter’, or ‘radian’)

    +
    +
    +
    +
    +
    +
    +__init__(start, step, size, unit='SECOND')
    +

    Creates a new SeriesAxis axis

    +
    +
    Parameters:
    +
    +
    startfloat

    starting time point

    +
    +
    stepfloat

    sampling time (TR)

    +
    +
    sizeint

    number of time points

    +
    +
    unitstr

    Unit of the step size (one of ‘second’, ‘hertz’, ‘meter’, or ‘radian’)

    +
    +
    +
    +
    +
    + +
    +
    +classmethod from_index_mapping(mim)
    +

    Creates a new SeriesAxis axis based on a CIFTI-2 dataset

    +
    +
    Parameters:
    +
    +
    mimcifti2.Cifti2MatrixIndicesMap
    +
    +
    +
    Returns:
    +
    +
    SeriesAxis
    +
    +
    +
    +
    + +
    +
    +get_element(index)
    +

    Gives the time point of a specific row/column

    +
    +
    Parameters:
    +
    +
    indexint

    Indexes the row/column of interest

    +
    +
    +
    +
    Returns:
    +
    +
    float
    +
    +
    +
    +
    + +
    +
    +size = None
    +
    + +
    +
    +property time
    +
    + +
    +
    +to_mapping(dim)
    +

    Converts the SeriesAxis to a MatrixIndicesMap for storage in CIFTI-2 format

    +
    +
    Parameters:
    +
    +
    dimint

    which dimension of the CIFTI-2 vector/matrix is described by this dataset (zero-based)

    +
    +
    +
    +
    Returns:
    +
    +
    cifti2.Cifti2MatrixIndicesMap
    +
    +
    +
    +
    + +
    +
    +property unit
    +
    + +
    + +
    +
    +

    from_index_mapping

    +
    +
    +nibabel.cifti2.cifti2_axes.from_index_mapping(mim)
    +

    Parses the MatrixIndicesMap to find the appropriate CIFTI-2 axis describing the rows or columns

    +
    +
    Parameters:
    +
    +
    mimcifti2.Cifti2MatrixIndicesMap
    +
    +
    +
    Returns:
    +
    +
    axissubclass of Axis
    +
    +
    +
    +
    + +
    +
    +

    to_header

    +
    +
    +nibabel.cifti2.cifti2_axes.to_header(axes)
    +

    Converts the axes describing the rows/columns of a CIFTI-2 vector/matrix to a Cifti2Header

    +
    +
    Parameters:
    +
    +
    axesiterable of Axis objects

    one or more axes describing each dimension in turn

    +
    +
    +
    +
    Returns:
    +
    +
    headercifti2.Cifti2Header
    +
    +
    +
    +
    + +
    +
    +

    Cifti2Extension

    +
    +
    +class nibabel.cifti2.parse_cifti2.Cifti2Extension(code: int | str, content: bytes = b'', object: T | None = None)
    +

    Bases: Nifti1Extension[Cifti2Header]

    +
    +
    Parameters:
    +
    +
    codeint or str

    Canonical extension code as defined in the NIfTI standard, given +either as integer or corresponding label +(see extension_codes)

    +
    +
    contentbytes, optional

    Extension content as read from the NIfTI file header.

    +
    +
    objectoptional

    Extension content in runtime form.

    +
    +
    +
    +
    +
    +
    +__init__(code: int | str, content: bytes = b'', object: T | None = None) None
    +
    +
    Parameters:
    +
    +
    codeint or str

    Canonical extension code as defined in the NIfTI standard, given +either as integer or corresponding label +(see extension_codes)

    +
    +
    contentbytes, optional

    Extension content as read from the NIfTI file header.

    +
    +
    objectoptional

    Extension content in runtime form.

    +
    +
    +
    +
    +
    + +
    +
    +code: int = 32
    +
    + +
    + +
    +
    +

    Cifti2Parser

    +
    +
    +class nibabel.cifti2.parse_cifti2.Cifti2Parser(encoding=None, buffer_size=3500000, verbose=0)
    +

    Bases: XmlParser

    +

    Class to parse an XML string into a CIFTI-2 header object

    +
    +
    Parameters:
    +
    +
    encodingstr

    string containing xml document

    +
    +
    buffer_size: None or int, optional

    size of read buffer. None uses default buffer_size +from xml.parsers.expat.

    +
    +
    verboseint, optional

    amount of output during parsing (0=silent, by default).

    +
    +
    +
    +
    +
    +
    +__init__(encoding=None, buffer_size=3500000, verbose=0)
    +
    +
    Parameters:
    +
    +
    encodingstr

    string containing xml document

    +
    +
    buffer_size: None or int, optional

    size of read buffer. None uses default buffer_size +from xml.parsers.expat.

    +
    +
    verboseint, optional

    amount of output during parsing (0=silent, by default).

    +
    +
    +
    +
    +
    + +
    +
    +CharacterDataHandler(data)
    +

    Collect character data chunks pending collation

    +

    The parser breaks the data up into chunks of size depending on the +buffer_size of the parser. A large bit of character data, with standard +parser buffer_size (such as 8K) can easily span many calls to this +function. We thus collect the chunks and process them when we hit start +or end tags.

    +
    + +
    +
    +EndElementHandler(name)
    +
    + +
    +
    +StartElementHandler(name, attrs)
    +
    + +
    +
    +flush_chardata()
    +

    Collate and process collected character data

    +
    + +
    +
    +property pending_data
    +

    True if there is character data pending for processing

    +
    + +
    + +
    +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.cmdline.html b/reference/nibabel.cmdline.html new file mode 100644 index 0000000000..4e31168a47 --- /dev/null +++ b/reference/nibabel.cmdline.html @@ -0,0 +1,999 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    cmdline

    +

    Functionality to be exposed in the command line

    + + + +
    +
    +

    Module: cmdline.conform

    +

    Conform neuroimaging volume to arbitrary shape and voxel size.

    + + + + + + +

    main([args])

    Main program function.

    +
    +
    +

    Module: cmdline.convert

    +

    Convert neuroimaging file to new parameters

    + + + + + + +

    main([args])

    Main program function.

    +
    +
    +

    Module: cmdline.dicomfs

    + + + + + + + + + + + + + + + + + + + + + +

    DICOMFS(*args, **kwargs)

    FileHandle(fno)

    dummy_fuse()

    Dummy fuse "module" so that nose does not blow during doctests

    fuse

    alias of dummy_fuse

    get_opt_parser()

    main([args])

    +
    +
    +

    Module: cmdline.diff

    +

    Quick summary of the differences among a set of neuroimaging files

    +
    +
    Notes:
      +
    • difference in data types for header fields will be detected, but +endianness difference will not be detected. It is done so to compare files +with native endianness used in data files.

    • +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    are_values_different(*values)

    Generically compare values, return True if different

    diff(files[, header_fields, ...])

    display_diff(files, diff)

    Format header differences into a nice string

    get_data_diff(files[, max_abs, max_rel, dtype])

    Get difference between data

    get_data_hash_diff(files[, dtype])

    Get difference between md5 values of data

    get_headers_diff(file_headers[, names])

    Get difference between headers

    get_opt_parser()

    main([args, out])

    Getting the show on the road

    +
    +
    +

    Module: cmdline.ls

    +

    Output a summary table for neuroimaging files (resolution, dimensionality, etc.)

    + + + + + + + + + + + + +

    get_opt_parser()

    main([args])

    Show must go on

    proc_file(f, opts)

    +
    +
    +

    Module: cmdline.nifti_dx

    +

    Print nifti diagnostics for header files

    + + + + + + +

    main([args])

    Go go team

    +
    +
    +

    Module: cmdline.parrec2nii

    +

    Code for PAR/REC to NIfTI converter command

    + + + + + + + + + + + + + + + + + + +

    error(msg, exit_code)

    get_opt_parser()

    main()

    proc_file(infile, opts)

    verbose(msg[, indent])

    +
    +
    +

    Module: cmdline.roi

    + + + + + + + + + + + + + + + +

    lossless_slice(img, slicers)

    main([args])

    parse_slice(crop[, allow_step])

    sanitize(args)

    +
    +
    +

    Module: cmdline.stats

    +

    Compute image statistics

    + + + + + + +

    main([args])

    Main program function.

    +
    +
    +

    Module: cmdline.tck2trk

    +

    Convert tractograms (TCK -> TRK).

    + + + + + + + + + +

    main()

    parse_args()

    +
    +
    +

    Module: cmdline.trk2tck

    +

    Convert tractograms (TRK -> TCK).

    + + + + + + + + + +

    main()

    parse_args()

    +
    +
    +

    Module: cmdline.utils

    +

    Helper utilities to be used in cmdline applications

    + + + + + + + + + + + + + + + +

    ap(helplist, format_[, sep])

    Little helper to enforce consistency

    safe_get(obj, name)

    A getattr which would return '-' if getattr fails

    table2string(table[, out])

    Given list of lists figure out their common widths and print to out

    verbose(thing, msg)

    Print s if thing is less than the verbose_level

    +
    +

    main

    +
    +
    +nibabel.cmdline.conform.main(args=None)
    +

    Main program function.

    +
    + +
    +
    +

    main

    +
    +
    +nibabel.cmdline.convert.main(args=None)
    +

    Main program function.

    +
    + +
    +
    +

    DICOMFS

    +
    +
    +class nibabel.cmdline.dicomfs.DICOMFS(*args, **kwargs)
    +

    Bases: object

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    +
    +get_paths()
    +
    + +
    +
    +getattr(path)
    +
    + +
    +
    +match_path(path)
    +
    + +
    +
    +open(path, flags)
    +
    + +
    +
    +read(path, size, offset, fh)
    +
    + +
    +
    +readdir(path, fh)
    +
    + +
    +
    +release(path, flags, fh)
    +
    + +
    + +
    +
    +

    FileHandle

    +
    +
    +class nibabel.cmdline.dicomfs.FileHandle(fno)
    +

    Bases: object

    +
    +
    +__init__(fno)
    +
    + +
    + +
    +
    +

    dummy_fuse

    +
    +
    +class nibabel.cmdline.dicomfs.dummy_fuse
    +

    Bases: object

    +

    Dummy fuse “module” so that nose does not blow during doctests

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    +
    +Fuse
    +

    alias of object

    +
    + +
    +
    +fuse_python_api = (0, 2)
    +
    + +
    + +
    +
    +

    fuse

    +
    +
    +nibabel.cmdline.dicomfs.fuse
    +

    alias of dummy_fuse

    +
    + +
    +
    +

    get_opt_parser

    +
    +
    +nibabel.cmdline.dicomfs.get_opt_parser()
    +
    + +
    +
    +

    main

    +
    +
    +nibabel.cmdline.dicomfs.main(args=None)
    +
    + +
    +
    +

    are_values_different

    +
    +
    +nibabel.cmdline.diff.are_values_different(*values)
    +

    Generically compare values, return True if different

    +

    Note that comparison is targeting reporting of comparison of the headers +so has following specifics: +- even a difference in data types is considered a difference, i.e. 1 != 1.0 +- nans are considered to be the “same”, although generally nan != nan

    +
    + +
    +
    +

    diff

    +
    +
    +nibabel.cmdline.diff.diff(files, header_fields='all', data_max_abs_diff=None, data_max_rel_diff=None, dtype=<class 'numpy.float64'>)
    +
    + +
    +
    +

    display_diff

    +
    +
    +nibabel.cmdline.diff.display_diff(files, diff)
    +

    Format header differences into a nice string

    +
    +
    Parameters:
    +
    +
    files: list of files that were compared so we can print their names
    +
    diff: dict of different valued header fields
    +
    +
    +
    Returns:
    +
    +
    str

    string-formatted table of differences

    +
    +
    +
    +
    +
    + +
    +
    +

    get_data_diff

    +
    +
    +nibabel.cmdline.diff.get_data_diff(files, max_abs=0, max_rel=0, dtype=<class 'numpy.float64'>)
    +

    Get difference between data

    +
    +
    Parameters:
    +
    +
    files: list of (str or ndarray)

    If list of strings is provided – they must be existing file names

    +
    +
    max_abs: float, optional

    Maximal absolute difference to tolerate.

    +
    +
    max_rel: float, optional

    Maximal relative (abs(diff)/mean(diff)) difference to tolerate. +If max_abs is specified, then those data points with lesser than that +absolute difference, are not considered for relative difference testing

    +
    +
    dtype: np, optional

    Datatype to be used when extracting data from files

    +
    +
    +
    +
    Returns:
    +
    +
    diffs: OrderedDict

    An ordered dict with a record per each file which has differences +with other files subsequent detected. Each record is a list of +difference records, one per each file pair. +Each difference record is an Ordered Dict with possible keys +‘abs’ or ‘rel’ showing maximal absolute or relative differences +in the file or the record (‘CMP’: ‘incompat’) if file shapes +are incompatible.

    +
    +
    +
    +
    +
    + +
    +
    +

    get_data_hash_diff

    +
    +
    +nibabel.cmdline.diff.get_data_hash_diff(files, dtype=<class 'numpy.float64'>)
    +

    Get difference between md5 values of data

    +
    +
    Parameters:
    +
    +
    files: list of actual files
    +
    +
    +
    Returns:
    +
    +
    list

    np.array: md5 values of respective files

    +
    +
    +
    +
    +
    + +
    +
    +

    get_headers_diff

    +
    +
    +nibabel.cmdline.diff.get_headers_diff(file_headers, names=None)
    +

    Get difference between headers

    +
    +
    Parameters:
    +
    +
    file_headers: list of actual headers (dicts) from files
    +
    names: list of header fields to test
    +
    +
    +
    Returns:
    +
    +
    dict

    str: list for each header field which differs, return list of +values per each file

    +
    +
    +
    +
    +
    + +
    +
    +

    get_opt_parser

    +
    +
    +nibabel.cmdline.diff.get_opt_parser()
    +
    + +
    +
    +

    main

    +
    +
    +nibabel.cmdline.diff.main(args=None, out=None)
    +

    Getting the show on the road

    +
    + +
    +
    +

    get_opt_parser

    +
    +
    +nibabel.cmdline.ls.get_opt_parser()
    +
    + +
    +
    +

    main

    +
    +
    +nibabel.cmdline.ls.main(args=None)
    +

    Show must go on

    +
    + +
    +
    +

    proc_file

    +
    +
    +nibabel.cmdline.ls.proc_file(f, opts)
    +
    + +
    +
    +

    main

    +
    +
    +nibabel.cmdline.nifti_dx.main(args=None)
    +

    Go go team

    +
    + +
    +
    +

    error

    +
    +
    +nibabel.cmdline.parrec2nii.error(msg, exit_code)
    +
    + +
    +
    +

    get_opt_parser

    +
    +
    +nibabel.cmdline.parrec2nii.get_opt_parser()
    +
    + +
    +
    +

    main

    +
    +
    +nibabel.cmdline.parrec2nii.main()
    +
    + +
    +
    +

    proc_file

    +
    +
    +nibabel.cmdline.parrec2nii.proc_file(infile, opts)
    +
    + +
    +
    +

    verbose

    +
    +
    +nibabel.cmdline.parrec2nii.verbose(msg, indent=0)
    +
    + +
    +
    +

    lossless_slice

    +
    +
    +nibabel.cmdline.roi.lossless_slice(img, slicers)
    +
    + +
    +
    +

    main

    +
    +
    +nibabel.cmdline.roi.main(args=None)
    +
    + +
    +
    +

    parse_slice

    +
    +
    +nibabel.cmdline.roi.parse_slice(crop, allow_step=True)
    +
    + +
    +
    +

    sanitize

    +
    +
    +nibabel.cmdline.roi.sanitize(args)
    +
    + +
    +
    +

    main

    +
    +
    +nibabel.cmdline.stats.main(args=None)
    +

    Main program function.

    +
    + +
    +
    +

    main

    +
    +
    +nibabel.cmdline.tck2trk.main()
    +
    + +
    +
    +

    parse_args

    +
    +
    +nibabel.cmdline.tck2trk.parse_args()
    +
    + +
    +
    +

    main

    +
    +
    +nibabel.cmdline.trk2tck.main()
    +
    + +
    +
    +

    parse_args

    +
    +
    +nibabel.cmdline.trk2tck.parse_args()
    +
    + +
    +
    +

    ap

    +
    +
    +nibabel.cmdline.utils.ap(helplist, format_, sep=', ')
    +

    Little helper to enforce consistency

    +
    + +
    +
    +

    safe_get

    +
    +
    +nibabel.cmdline.utils.safe_get(obj, name)
    +

    A getattr which would return ‘-’ if getattr fails

    +
    + +
    +
    +

    table2string

    +
    +
    +nibabel.cmdline.utils.table2string(table, out=None)
    +

    Given list of lists figure out their common widths and print to out

    +
    +
    Parameters:
    +
    +
    tablelist of lists of strings

    What is aimed to be printed

    +
    +
    outNone or stream

    Where to print. If None – will print and return string

    +
    +
    +
    +
    Returns:
    +
    +
    string if out was None
    +
    +
    +
    +
    + +
    +
    +

    verbose

    +
    +
    +nibabel.cmdline.utils.verbose(thing, msg)
    +

    Print s if thing is less than the verbose_level

    +
    + +
    +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.data.html b/reference/nibabel.data.html new file mode 100644 index 0000000000..0027490670 --- /dev/null +++ b/reference/nibabel.data.html @@ -0,0 +1,546 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    data

    +

    Utilities to find files from NIPY data packages

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Bomber(name, msg)

    Class to raise an informative error when used

    BomberError

    Error when trying to access Bomber instance

    DataError

    Datasource(base_path)

    Simple class to add base path to relative path

    VersionedDatasource(base_path[, config_filename])

    Datasource with version information in config file

    datasource_or_bomber(pkg_def, **options)

    Return a viable datasource or a Bomber

    find_data_dir(root_dirs, *names)

    Find relative path given path prefixes to search

    get_data_path()

    Return specified or guessed locations of NIPY data files

    make_datasource(pkg_def, **kwargs)

    Return datasource defined by pkg_def as found in data_path

    +
    +

    Bomber

    +
    +
    +class nibabel.data.Bomber(name, msg)
    +

    Bases: object

    +

    Class to raise an informative error when used

    +
    +
    +__init__(name, msg)
    +
    + +
    + +
    +
    +

    BomberError

    +
    +
    +class nibabel.data.BomberError
    +

    Bases: DataError, AttributeError

    +

    Error when trying to access Bomber instance

    +

    Should be instance of AttributeError to allow Python 3 inspect to do +various hasattr checks without raising an error

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    + +
    +
    +

    DataError

    +
    +
    +class nibabel.data.DataError
    +

    Bases: Exception

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    + +
    +
    +

    Datasource

    +
    +
    +class nibabel.data.Datasource(base_path)
    +

    Bases: object

    +

    Simple class to add base path to relative path

    +

    Initialize datasource

    +
    +
    Parameters:
    +
    +
    base_pathstr

    path to prepend to all relative paths

    +
    +
    +
    +
    +

    Examples

    +
    >>> from os.path import join as pjoin
    +>>> repo = Datasource(pjoin('a', 'path'))
    +>>> fname = repo.get_filename('somedir', 'afile.txt')
    +>>> fname == pjoin('a', 'path', 'somedir', 'afile.txt')
    +True
    +
    +
    +
    +
    +__init__(base_path)
    +

    Initialize datasource

    +
    +
    Parameters:
    +
    +
    base_pathstr

    path to prepend to all relative paths

    +
    +
    +
    +
    +

    Examples

    +
    >>> from os.path import join as pjoin
    +>>> repo = Datasource(pjoin('a', 'path'))
    +>>> fname = repo.get_filename('somedir', 'afile.txt')
    +>>> fname == pjoin('a', 'path', 'somedir', 'afile.txt')
    +True
    +
    +
    +
    + +
    +
    +get_filename(*path_parts)
    +

    Prepend base path to *path_parts

    +

    We make no check whether the returned path exists.

    +
    +
    Parameters:
    +
    +
    *path_partssequence of strings
    +
    +
    +
    Returns:
    +
    +
    fnamestr

    result of os.path.join(*path_parts), with +``self.base_path prepended

    +
    +
    +
    +
    +
    + +
    +
    +list_files(relative=True)
    +

    Recursively list the files in the data source directory.

    +
    +
    Parameters:
    +
    +
    relative: bool, optional

    If True, path returned are relative to the base path of +the data source.

    +
    +
    +
    +
    Returns:
    +
    +
    file_list: list of strings

    List of the paths of all the files in the data source.

    +
    +
    +
    +
    +
    + +
    + +
    +
    +

    VersionedDatasource

    +
    +
    +class nibabel.data.VersionedDatasource(base_path, config_filename=None)
    +

    Bases: Datasource

    +

    Datasource with version information in config file

    +

    Initialize versioned datasource

    +

    We assume that there is a configuration file with version +information in datasource directory tree.

    +

    The configuration file contains an entry like:

    +
    [DEFAULT]
    +version = 0.3
    +
    +
    +

    The version should have at least a major and a minor version +number in the form above.

    +
    +
    Parameters:
    +
    +
    base_pathstr

    path to prepend to all relative paths

    +
    +
    config_filanameNone or str

    relative path to configuration file containing version

    +
    +
    +
    +
    +
    +
    +__init__(base_path, config_filename=None)
    +

    Initialize versioned datasource

    +

    We assume that there is a configuration file with version +information in datasource directory tree.

    +

    The configuration file contains an entry like:

    +
    [DEFAULT]
    +version = 0.3
    +
    +
    +

    The version should have at least a major and a minor version +number in the form above.

    +
    +
    Parameters:
    +
    +
    base_pathstr

    path to prepend to all relative paths

    +
    +
    config_filanameNone or str

    relative path to configuration file containing version

    +
    +
    +
    +
    +
    + +
    + +
    +
    +

    datasource_or_bomber

    +
    +
    +nibabel.data.datasource_or_bomber(pkg_def, **options)
    +

    Return a viable datasource or a Bomber

    +

    This is to allow module level creation of datasource objects. We +create the objects, so that, if the data exist, and are the correct +version, the objects are valid datasources, otherwise, they +raise an error on access, warning about the lack of data or the +version numbers.

    +

    The parameters are as for make_datasource in this module.

    +
    +
    Parameters:
    +
    +
    pkg_defdict

    dict containing at least key ‘relpath’. Can optionally have keys ‘name’ +(package name), ‘install hint’ (for helpful error messages) and ‘min +version’ giving the minimum necessary version string for the package.

    +
    +
    data_pathsequence of strings or None, optional
    +
    +
    +
    Returns:
    +
    +
    dsdatasource or Bomber instance
    +
    +
    +
    +
    + +
    +
    +

    find_data_dir

    +
    +
    +nibabel.data.find_data_dir(root_dirs, *names)
    +

    Find relative path given path prefixes to search

    +

    We raise a DataError if we can’t find the relative path

    +
    +
    Parameters:
    +
    +
    root_dirssequence of strings

    sequence of paths in which to search for data directory

    +
    +
    *namessequence of strings

    sequence of strings naming directory to find. The name to search +for is given by os.path.join(*names)

    +
    +
    +
    +
    Returns:
    +
    +
    data_dirstr

    full path (root path added to *names above)

    +
    +
    +
    +
    +
    + +
    +
    +

    get_data_path

    +
    +
    +nibabel.data.get_data_path()
    +

    Return specified or guessed locations of NIPY data files

    +

    The algorithm is to return paths, extracted from strings, where +strings are found in the following order:

    +
      +
    1. The contents of environment variable NIPY_DATA_PATH

    2. +
    3. Any section = DATA, key = path value in a config.ini +file in your nipy user directory (found with +get_nipy_user_dir())

    4. +
    5. Any section = DATA, key = path value in any files found +with a sorted(glob.glob(os.path.join(sys_dir, '*.ini'))) +search, where sys_dir is found with get_nipy_system_dir()

    6. +
    7. If sys.prefix is /usr, we add +/usr/local/share/nipy. We need this because Python 2.6 in +Debian / Ubuntu does default installs to /usr/local.

    8. +
    9. The result of get_nipy_user_dir()

    10. +
    +

    Therefore, any paths found in NIPY_DATA_PATH will be searched +before paths found in the user directory config.ini

    +
    +
    Parameters:
    +
    +
    None
    +
    +
    +
    Returns:
    +
    +
    pathssequence of paths
    +
    +
    +
    +

    Notes

    +

    We have to add /usr/local/share/nipy if sys.prefix is /usr, +because Debian has patched distutils in Python 2.6 to do default +distutils installs there:

    + +

    Examples

    +
    >>> pth = get_data_path()
    +
    +
    +
    + +
    +
    +

    make_datasource

    +
    +
    +nibabel.data.make_datasource(pkg_def, **kwargs)
    +

    Return datasource defined by pkg_def as found in data_path

    +

    data_path is the only allowed keyword argument.

    +

    pkg_def is a dictionary with at least one key - ‘relpath’. ‘relpath’ is +a relative path with unix forward slash separators.

    +

    The relative path to the data is found with:

    +
    names = pkg_def['name'].split('/')
    +rel_path = os.path.join(names)
    +
    +
    +

    We search for this relative path in the list of paths given by data_path. +By default data_path is given by get_data_path() in this module.

    +

    If we can’t find the relative path, raise a DataError

    +
    +
    Parameters:
    +
    +
    pkg_defdict

    dict containing at least the key ‘relpath’. ‘relpath’ is the data path +of the package relative to data_path. It is in unix path format +(using forward slashes as directory separators). pkg_def can also +contain optional keys ‘name’ (the name of the package), and / or a key +‘install hint’ that we use in the returned error message from trying to +use the resulting datasource

    +
    +
    data_pathsequence of strings or None, optional

    sequence of paths in which to search for data. If None (the +default), then use get_data_path()

    +
    +
    +
    +
    Returns:
    +
    +
    datasourceVersionedDatasource

    An initialized VersionedDatasource instance

    +
    +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.dataobj_images.html b/reference/nibabel.dataobj_images.html new file mode 100644 index 0000000000..7b3f991ec0 --- /dev/null +++ b/reference/nibabel.dataobj_images.html @@ -0,0 +1,509 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    dataobj_images

    +

    File-based images that have data arrays

    +

    The class:DataObjImage class defines an image that extends the +FileBasedImage by adding an array-like object, named dataobj. +This can either be an actual numpy array, or an object that:

    +
      +
    • returns an array from numpy.asanyarray(obj);

    • +
    • has an attribute or property shape.

    • +
    + + + + + + +

    DataobjImage(dataobj[, header, extra, file_map])

    Template class for images that have dataobj data stores

    +
    +

    DataobjImage

    +
    +
    +class nibabel.dataobj_images.DataobjImage(dataobj: ArrayLike, header: FileBasedHeader | ty.Mapping | None = None, extra: ty.Mapping | None = None, file_map: FileMap | None = None)
    +

    Bases: FileBasedImage

    +

    Template class for images that have dataobj data stores

    +

    Initialize dataobj image

    +

    The datobj image is a combination of (dataobj, header), with optional +metadata in extra, and filename / file-like objects contained in the +file_map mapping.

    +
    +
    Parameters:
    +
    +
    dataobjobject

    Object containing image data. It should be some object that returns +an array from np.asanyarray. It should have shape and +ndim attributes or properties

    +
    +
    headerNone or mapping or header instance, optional

    metadata for this image format

    +
    +
    extraNone or mapping, optional

    metadata to associate with image that cannot be stored in the +metadata of this image type

    +
    +
    file_mapmapping, optional

    mapping giving file information for this image format

    +
    +
    +
    +
    +
    +
    +__init__(dataobj: ArrayLike, header: FileBasedHeader | ty.Mapping | None = None, extra: ty.Mapping | None = None, file_map: FileMap | None = None)
    +

    Initialize dataobj image

    +

    The datobj image is a combination of (dataobj, header), with optional +metadata in extra, and filename / file-like objects contained in the +file_map mapping.

    +
    +
    Parameters:
    +
    +
    dataobjobject

    Object containing image data. It should be some object that returns +an array from np.asanyarray. It should have shape and +ndim attributes or properties

    +
    +
    headerNone or mapping or header instance, optional

    metadata for this image format

    +
    +
    extraNone or mapping, optional

    metadata to associate with image that cannot be stored in the +metadata of this image type

    +
    +
    file_mapmapping, optional

    mapping giving file information for this image format

    +
    +
    +
    +
    +
    + +
    +
    +property dataobj: ArrayLike
    +
    + +
    +
    +classmethod from_file_map(file_map: FileMap, *, mmap: bool | ty.Literal['c', 'r'] = True, keep_file_open: bool | None = None) ArrayImgT
    +

    Class method to create image from mapping in file_map

    +
    +
    Parameters:
    +
    +
    file_mapdict

    Mapping with (key, value) pairs of (file_type, FileHolder +instance giving file-likes for each file needed for this image +type.

    +
    +
    mmap{True, False, ‘c’, ‘r’}, optional, keyword only

    mmap controls the use of numpy memory mapping for reading image +array data. If False, do not try numpy memmap for data array. +If one of {‘c’, ‘r’}, try numpy memmap with mode=mmap. A +mmap value of True gives the same behavior as mmap='c'. If +image data file cannot be memory-mapped, ignore mmap value and +read array from file.

    +
    +
    keep_file_open{ None, True, False }, optional, keyword only

    keep_file_open controls whether a new file handle is created +every time the image is accessed, or a single file handle is +created and used for the lifetime of this ArrayProxy. If +True, a single file handle is created and used. If False, +a new file handle is created every time the image is accessed. +If file_map refers to an open file handle, this setting has no +effect. The default value (None) will result in the value of +nibabel.arrayproxy.KEEP_FILE_OPEN_DEFAULT being used.

    +
    +
    +
    +
    Returns:
    +
    +
    imgDataobjImage instance
    +
    +
    +
    +
    + +
    +
    +classmethod from_filename(filename: FileSpec, *, mmap: bool | ty.Literal['c', 'r'] = True, keep_file_open: bool | None = None) ArrayImgT
    +

    Class method to create image from filename filename

    +
    +
    Parameters:
    +
    +
    filenamestr

    Filename of image to load

    +
    +
    mmap{True, False, ‘c’, ‘r’}, optional, keyword only

    mmap controls the use of numpy memory mapping for reading image +array data. If False, do not try numpy memmap for data array. +If one of {‘c’, ‘r’}, try numpy memmap with mode=mmap. A +mmap value of True gives the same behavior as mmap='c'. If +image data file cannot be memory-mapped, ignore mmap value and +read array from file.

    +
    +
    keep_file_open{ None, True, False }, optional, keyword only

    keep_file_open controls whether a new file handle is created +every time the image is accessed, or a single file handle is +created and used for the lifetime of this ArrayProxy. If +True, a single file handle is created and used. If False, +a new file handle is created every time the image is accessed. +The default value (None) will result in the value of +nibabel.arrayproxy.KEEP_FILE_OPEN_DEFAULT being used.

    +
    +
    +
    +
    Returns:
    +
    +
    imgDataobjImage instance
    +
    +
    +
    +
    + +
    +
    +get_data(caching='fill')
    +

    Return image data from image with any necessary scaling applied

    +

    get_data() is deprecated in favor of get_fdata(), which has a more predictable return type. To obtain get_data() behavior going forward, use numpy.asanyarray(img.dataobj).

    +
      +
    • deprecated from version: 3.0

    • +
    • Raises <class ‘nibabel.deprecator.ExpiredDeprecationError’> as of version: 5.0

    • +
    +
    + +
    +
    +get_fdata(caching: ty.Literal['fill', 'unchanged'] = 'fill', dtype: npt.DTypeLike = <class 'numpy.float64'>) np.ndarray[ty.Any, np.dtype[np.floating]]
    +

    Return floating point image data with necessary scaling applied

    +

    The image dataobj property can be an array proxy or an array. An +array proxy is an object that knows how to load the image data from +disk. An image with an array proxy dataobj is a proxy image; an +image with an array in dataobj is an array image.

    +

    The default behavior for get_fdata() on a proxy image is to read +the data from the proxy, and store in an internal cache. Future calls +to get_fdata will return the cached array. This is the behavior +selected with caching == “fill”.

    +

    Once the data has been cached and returned from an array proxy, if you +modify the returned array, you will also modify the cached array +(because they are the same array). Regardless of the caching flag, +this is always true of an array image.

    +
    +
    Parameters:
    +
    +
    caching{‘fill’, ‘unchanged’}, optional

    See the Notes section for a detailed explanation. This argument +specifies whether the image object should fill in an internal +cached reference to the returned image data array. “fill” specifies +that the image should fill an internal cached reference if +currently empty. Future calls to get_fdata will return this +cached reference. You might prefer “fill” to save the image object +from having to reload the array data from disk on each call to +get_fdata. “unchanged” means that the image should not fill in +the internal cached reference if the cache is currently empty. You +might prefer “unchanged” to “fill” if you want to make sure that +the call to get_fdata does not create an extra (cached) +reference to the returned array. In this case it is easier for +Python to free the memory from the returned array.

    +
    +
    dtypenumpy dtype specifier

    A numpy dtype specifier specifying a floating point type. Data is +returned as this floating point type. Default is np.float64.

    +
    +
    +
    +
    Returns:
    +
    +
    fdataarray

    Array of image data of data type dtype.

    +
    +
    +
    +
    +
    +

    See also

    +
    +
    uncache

    empty the array data cache

    +
    +
    +
    +

    Notes

    +

    All images have a property dataobj that represents the image array +data. Images that have been loaded from files usually do not load the +array data from file immediately, in order to reduce image load time +and memory use. For these images, dataobj is an array proxy; an +object that knows how to load the image array data from file.

    +

    By default (caching == “fill”), when you call get_fdata on a +proxy image, we load the array data from disk, store (cache) an +internal reference to this array data, and return the array. The next +time you call get_fdata, you will get the cached reference to the +array, so we don’t have to load the array data from disk again.

    +

    Array images have a dataobj property that already refers to an +array in memory, so there is no benefit to caching, and the caching +keywords have no effect.

    +

    For proxy images, you may not want to fill the cache after reading the +data from disk because the cache will hold onto the array memory until +the image object is deleted, or you use the image uncache method. +If you don’t want to fill the cache, then always use +get_fdata(caching='unchanged'); in this case get_fdata will not +fill the cache (store the reference to the array) if the cache is empty +(no reference to the array). If the cache is full, “unchanged” leaves +the cache full and returns the cached array reference.

    +

    The cache can effect the behavior of the image, because if the cache is +full, or you have an array image, then modifying the returned array +will modify the result of future calls to get_fdata(). For example +you might do this:

    +
    >>> import os
    +>>> import nibabel as nib
    +>>> from nibabel.testing import data_path
    +>>> img_fname = os.path.join(data_path, 'example4d.nii.gz')
    +
    +
    +
    >>> img = nib.load(img_fname) # This is a proxy image
    +>>> nib.is_proxy(img.dataobj)
    +True
    +
    +
    +

    The array is not yet cached by a call to “get_fdata”, so:

    +
    >>> img.in_memory
    +False
    +
    +
    +

    After we call get_fdata using the default caching == ‘fill’, the +cache contains a reference to the returned array data:

    +
    >>> data = img.get_fdata()
    +>>> img.in_memory
    +True
    +
    +
    +

    We modify an element in the returned data array:

    +
    >>> data[0, 0, 0, 0]
    +0.0
    +>>> data[0, 0, 0, 0] = 99
    +>>> data[0, 0, 0, 0]
    +99.0
    +
    +
    +

    The next time we call ‘get_fdata’, the method returns the cached +reference to the (modified) array:

    +
    >>> data_again = img.get_fdata()
    +>>> data_again is data
    +True
    +>>> data_again[0, 0, 0, 0]
    +99.0
    +
    +
    +

    If you had initially used caching == ‘unchanged’ then the returned +data array would have been loaded from file, but not cached, and:

    +
    >>> img = nib.load(img_fname)  # a proxy image again
    +>>> data = img.get_fdata(caching='unchanged')
    +>>> img.in_memory
    +False
    +>>> data[0, 0, 0] = 99
    +>>> data_again = img.get_fdata(caching='unchanged')
    +>>> data_again is data
    +False
    +>>> data_again[0, 0, 0, 0]
    +0.0
    +
    +
    +
    + +
    +
    +property in_memory: bool
    +

    True when any array data is in memory cache

    +

    There are separate caches for get_data reads and get_fdata reads. +This property is True if either of those caches are set.

    +
    + +
    +
    +classmethod load(filename: FileSpec, *, mmap: bool | ty.Literal['c', 'r'] = True, keep_file_open: bool | None = None) ArrayImgT
    +

    Class method to create image from filename filename

    +
    +
    Parameters:
    +
    +
    filenamestr

    Filename of image to load

    +
    +
    mmap{True, False, ‘c’, ‘r’}, optional, keyword only

    mmap controls the use of numpy memory mapping for reading image +array data. If False, do not try numpy memmap for data array. +If one of {‘c’, ‘r’}, try numpy memmap with mode=mmap. A +mmap value of True gives the same behavior as mmap='c'. If +image data file cannot be memory-mapped, ignore mmap value and +read array from file.

    +
    +
    keep_file_open{ None, True, False }, optional, keyword only

    keep_file_open controls whether a new file handle is created +every time the image is accessed, or a single file handle is +created and used for the lifetime of this ArrayProxy. If +True, a single file handle is created and used. If False, +a new file handle is created every time the image is accessed. +The default value (None) will result in the value of +nibabel.arrayproxy.KEEP_FILE_OPEN_DEFAULT being used.

    +
    +
    +
    +
    Returns:
    +
    +
    imgDataobjImage instance
    +
    +
    +
    +
    + +
    +
    +property ndim: int
    +
    + +
    +
    +property shape: tuple[int, ...]
    +
    + +
    +
    +uncache() None
    +

    Delete any cached read of data from proxied data

    +

    Remember there are two types of images:

    +
      +
    • array images where the data img.dataobj is an array

    • +
    • proxy images where the data img.dataobj is a proxy object

    • +
    +

    If you call img.get_fdata() on a proxy image, the result of reading +from the proxy gets cached inside the image object, and this cache is +what gets returned from the next call to img.get_fdata(). If you +modify the returned data, as in:

    +
    data = img.get_fdata()
    +data[:] = 42
    +
    +
    +

    then the next call to img.get_fdata() returns the modified array, +whether the image is an array image or a proxy image:

    +
    assert np.all(img.get_fdata() == 42)
    +
    +
    +

    When you uncache an array image, this has no effect on the return of +img.get_fdata(), but when you uncache a proxy image, the result of +img.get_fdata() returns to its original value.

    +
    + +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.deprecated.html b/reference/nibabel.deprecated.html new file mode 100644 index 0000000000..9a29eac7ff --- /dev/null +++ b/reference/nibabel.deprecated.html @@ -0,0 +1,277 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    deprecated

    +

    Module to help with deprecating objects and classes

    + + + + + + + + + + + + + + + +

    FutureWarningMixin(*args, **kwargs)

    Insert FutureWarning for object creation

    ModuleProxy(module_name)

    Proxy for module that may not yet have been imported

    VisibleDeprecationWarning

    Deprecation warning that will be shown by default

    alert_future_error(msg, version, *[, ...])

    Warn or error with appropriate messages for changing functionality.

    +
    +

    FutureWarningMixin

    +
    +
    +class nibabel.deprecated.FutureWarningMixin(*args: P.args, **kwargs: P.kwargs)
    +

    Bases: object

    +

    Insert FutureWarning for object creation

    +

    Examples

    +
    >>> class C: pass
    +>>> class D(FutureWarningMixin, C):
    +...     warn_message = "Please, don't use this class"
    +
    +
    +

    Record the warning

    +
    >>> with warnings.catch_warnings(record=True) as warns:
    +...     d = D()
    +...     warns[0].message.args[0]
    +"Please, don't use this class"
    +
    +
    +
    +
    +__init__(*args: P.args, **kwargs: P.kwargs) None
    +
    + +
    +
    +warn_message = 'This class will be removed in future versions'
    +
    + +
    + +
    +
    +

    ModuleProxy

    +
    +
    +class nibabel.deprecated.ModuleProxy(module_name: str)
    +

    Bases: object

    +

    Proxy for module that may not yet have been imported

    +
    +
    Parameters:
    +
    +
    module_namestr

    Full module name e.g. nibabel.minc

    +
    +
    +
    +
    +

    Examples

    +
    +
    ::

    arr = np.arange(24).reshape((2, 3, 4)) +nifti1 = ModuleProxy(‘nibabel.nifti1’) +nifti1_image = nifti1.Nifti1Image(arr, np.eye(4))

    +
    +
    +

    So, the nifti1 object is a proxy that will import the required module +when you do attribute access and return the attributes of the imported +module.

    +
    +
    +__init__(module_name: str) None
    +
    + +
    + +
    +
    +

    VisibleDeprecationWarning

    +
    +
    +class nibabel.deprecated.VisibleDeprecationWarning
    +

    Bases: UserWarning

    +

    Deprecation warning that will be shown by default

    +

    Python >= 2.7 does not show standard DeprecationWarnings by default:

    +

    http://docs.python.org/dev/whatsnew/2.7.html#the-future-for-python-2-x

    +

    Use this class for cases where we do want to show deprecations by default.

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    + +
    +
    +

    alert_future_error

    +
    +
    +nibabel.deprecated.alert_future_error(msg: str, version: str, *, warning_class: type[Warning] = <class 'FutureWarning'>, error_class: type[Exception] = <class 'RuntimeError'>, warning_rec: str = '', error_rec: str = '', stacklevel: int = 2) None
    +

    Warn or error with appropriate messages for changing functionality.

    +
    +
    Parameters:
    +
    +
    msgstr

    Description of the condition that led to the alert

    +
    +
    versionstr

    NiBabel version at which the warning will become an error

    +
    +
    warning_classsubclass of Warning, optional

    Warning class to emit before version

    +
    +
    error_classsubclass of Exception, optional

    Error class to emit after version

    +
    +
    warning_recstr, optional

    Guidance for suppressing the warning and avoiding the future error

    +
    +
    error_rec: str, optional

    Guidance for resolving the error

    +
    +
    stacklevel: int, optional

    Warnings stacklevel to provide; note that this will be incremented by +1, so provide the stacklevel you would provide directly to warnings.warn()

    +
    +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.deprecator.html b/reference/nibabel.deprecator.html new file mode 100644 index 0000000000..d06abb3e2a --- /dev/null +++ b/reference/nibabel.deprecator.html @@ -0,0 +1,225 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    deprecator

    +

    Class for recording and reporting deprecations

    + + + + + + + + + +

    Deprecator(version_comparator, int], ...)

    Class to make decorator marking function or method as deprecated

    ExpiredDeprecationError

    Error for expired deprecation

    +
    +

    Deprecator

    +
    +
    +class nibabel.deprecator.Deprecator(version_comparator: ~typing.Callable[[str], int], warn_class: type[Warning] = <class 'DeprecationWarning'>, error_class: type[Exception] = <class 'nibabel.deprecator.ExpiredDeprecationError'>)
    +

    Bases: object

    +

    Class to make decorator marking function or method as deprecated

    +

    The decorated function / method will:

    +
      +
    • Raise the given warning_class warning when the function / method gets +called, up to (and including) version until (if specified);

    • +
    • Raise the given error_class error when the function / method gets +called, when the package version is greater than version until (if +specified).

    • +
    +
    +
    Parameters:
    +
    +
    version_comparatorcallable

    Callable accepting string as argument, and return 1 if string +represents a higher version than encoded in the version_comparator, 0 +if the version is equal, and -1 if the version is lower. For example, +the version_comparator may compare the input version string to the +current package version string.

    +
    +
    warn_classclass, optional

    Class of warning to generate for deprecation.

    +
    +
    error_classclass, optional

    Class of error to generate when version_comparator returns 1 for a +given argument of until in the __call__ method (see below).

    +
    +
    +
    +
    +
    +
    +__init__(version_comparator: ~typing.Callable[[str], int], warn_class: type[Warning] = <class 'DeprecationWarning'>, error_class: type[Exception] = <class 'nibabel.deprecator.ExpiredDeprecationError'>) None
    +
    + +
    +
    +is_bad_version(version_str: str) bool
    +

    Return True if version_str is too high

    +

    Tests version_str with self.version_comparator

    +
    +
    Parameters:
    +
    +
    version_strstr

    String giving version to test

    +
    +
    +
    +
    Returns:
    +
    +
    is_badbool

    True if version_str is for version below that expected by +self.version_comparator, False otherwise.

    +
    +
    +
    +
    +
    + +
    + +
    +
    +

    ExpiredDeprecationError

    +
    +
    +class nibabel.deprecator.ExpiredDeprecationError
    +

    Bases: RuntimeError

    +

    Error for expired deprecation

    +

    Error raised when a called function or method has passed out of its +deprecation period.

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.dft.html b/reference/nibabel.dft.html new file mode 100644 index 0000000000..d1c03c9d2f --- /dev/null +++ b/reference/nibabel.dft.html @@ -0,0 +1,269 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    dft

    +

    DICOM filesystem tools

    + + + + + + + + + + + + + + + + + + + + + + + + +

    CachingError

    error while caching

    DFTError

    base class for DFT exceptions

    InstanceStackError(series, i, si)

    bad series of instance numbers

    VolumeError

    unsupported volume parameter

    clear_cache()

    get_studies([base_dir, followlinks])

    update_cache(base_dir[, followlinks])

    +
    +

    CachingError

    +
    +
    +class nibabel.dft.CachingError
    +

    Bases: DFTError

    +

    error while caching

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    + +
    +
    +

    DFTError

    +
    +
    +class nibabel.dft.DFTError
    +

    Bases: Exception

    +

    base class for DFT exceptions

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    + +
    +
    +

    InstanceStackError

    +
    +
    +class nibabel.dft.InstanceStackError(series, i, si)
    +

    Bases: DFTError

    +

    bad series of instance numbers

    +
    +
    +__init__(series, i, si)
    +
    + +
    + +
    +
    +

    VolumeError

    +
    +
    +class nibabel.dft.VolumeError
    +

    Bases: DFTError

    +

    unsupported volume parameter

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    + +
    +
    +

    clear_cache

    +
    +
    +nibabel.dft.clear_cache()
    +
    + +
    +
    +

    get_studies

    +
    +
    +nibabel.dft.get_studies(base_dir=None, followlinks=False)
    +
    + +
    +
    +

    update_cache

    +
    +
    +nibabel.dft.update_cache(base_dir, followlinks=False)
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.ecat.html b/reference/nibabel.ecat.html new file mode 100644 index 0000000000..083aaec648 --- /dev/null +++ b/reference/nibabel.ecat.html @@ -0,0 +1,899 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    ecat

    +

    Read ECAT format images

    +

    An ECAT format image consists of:

    +
      +
    • a main header;

    • +
    • at least one matrix list (mlist);

    • +
    +

    ECAT thinks of memory locations in terms of blocks. One block is 512 +bytes. Thus block 1 starts at 0 bytes, block 2 at 512 bytes, and so on.

    +

    The matrix list is an array with one row per frame in the data.

    +

    Columns in the matrix list are:

    +
      +
    • 0: Matrix identifier (frame number)

    • +
    • 1: matrix data start block number (subheader followed by image data)

    • +
    • 2: Last block number of matrix (image) data

    • +
    • 3: Matrix status

      +
      +
        +
      • 1: hxists - rw

      • +
      • 2: exists - ro

      • +
      • 3: matrix deleted

      • +
      +
      +
    • +
    +

    There is one sub-header for each image frame (or matrix in the terminology +above). A sub-header can also be called an image header. The sub-header is +one block (512 bytes), and the frame (image) data follows.

    +

    There is very little documentation of the ECAT format, and many of the comments +in this code come from a combination of trial and error and wild speculation.

    +

    XMedcon can read and write ECAT 6 format, and read ECAT 7 format: see +http://xmedcon.sourceforge.net and the ECAT files in the source of XMedCon, +currently libs/tpc/*ecat* and source/m-ecat*. Unfortunately XMedCon is +GPL and some of the header files are adapted from CTI files (called CTI code +below). It’s not clear what the licenses are for these files.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    EcatHeader([binaryblock, endianness, check])

    Class for basic Ecat PET header

    EcatImage(dataobj, affine, header, ...[, ...])

    Class returns a list of Ecat images, with one image(hdr/data) per frame

    EcatImageArrayProxy(subheader)

    Ecat implementation of array proxy protocol

    EcatSubHeader(hdr, mlist, fileobj)

    parses the subheaders in the ecat (.v) file there is one subheader for each frame in the ecat file

    get_frame_order(mlist)

    Returns the order of the frames stored in the file Sometimes Frames are not stored in the file in chronological order, this can be used to extract frames in correct order

    get_series_framenumbers(mlist)

    Returns framenumber of data as it was collected, as part of a series; not just the order of how it was stored in this or across other files

    read_mlist(fileobj, endianness)

    read (nframes, 4) matrix list array from fileobj

    read_subheaders(fileobj, mlist, endianness)

    Retrieve all subheaders and return list of subheader recarrays

    +
    +

    EcatHeader

    +
    +
    +class nibabel.ecat.EcatHeader(binaryblock=None, endianness=None, check=True)
    +

    Bases: WrapStruct, SpatialHeader

    +

    Class for basic Ecat PET header

    +

    Sub-parts of standard Ecat File

    +
      +
    • main header

    • +
    • matrix list +which lists the information for each frame collected (can have 1 to many +frames)

    • +
    • subheaders specific to each frame with possibly-variable sized data +blocks

    • +
    +

    This just reads the main Ecat Header, it does not load the data or read the +mlist or any sub headers

    +

    Initialize Ecat header from bytes object

    +
    +
    Parameters:
    +
    +
    binaryblock{None, bytes} optional

    binary block to set into header, By default, None in which case we +insert default empty header block

    +
    +
    endianness{None, ‘<’, ‘>’, other endian code}, optional

    endian code of binary block, If None, guess endianness +from the data

    +
    +
    check{True, False}, optional

    Whether to check and fix header for errors. No checks currently +implemented, so value has no effect.

    +
    +
    +
    +
    +
    +
    +__init__(binaryblock=None, endianness=None, check=True)
    +

    Initialize Ecat header from bytes object

    +
    +
    Parameters:
    +
    +
    binaryblock{None, bytes} optional

    binary block to set into header, By default, None in which case we +insert default empty header block

    +
    +
    endianness{None, ‘<’, ‘>’, other endian code}, optional

    endian code of binary block, If None, guess endianness +from the data

    +
    +
    check{True, False}, optional

    Whether to check and fix header for errors. No checks currently +implemented, so value has no effect.

    +
    +
    +
    +
    +
    + +
    +
    +classmethod default_structarr(endianness=None)
    +

    Return header data for empty header with given endianness

    +
    + +
    +
    +get_data_dtype()
    +

    Get numpy dtype for data from header

    +
    + +
    +
    +get_filetype()
    +

    Type of ECAT Matrix File from code stored in header

    +
    + +
    +
    +get_patient_orient()
    +

    gets orientation of patient based on code stored +in header, not always reliable

    +
    + +
    +
    +classmethod guessed_endian(hdr)
    +

    Guess endian from MAGIC NUMBER value of header data

    +
    + +
    +
    +template_dtype = dtype([('magic_number', 'S14'), ('original_filename', 'S32'), ('sw_version', '<u2'), ('system_type', '<u2'), ('file_type', '<u2'), ('serial_number', 'S10'), ('scan_start_time', '<u4'), ('isotope_name', 'S8'), ('isotope_halflife', '<f4'), ('radiopharmaceutical', 'S32'), ('gantry_tilt', '<f4'), ('gantry_rotation', '<f4'), ('bed_elevation', '<f4'), ('intrinsic_tilt', '<f4'), ('wobble_speed', '<u2'), ('transm_source_type', '<u2'), ('distance_scanned', '<f4'), ('transaxial_fov', '<f4'), ('angular_compression', '<u2'), ('coin_samp_mode', '<u2'), ('axial_samp_mode', '<u2'), ('ecat_calibration_factor', '<f4'), ('calibration_unitS', '<u2'), ('calibration_units_type', '<u2'), ('compression_code', '<u2'), ('study_type', 'S12'), ('patient_id', 'S16'), ('patient_name', 'S32'), ('patient_sex', 'S1'), ('patient_dexterity', 'S1'), ('patient_age', '<f4'), ('patient_height', '<f4'), ('patient_weight', '<f4'), ('patient_birth_date', '<u4'), ('physician_name', 'S32'), ('operator_name', 'S32'), ('study_description', 'S32'), ('acquisition_type', '<u2'), ('patient_orientation', '<u2'), ('facility_name', 'S20'), ('num_planes', '<u2'), ('num_frames', '<u2'), ('num_gates', '<u2'), ('num_bed_pos', '<u2'), ('init_bed_position', '<f4'), ('bed_position', '<f4', (15,)), ('plane_separation', '<f4'), ('lwr_sctr_thres', '<u2'), ('lwr_true_thres', '<u2'), ('upr_true_thres', '<u2'), ('user_process_code', 'S10'), ('acquisition_mode', '<u2'), ('bin_size', '<f4'), ('branching_fraction', '<f4'), ('dose_start_time', '<u4'), ('dosage', '<f4'), ('well_counter_corr_factor', '<f4'), ('data_units', 'S32'), ('septa_state', '<u2'), ('fill', 'S12')])
    +
    + +
    + +
    +
    +

    EcatImage

    +
    +
    +class nibabel.ecat.EcatImage(dataobj, affine, header, subheader, mlist, extra=None, file_map=None)
    +

    Bases: SpatialImage

    +

    Class returns a list of Ecat images, with one image(hdr/data) per frame

    +

    Initialize Image

    +

    The image is a combination of +(array, affine matrix, header, subheader, mlist) +with optional meta data in extra, and filename / file-like objects +contained in the file_map.

    +
    +
    Parameters:
    +
    +
    dataobjarray-like

    image data

    +
    +
    affineNone or (4,4) array-like

    homogeneous affine giving relationship between voxel coords and +world coords.

    +
    +
    headerNone or header instance

    meta data for this image format

    +
    +
    subheaderNone or subheader instance

    meta data for each sub-image for frame in the image

    +
    +
    mlistNone or array

    Matrix list array giving offset and order of data in file

    +
    +
    extraNone or mapping, optional

    metadata associated with this image that cannot be +stored in header or subheader

    +
    +
    file_mapmapping, optional

    mapping giving file information for this image format

    +
    +
    +
    +
    +

    Examples

    +
    >>> import os
    +>>> import nibabel as nib
    +>>> nibabel_dir = os.path.dirname(nib.__file__)
    +>>> from nibabel import ecat
    +>>> ecat_file = os.path.join(nibabel_dir,'tests','data','tinypet.v')
    +>>> img = ecat.load(ecat_file)
    +>>> frame0 = img.get_frame(0)
    +>>> frame0.shape == (10, 10, 3)
    +True
    +>>> data4d = img.get_fdata()
    +>>> data4d.shape == (10, 10, 3, 1)
    +True
    +
    +
    +
    +
    +__init__(dataobj, affine, header, subheader, mlist, extra=None, file_map=None)
    +

    Initialize Image

    +

    The image is a combination of +(array, affine matrix, header, subheader, mlist) +with optional meta data in extra, and filename / file-like objects +contained in the file_map.

    +
    +
    Parameters:
    +
    +
    dataobjarray-like

    image data

    +
    +
    affineNone or (4,4) array-like

    homogeneous affine giving relationship between voxel coords and +world coords.

    +
    +
    headerNone or header instance

    meta data for this image format

    +
    +
    subheaderNone or subheader instance

    meta data for each sub-image for frame in the image

    +
    +
    mlistNone or array

    Matrix list array giving offset and order of data in file

    +
    +
    extraNone or mapping, optional

    metadata associated with this image that cannot be +stored in header or subheader

    +
    +
    file_mapmapping, optional

    mapping giving file information for this image format

    +
    +
    +
    +
    +

    Examples

    +
    >>> import os
    +>>> import nibabel as nib
    +>>> nibabel_dir = os.path.dirname(nib.__file__)
    +>>> from nibabel import ecat
    +>>> ecat_file = os.path.join(nibabel_dir,'tests','data','tinypet.v')
    +>>> img = ecat.load(ecat_file)
    +>>> frame0 = img.get_frame(0)
    +>>> frame0.shape == (10, 10, 3)
    +True
    +>>> data4d = img.get_fdata()
    +>>> data4d.shape == (10, 10, 3, 1)
    +True
    +
    +
    +
    + +
    +
    +ImageArrayProxy
    +

    alias of EcatImageArrayProxy

    +
    + +
    +
    +property affine
    +
    + +
    +
    +files_types: tuple[ExtensionSpec, ...] = (('image', '.v'), ('header', '.v'))
    +
    + +
    +
    +classmethod from_file_map(file_map, *, mmap=True, keep_file_open=None)
    +

    class method to create image from mapping +specified in file_map

    +
    + +
    +
    +classmethod from_image(img)
    +

    Class method to create new instance of own class from img

    +
    +
    Parameters:
    +
    +
    imgspatialimage instance

    In fact, an object with the API of spatialimage - +specifically dataobj, affine, header and extra.

    +
    +
    +
    +
    Returns:
    +
    +
    cimgspatialimage instance

    Image, of our own class

    +
    +
    +
    +
    +
    + +
    +
    +get_data_dtype(frame)
    +
    + +
    +
    +get_frame(frame, orientation=None)
    +

    Get full volume for a time frame

    +
    +
    Parameters:
    +
      +
    • frame – Time frame index from where to fetch data

    • +
    • orientation – None (default), ‘neurological’ or ‘radiological’

    • +
    +
    +
    Return type:
    +

    Numpy array containing (possibly oriented) raw data

    +
    +
    +
    + +
    +
    +get_frame_affine(frame)
    +

    returns 4X4 affine

    +
    + +
    +
    +get_mlist()
    +

    get access to the mlist

    +
    + +
    +
    +get_subheaders()
    +

    get access to subheaders

    +
    + +
    +
    +header_class
    +

    alias of EcatHeader

    +
    + +
    +
    +classmethod load(filespec)
    +

    Class method to create image from filename filename

    +
    +
    Parameters:
    +
    +
    filenamestr

    Filename of image to load

    +
    +
    mmap{True, False, ‘c’, ‘r’}, optional, keyword only

    mmap controls the use of numpy memory mapping for reading image +array data. If False, do not try numpy memmap for data array. +If one of {‘c’, ‘r’}, try numpy memmap with mode=mmap. A +mmap value of True gives the same behavior as mmap='c'. If +image data file cannot be memory-mapped, ignore mmap value and +read array from file.

    +
    +
    keep_file_open{ None, True, False }, optional, keyword only

    keep_file_open controls whether a new file handle is created +every time the image is accessed, or a single file handle is +created and used for the lifetime of this ArrayProxy. If +True, a single file handle is created and used. If False, +a new file handle is created every time the image is accessed. +The default value (None) will result in the value of +nibabel.arrayproxy.KEEP_FILE_OPEN_DEFAULT being used.

    +
    +
    +
    +
    Returns:
    +
    +
    imgDataobjImage instance
    +
    +
    +
    +
    + +
    +
    +property shape
    +
    + +
    +
    +subheader_class
    +

    alias of EcatSubHeader

    +
    + +
    +
    +to_file_map(file_map=None)
    +

    Write ECAT7 image to file_map or contained self.file_map

    +

    The format consist of:

    +
      +
    • +
      A main header (512L) with dictionary entries in the form

      [numAvail, nextDir, previousDir, numUsed]

      +
      +
      +
    • +
    • For every frame (3D volume in 4D data) +- A subheader (size = frame_offset) +- Frame data (3D volume)

    • +
    +
    + +
    +
    +valid_exts: tuple[str, ...] = ('.v',)
    +
    + +
    + +
    +
    +

    EcatImageArrayProxy

    +
    +
    +class nibabel.ecat.EcatImageArrayProxy(subheader)
    +

    Bases: object

    +

    Ecat implementation of array proxy protocol

    +

    The array proxy allows us to freeze the passed fileobj and +header such that it returns the expected data array.

    +
    +
    +__init__(subheader)
    +
    + +
    +
    +property is_proxy
    +
    + +
    +
    +property ndim
    +
    + +
    +
    +property shape
    +
    + +
    + +
    +
    +

    EcatSubHeader

    +
    +
    +class nibabel.ecat.EcatSubHeader(hdr, mlist, fileobj)
    +

    Bases: object

    +

    parses the subheaders in the ecat (.v) file +there is one subheader for each frame in the ecat file

    +
    +
    Parameters:
    +
    +
    hdrEcatHeader

    ECAT main header

    +
    +
    mlistarray shape (N, 4)

    Matrix list

    +
    +
    fileobjECAT file <filename>.v fileholder or file object

    with read, seek methods

    +
    +
    +
    +
    +
    +
    +__init__(hdr, mlist, fileobj)
    +

    parses the subheaders in the ecat (.v) file +there is one subheader for each frame in the ecat file

    +
    +
    Parameters:
    +
    +
    hdrEcatHeader

    ECAT main header

    +
    +
    mlistarray shape (N, 4)

    Matrix list

    +
    +
    fileobjECAT file <filename>.v fileholder or file object

    with read, seek methods

    +
    +
    +
    +
    +
    + +
    +
    +data_from_fileobj(frame=0, orientation=None)
    +

    Read scaled data from file for a given frame

    +
    +
    Parameters:
    +
      +
    • frame – Time frame index from where to fetch data

    • +
    • orientation – None (default), ‘neurological’ or ‘radiological’

    • +
    +
    +
    Return type:
    +

    Numpy array containing (possibly oriented) raw data

    +
    +
    +
    +

    See also

    +

    raw_data_from_fileobj

    +
    +
    + +
    +
    +get_frame_affine(frame=0)
    +

    returns best affine for given frame of data

    +
    + +
    +
    +get_nframes()
    +

    returns number of frames

    +
    + +
    +
    +get_shape(frame=0)
    +

    returns shape of given frame

    +
    + +
    +
    +get_zooms(frame=0)
    +

    returns zooms …pixdims

    +
    + +
    +
    +raw_data_from_fileobj(frame=0, orientation=None)
    +

    Get raw data from file object.

    +
    +
    Parameters:
    +
      +
    • frame – Time frame index from where to fetch data

    • +
    • orientation – None (default), ‘neurological’ or ‘radiological’

    • +
    +
    +
    Return type:
    +

    Numpy array containing (possibly oriented) raw data

    +
    +
    +
    +

    See also

    +

    data_from_fileobj

    +
    +
    + +
    + +
    +
    +

    get_frame_order

    +
    +
    +nibabel.ecat.get_frame_order(mlist)
    +

    Returns the order of the frames stored in the file +Sometimes Frames are not stored in the file in +chronological order, this can be used to extract frames +in correct order

    +
    +
    Returns:
    +
    +
    id_dict: dict mapping frame number -> [mlist_row, mlist_id]
    +
    (where mlist id is value in the first column of the mlist matrix )
    +
    +
    +
    +

    Examples

    +
    >>> import os
    +>>> import nibabel as nib
    +>>> nibabel_dir = os.path.dirname(nib.__file__)
    +>>> from nibabel import ecat
    +>>> ecat_file = os.path.join(nibabel_dir,'tests','data','tinypet.v')
    +>>> img = ecat.load(ecat_file)
    +>>> mlist = img.get_mlist()
    +>>> get_frame_order(mlist)
    +{0: [0, 16842758]}
    +
    +
    +
    + +
    +
    +

    get_series_framenumbers

    +
    +
    +nibabel.ecat.get_series_framenumbers(mlist)
    +

    Returns framenumber of data as it was collected, +as part of a series; not just the order of how it was +stored in this or across other files

    +

    For example, if the data is split between multiple files +this should give you the true location of this frame as +collected in the series +(Frames are numbered starting at ONE (1) not Zero)

    +
    +
    Returns:
    +
    +
    frame_dict: dict mapping order_stored -> frame in series

    where frame in series counts from 1; [1,2,3,4…]

    +
    +
    +
    +
    +

    Examples

    +
    >>> import os
    +>>> import nibabel as nib
    +>>> nibabel_dir = os.path.dirname(nib.__file__)
    +>>> from nibabel import ecat
    +>>> ecat_file = os.path.join(nibabel_dir,'tests','data','tinypet.v')
    +>>> img = ecat.load(ecat_file)
    +>>> mlist = img.get_mlist()
    +>>> get_series_framenumbers(mlist)
    +{0: 1}
    +
    +
    +
    + +
    +
    +

    read_mlist

    +
    +
    +nibabel.ecat.read_mlist(fileobj, endianness)
    +

    read (nframes, 4) matrix list array from fileobj

    +
    +
    Parameters:
    +
    +
    fileobjfile-like

    an open file-like object implementing seek and read

    +
    +
    +
    +
    Returns:
    +
    +
    mlist(nframes, 4) ndarray

    matrix list is an array with nframes rows and columns:

    +
      +
    • 0: Matrix identifier (frame number)

    • +
    • 1: matrix data start block number (subheader followed by image data)

    • +
    • 2: Last block number of matrix (image) data

    • +
    • 3: Matrix status

      +
      +
        +
      • 1: hxists - rw

      • +
      • 2: exists - ro

      • +
      • 3: matrix deleted

      • +
      +
      +
    • +
    +
    +
    +
    +
    +

    Notes

    +

    A block is 512 bytes.

    +

    block_no in the code below is 1-based. block 1 is the main header, +and the mlist blocks start at block number 2.

    +

    The 512 bytes in an mlist block contain 32 rows of the int32 (nframes, +4) mlist matrix.

    +

    The first row of these 32 looks like a special row. The 4 values appear +to be (respectively):

    +
      +
    • not sure - maybe negative number of mlist rows (out of 31) that are +blank and not used in this block. Called nfree but unused in CTI +code;

    • +
    • block_no - of next set of mlist entries or 2 if no more entries. We also +allow 1 or 0 to signal no more entries;

    • +
    • <no idea>. Called prvblk in CTI code, so maybe previous block no;

    • +
    • n_rows - number of mlist rows in this block (between ?0 and 31) (called +nused in CTI code).

    • +
    +
    + +
    +
    +

    read_subheaders

    +
    +
    +nibabel.ecat.read_subheaders(fileobj, mlist, endianness)
    +

    Retrieve all subheaders and return list of subheader recarrays

    +
    +
    Parameters:
    +
    +
    fileobjfile-like

    implementing read and seek

    +
    +
    mlist(nframes, 4) ndarray

    Columns are: +* 0 - Matrix identifier. +* 1 - subheader block number +* 2 - Last block number of matrix data block. +* 3 - Matrix status

    +
    +
    endianness{‘<’, ‘>’}

    little / big endian code

    +
    +
    +
    +
    Returns:
    +
    +
    subheaderslist

    List of subheader structured arrays

    +
    +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.environment.html b/reference/nibabel.environment.html new file mode 100644 index 0000000000..7d0d3da618 --- /dev/null +++ b/reference/nibabel.environment.html @@ -0,0 +1,233 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    environment

    +

    Settings from the system environment relevant to NIPY

    + + + + + + + + + + + + +

    get_home_dir()

    Return the closest possible equivalent to a 'home' directory.

    get_nipy_system_dir()

    Get systemwide NIPY configuration file directory

    get_nipy_user_dir()

    Get the NIPY user directory

    +
    +

    get_home_dir

    +
    +
    +nibabel.environment.get_home_dir()
    +

    Return the closest possible equivalent to a ‘home’ directory.

    +

    The path may not exist; code using this routine should not +expect the directory to exist.

    +
    +
    Parameters:
    +
    +
    None
    +
    +
    +
    Returns:
    +
    +
    home_dirstring

    best guess at location of home directory

    +
    +
    +
    +
    +
    + +
    +
    +

    get_nipy_system_dir

    +
    +
    +nibabel.environment.get_nipy_system_dir()
    +

    Get systemwide NIPY configuration file directory

    +

    On posix systems this will be /etc/nipy. +On Windows, the directory is less useful, but by default it will be +C:\etc\nipy

    +

    The path may well not exist; code using this routine should not +expect the directory to exist.

    +
    +
    Parameters:
    +
    +
    None
    +
    +
    +
    Returns:
    +
    +
    nipy_dirstring

    path to systemwide NIPY configuration directory

    +
    +
    +
    +
    +

    Examples

    +
    >>> pth = get_nipy_system_dir()
    +
    +
    +
    + +
    +
    +

    get_nipy_user_dir

    +
    +
    +nibabel.environment.get_nipy_user_dir()
    +

    Get the NIPY user directory

    +

    This uses the logic in get_home_dir to find the home directory +and the adds either .nipy or _nipy to the end of the path.

    +

    We check first in environment variable NIPY_USER_DIR, otherwise +returning the default of <homedir>/.nipy (Unix) or +<homedir>/_nipy (Windows)

    +

    The path may well not exist; code using this routine should not +expect the directory to exist.

    +
    +
    Parameters:
    +
    +
    None
    +
    +
    +
    Returns:
    +
    +
    nipy_dirstring

    path to user’s NIPY configuration directory

    +
    +
    +
    +
    +

    Examples

    +
    >>> pth = get_nipy_user_dir()
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.eulerangles.html b/reference/nibabel.eulerangles.html new file mode 100644 index 0000000000..b8978fb368 --- /dev/null +++ b/reference/nibabel.eulerangles.html @@ -0,0 +1,519 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    eulerangles

    +

    Module implementing Euler angle rotations and their conversions

    +

    See:

    + +

    See also: Representing Attitude with Euler Angles and Quaternions: A +Reference (2006) by James Diebel. A cached PDF link last found here:

    +

    http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.110.5134

    +

    Euler’s rotation theorem tells us that any rotation in 3D can be +described by 3 angles. Let’s call the 3 angles the Euler angle vector +and call the angles in the vector \(alpha\), \(beta\) and +\(gamma\). The vector is [ \(alpha\), +\(beta\). \(gamma\) ] and, in this description, the order of the +parameters specifies the order in which the rotations occur (so the +rotation corresponding to \(alpha\) is applied first).

    +

    In order to specify the meaning of an Euler angle vector we need to +specify the axes around which each of the rotations corresponding to +\(alpha\), \(beta\) and \(gamma\) will occur.

    +

    There are therefore three axes for the rotations \(alpha\), +\(beta\) and \(gamma\); let’s call them \(i\) \(j\), +\(k\).

    +

    Let us express the rotation \(alpha\) around axis i as a 3 by 3 +rotation matrix A. Similarly \(beta\) around j becomes 3 x 3 +matrix B and \(gamma\) around k becomes matrix G. Then the +whole rotation expressed by the Euler angle vector [ \(alpha\), +\(beta\). \(gamma\) ], R is given by:

    +
    R = np.dot(G, np.dot(B, A))
    +
    +
    +

    See http://mathworld.wolfram.com/EulerAngles.html

    +

    The order \(G B A\) expresses the fact that the rotations are +performed in the order of the vector (\(alpha\) around axis i = +A first).

    +

    To convert a given Euler angle vector to a meaningful rotation, and a +rotation matrix, we need to define:

    +
      +
    • the axes i, j, k

    • +
    • whether a rotation matrix should be applied on the left of a vector to +be transformed (vectors are column vectors) or on the right (vectors +are row vectors).

    • +
    • whether the rotations move the axes as they are applied (intrinsic +rotations) - compared the situation where the axes stay fixed and the +vectors move within the axis frame (extrinsic)

    • +
    • the handedness of the coordinate system

    • +
    +

    See: https://en.wikipedia.org/wiki/Rotation_matrix#Ambiguities

    +

    We are using the following conventions:

    +
      +
    • axes i, j, k are the z, y, and x axes respectively. Thus +an Euler angle vector [ \(alpha\), \(beta\). \(gamma\) ] +in our convention implies a \(alpha\) radian rotation around the +z axis, followed by a \(beta\) rotation around the y axis, +followed by a \(gamma\) rotation around the x axis.

    • +
    • the rotation matrix applies on the left, to column vectors on the +right, so if R is the rotation matrix, and v is a 3 x N matrix +with N column vectors, the transformed vector set vdash is given by +vdash = np.dot(R, v).

    • +
    • extrinsic rotations - the axes are fixed, and do not move with the +rotations.

    • +
    • a right-handed coordinate system

    • +
    +

    The convention of rotation around z, followed by rotation around +y, followed by rotation around x, is known (confusingly) as +“xyz”, pitch-roll-yaw, Cardan angles, or Tait-Bryan angles.

    + + + + + + + + + + + + + + + + + + + + + +

    angle_axis2euler(theta, vector[, is_normalized])

    Convert angle, axis pair to Euler angles

    euler2angle_axis([z, y, x])

    Return angle, axis corresponding to these Euler angles

    euler2mat([z, y, x])

    Return matrix for rotations around z, y and x axes

    euler2quat([z, y, x])

    Return quaternion corresponding to these Euler angles

    mat2euler(M[, cy_thresh])

    Discover Euler angle vector from 3x3 matrix

    quat2euler(q)

    Return Euler angles corresponding to quaternion q

    +
    +

    angle_axis2euler

    +
    +
    +nibabel.eulerangles.angle_axis2euler(theta, vector, is_normalized=False)
    +

    Convert angle, axis pair to Euler angles

    +
    +
    Parameters:
    +
    +
    thetascalar

    angle of rotation

    +
    +
    vector3 element sequence

    vector specifying axis for rotation.

    +
    +
    is_normalizedbool, optional

    True if vector is already normalized (has norm of 1). Default +False

    +
    +
    +
    +
    Returns:
    +
    +
    zscalar
    +
    yscalar
    +
    xscalar

    Rotations in radians around z, y, x axes, respectively

    +
    +
    +
    +
    +

    Notes

    +

    It’s possible to reduce the amount of calculation a little, by +combining parts of the angle_axis2mat and mat2euler +functions, but the reduction in computation is small, and the code +repetition is large.

    +

    Examples

    +
    >>> z, y, x = angle_axis2euler(0, [1, 0, 0])
    +>>> np.allclose((z, y, x), 0)
    +True
    +
    +
    +
    + +
    +
    +

    euler2angle_axis

    +
    +
    +nibabel.eulerangles.euler2angle_axis(z=0, y=0, x=0)
    +

    Return angle, axis corresponding to these Euler angles

    +

    Uses the z, then y, then x convention above

    +
    +
    Parameters:
    +
    +
    zscalar

    Rotation angle in radians around z-axis (performed first)

    +
    +
    yscalar

    Rotation angle in radians around y-axis

    +
    +
    xscalar

    Rotation angle in radians around x-axis (performed last)

    +
    +
    +
    +
    Returns:
    +
    +
    thetascalar

    angle of rotation

    +
    +
    vectorarray shape (3,)

    axis around which rotation occurs

    +
    +
    +
    +
    +

    Examples

    +
    >>> theta, vec = euler2angle_axis(0, 1.5, 0)
    +>>> print(theta)
    +1.5
    +>>> np.allclose(vec, [0, 1, 0])
    +True
    +
    +
    +
    + +
    +
    +

    euler2mat

    +
    +
    +nibabel.eulerangles.euler2mat(z=0, y=0, x=0)
    +

    Return matrix for rotations around z, y and x axes

    +

    Uses the z, then y, then x convention above

    +
    +
    Parameters:
    +
    +
    zscalar

    Rotation angle in radians around z-axis (performed first)

    +
    +
    yscalar

    Rotation angle in radians around y-axis

    +
    +
    xscalar

    Rotation angle in radians around x-axis (performed last)

    +
    +
    +
    +
    Returns:
    +
    +
    Marray shape (3,3)

    Rotation matrix giving same rotation as for given angles

    +
    +
    +
    +
    +

    Notes

    +

    The direction of rotation is given by the right-hand rule (orient +the thumb of the right hand along the axis around which the rotation +occurs, with the end of the thumb at the positive end of the axis; +curl your fingers; the direction your fingers curl is the direction +of rotation). Therefore, the rotations are counterclockwise if +looking along the axis of rotation from positive to negative.

    +

    Examples

    +
    >>> zrot = 1.3 # radians
    +>>> yrot = -0.1
    +>>> xrot = 0.2
    +>>> M = euler2mat(zrot, yrot, xrot)
    +>>> M.shape == (3, 3)
    +True
    +
    +
    +

    The output rotation matrix is equal to the composition of the +individual rotations

    +
    >>> M1 = euler2mat(zrot)
    +>>> M2 = euler2mat(0, yrot)
    +>>> M3 = euler2mat(0, 0, xrot)
    +>>> composed_M = np.dot(M3, np.dot(M2, M1))
    +>>> np.allclose(M, composed_M)
    +True
    +
    +
    +

    You can specify rotations by named arguments

    +
    >>> np.all(M3 == euler2mat(x=xrot))
    +True
    +
    +
    +

    When applying M to a vector, the vector should column vector to the +right of M. If the right hand side is a 2D array rather than a +vector, then each column of the 2D array represents a vector.

    +
    >>> vec = np.array([1, 0, 0]).reshape((3,1))
    +>>> v2 = np.dot(M, vec)
    +>>> vecs = np.array([[1, 0, 0],[0, 1, 0]]).T # giving 3x2 array
    +>>> vecs2 = np.dot(M, vecs)
    +
    +
    +

    Rotations are counter-clockwise.

    +
    >>> zred = np.dot(euler2mat(z=np.pi/2), np.eye(3))
    +>>> np.allclose(zred, [[0, -1, 0],[1, 0, 0], [0, 0, 1]])
    +True
    +>>> yred = np.dot(euler2mat(y=np.pi/2), np.eye(3))
    +>>> np.allclose(yred, [[0, 0, 1],[0, 1, 0], [-1, 0, 0]])
    +True
    +>>> xred = np.dot(euler2mat(x=np.pi/2), np.eye(3))
    +>>> np.allclose(xred, [[1, 0, 0],[0, 0, -1], [0, 1, 0]])
    +True
    +
    +
    +
    + +
    +
    +

    euler2quat

    +
    +
    +nibabel.eulerangles.euler2quat(z=0, y=0, x=0)
    +

    Return quaternion corresponding to these Euler angles

    +

    Uses the z, then y, then x convention above

    +
    +
    Parameters:
    +
    +
    zscalar

    Rotation angle in radians around z-axis (performed first)

    +
    +
    yscalar

    Rotation angle in radians around y-axis

    +
    +
    xscalar

    Rotation angle in radians around x-axis (performed last)

    +
    +
    +
    +
    Returns:
    +
    +
    quatarray shape (4,)

    Quaternion in w, x, y z (real, then vector) format

    +
    +
    +
    +
    +

    Notes

    +

    We can derive this formula in Sympy using:

    +
      +
    1. Formula giving quaternion corresponding to rotation of theta radians +about arbitrary axis: +http://mathworld.wolfram.com/EulerParameters.html

    2. +
    3. Generated formulae from 1.) for quaternions corresponding to +theta radians rotations about x, y, z axes

    4. +
    5. Apply quaternion multiplication formula - +https://en.wikipedia.org/wiki/Quaternions#Hamilton_product - to +formulae from 2.) to give formula for combined rotations.

    6. +
    +
    + +
    +
    +

    mat2euler

    +
    +
    +nibabel.eulerangles.mat2euler(M, cy_thresh=None)
    +

    Discover Euler angle vector from 3x3 matrix

    +

    Uses the conventions above.

    +
    +
    Parameters:
    +
    +
    Marray-like, shape (3,3)
    +
    cy_threshNone or scalar, optional

    threshold below which to give up on straightforward arctan for +estimating x rotation. If None (default), estimate from +precision of input.

    +
    +
    +
    +
    Returns:
    +
    +
    zscalar
    +
    yscalar
    +
    xscalar

    Rotations in radians around z, y, x axes, respectively

    +
    +
    +
    +
    +

    Notes

    +

    If there was no numerical error, the routine could be derived using +Sympy expression for z then y then x rotation matrix, which is:

    +
    [                       cos(y)*cos(z),                       -cos(y)*sin(z),         sin(y)],
    +[cos(x)*sin(z) + cos(z)*sin(x)*sin(y), cos(x)*cos(z) - sin(x)*sin(y)*sin(z), -cos(y)*sin(x)],
    +[sin(x)*sin(z) - cos(x)*cos(z)*sin(y), cos(z)*sin(x) + cos(x)*sin(y)*sin(z),  cos(x)*cos(y)]
    +
    +
    +

    with the obvious derivations for z, y, and x

    +
    +

    z = atan2(-r12, r11) +y = asin(r13) +x = atan2(-r23, r33)

    +
    +

    Problems arise when cos(y) is close to zero, because both of:

    +
    z = atan2(cos(y)*sin(z), cos(y)*cos(z))
    +x = atan2(cos(y)*sin(x), cos(x)*cos(y))
    +
    +
    +

    will be close to atan2(0, 0), and highly unstable.

    +

    The cy fix for numerical instability below is from: Graphics +Gems IV, Paul Heckbert (editor), Academic Press, 1994, ISBN: +0123361559. Specifically it comes from EulerAngles.c by Ken +Shoemake, and deals with the case where cos(y) is close to zero:

    +

    See: http://www.graphicsgems.org/

    +

    The code appears to be licensed (from the website) as “can be used +without restrictions”.

    +
    + +
    +
    +

    quat2euler

    +
    +
    +nibabel.eulerangles.quat2euler(q)
    +

    Return Euler angles corresponding to quaternion q

    +
    +
    Parameters:
    +
    +
    q4 element sequence

    w, x, y, z of quaternion

    +
    +
    +
    +
    Returns:
    +
    +
    zscalar

    Rotation angle in radians around z-axis (performed first)

    +
    +
    yscalar

    Rotation angle in radians around y-axis

    +
    +
    xscalar

    Rotation angle in radians around x-axis (performed last)

    +
    +
    +
    +
    +

    Notes

    +

    It’s possible to reduce the amount of calculation a little, by +combining parts of the quat2mat and mat2euler functions, but +the reduction in computation is small, and the code repetition is +large.

    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.filebasedimages.html b/reference/nibabel.filebasedimages.html new file mode 100644 index 0000000000..a81290f652 --- /dev/null +++ b/reference/nibabel.filebasedimages.html @@ -0,0 +1,794 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    filebasedimages

    +

    Common interface for any image format–volume or surface, binary or xml

    + + + + + + + + + + + + + + + +

    FileBasedHeader()

    Template class to implement header protocol

    FileBasedImage([header, extra, file_map])

    Abstract image class with interface for loading/saving images from disk.

    ImageFileError

    SerializableImage([header, extra, file_map])

    Abstract image class for (de)serializing images to/from byte streams/strings.

    +
    +

    FileBasedHeader

    +
    +
    +class nibabel.filebasedimages.FileBasedHeader
    +

    Bases: object

    +

    Template class to implement header protocol

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    +
    +copy() HdrT
    +

    Copy object to independent representation

    +

    The copy should not be affected by any changes to the original +object.

    +
    + +
    +
    +classmethod from_fileobj(fileobj: IOBase) HdrT
    +
    + +
    +
    +classmethod from_header(header: FileBasedHeader | Mapping | None = None) HdrT
    +
    + +
    +
    +write_to(fileobj: IOBase) None
    +
    + +
    + +
    +
    +

    FileBasedImage

    +
    +
    +class nibabel.filebasedimages.FileBasedImage(header: FileBasedHeader | Mapping | None = None, extra: Mapping | None = None, file_map: Mapping[str, FileHolder] | None = None)
    +

    Bases: object

    +

    Abstract image class with interface for loading/saving images from disk.

    +

    The class doesn’t define any image properties.

    +

    It has:

    +

    attributes:

    +
    +
      +
    • extra

    • +
    +
    +

    properties:

    +
    +
      +
    • header

    • +
    +
    +

    methods:

    +
    +
      +
    • to_filename(fname) - writes data to filename(s) derived from +fname, where the derivation may differ between formats.

    • +
    • to_file_map() - save image to files with which the image is already +associated.

    • +
    +
    +

    classmethods:

    +
    +
      +
    • from_filename(fname) - make instance by loading from filename

    • +
    • from_file_map(fmap) - make instance from file map

    • +
    • instance_to_filename(img, fname) - save img instance to +filename fname.

    • +
    +
    +

    It also has a header - some standard set of meta-data that is specific +to the image format, and extra - a dictionary container for any other +metadata.

    +

    You cannot slice an image, and trying to slice an image generates an +informative TypeError.

    +

    There are several ways of writing data

    +

    There is the usual way, which is the default:

    +
    img.to_filename(fname)
    +
    +
    +

    and that is, to take the data encapsulated by the image and cast it to +the datatype the header expects, setting any available header scaling +into the header to help the data match.

    +

    You can load the data into an image from file with:

    +
    img.from_filename(fname)
    +
    +
    +

    The image stores its associated files in its file_map attribute. In +order to just save an image, for which you know there is an associated +filename, or other storage, you can do:

    +
    img.to_file_map()
    +
    +
    +

    Files interface

    +

    The image has an attribute file_map. This is a mapping, that has keys +corresponding to the file types that an image needs for storage. For +example, the Analyze data format needs an image and a header +file type for storage:

    +
    >>> import numpy as np
    +>>> import nibabel as nib
    +>>> data = np.arange(24, dtype='f4').reshape((2,3,4))
    +>>> img = nib.AnalyzeImage(data, np.eye(4))
    +>>> sorted(img.file_map)
    +['header', 'image']
    +
    +
    +

    The values of file_map are not in fact files but objects with +attributes filename, fileobj and pos.

    +

    The reason for this interface, is that the contents of files has to +contain enough information so that an existing image instance can save +itself back to the files pointed to in file_map. When a file holder +holds active file-like objects, then these may be affected by the +initial file read; in this case, the file-like objects need to +carry the position at which a write (with to_file_map) should place the +data. The file_map contents should therefore be such, that this will +work.

    +

    Initialize image

    +

    The image is a combination of (header), with +optional metadata in extra, and filename / file-like objects +contained in the file_map mapping.

    +
    +
    Parameters:
    +
    +
    headerNone or mapping or header instance, optional

    metadata for this image format

    +
    +
    extraNone or mapping, optional

    metadata to associate with image that cannot be stored in the +metadata of this image type

    +
    +
    file_mapmapping, optional

    mapping giving file information for this image format

    +
    +
    +
    +
    +
    +
    +__init__(header: FileBasedHeader | Mapping | None = None, extra: Mapping | None = None, file_map: Mapping[str, FileHolder] | None = None)
    +

    Initialize image

    +

    The image is a combination of (header), with +optional metadata in extra, and filename / file-like objects +contained in the file_map mapping.

    +
    +
    Parameters:
    +
    +
    headerNone or mapping or header instance, optional

    metadata for this image format

    +
    +
    extraNone or mapping, optional

    metadata to associate with image that cannot be stored in the +metadata of this image type

    +
    +
    file_mapmapping, optional

    mapping giving file information for this image format

    +
    +
    +
    +
    +
    + +
    +
    +files_types: tuple[ExtensionSpec, ...] = (('image', None),)
    +
    + +
    +
    +classmethod filespec_to_file_map(filespec: FileSpec) FileMap
    +

    Make file_map for this class from filename filespec

    +

    Class method

    +
    +
    Parameters:
    +
    +
    filespecstr or os.PathLike

    Filename that might be for this image file type.

    +
    +
    +
    +
    Returns:
    +
    +
    file_mapdict

    file_map dict with (key, value) pairs of (file_type, +FileHolder instance), where file_type is a string giving the +type of the contained file.

    +
    +
    +
    +
    Raises:
    +
    +
    ImageFileError

    if filespec is not recognizable as being a filename for this +image type.

    +
    +
    +
    +
    +
    + +
    +
    +classmethod from_file_map(file_map: Mapping[str, FileHolder]) ImgT
    +
    + +
    +
    +classmethod from_filename(filename: FileSpec) ImgT
    +
    + +
    +
    +classmethod from_image(img: FileBasedImage) ImgT
    +

    Class method to create new instance of own class from img

    +
    +
    Parameters:
    +
    +
    imgFileBasedImage instance

    In fact, an object with the API of FileBasedImage.

    +
    +
    +
    +
    Returns:
    +
    +
    imgFileBasedImage instance

    Image, of our own class

    +
    +
    +
    +
    +
    + +
    +
    +get_filename() str | None
    +

    Fetch the image filename

    +
    +
    Parameters:
    +
    +
    None
    +
    +
    +
    Returns:
    +
    +
    fnameNone or str

    Returns None if there is no filename, or a filename string. +If an image may have several filenames associated with it (e.g. +Analyze .img, .hdr pair) then we return the more characteristic +filename (the .img filename in the case of Analyze’)

    +
    +
    +
    +
    +
    + +
    +
    +property header: FileBasedHeader
    +
    + +
    +
    +header_class
    +

    alias of FileBasedHeader

    +
    + +
    +
    +classmethod instance_to_filename(img: FileBasedImage, filename: FileSpec) None
    +

    Save img in our own format, to name implied by filename

    +

    This is a class method

    +
    +
    Parameters:
    +
    +
    imgany FileBasedImage instance
    +
    filenamestr

    Filename, implying name to which to save image.

    +
    +
    +
    +
    +
    + +
    +
    +classmethod load(filename: FileSpec) ImgT
    +
    + +
    +
    +classmethod make_file_map(mapping: Mapping[str, str | IOBase] | None = None) Mapping[str, FileHolder]
    +

    Class method to make files holder for this image type

    +
    +
    Parameters:
    +
    +
    mappingNone or mapping, optional

    mapping with keys corresponding to image file types (such as +‘image’, ‘header’ etc, depending on image class) and values +that are filenames or file-like. Default is None

    +
    +
    +
    +
    Returns:
    +
    +
    file_mapdict

    dict with string keys given by first entry in tuples in +sequence klass.files_types, and values of type FileHolder, +where FileHolder objects have default values, other than +those given by mapping

    +
    +
    +
    +
    +
    + +
    +
    +makeable: bool = True
    +
    + +
    +
    +classmethod path_maybe_image(filename: FileSpec, sniff: FileSniff | None = None, sniff_max: int = 1024) tuple[bool, FileSniff | None]
    +

    Return True if filename may be image matching this class

    +
    +
    Parameters:
    +
    +
    filenamestr or os.PathLike

    Filename for an image, or an image header (metadata) file. +If filename points to an image data file, and the image type has +a separate “header” file, we work out the name of the header file, +and read from that instead of filename.

    +
    +
    sniffNone or (bytes, filename), optional

    Bytes content read from a previous call to this method, on another +class, with metadata filename. This allows us to read metadata +bytes once from the image or header, and pass this read set of +bytes to other image classes, therefore saving a repeat read of the +metadata. filename is used to validate that metadata would be +read from the same file, re-reading if not. None forces this +method to read the metadata.

    +
    +
    sniff_maxint, optional

    The maximum number of bytes to read from the metadata. If the +metadata file is long enough, we read this many bytes from the +file, otherwise we read to the end of the file. Longer values +sniff more of the metadata / image file, making it more likely that +the returned sniff will be useful for later calls to +path_maybe_image for other image classes.

    +
    +
    +
    +
    Returns:
    +
    +
    maybe_imagebool

    True if filename may be valid for an image of this class.

    +
    +
    sniffNone or (bytes, filename)

    Read bytes content from found metadata. May be None if the file +does not appear to have useful metadata.

    +
    +
    +
    +
    +
    + +
    +
    +rw: bool = True
    +
    + +
    +
    +set_filename(filename: str) None
    +

    Sets the files in the object from a given filename

    +

    The different image formats may check whether the filename has +an extension characteristic of the format, and raise an error if +not.

    +
    +
    Parameters:
    +
    +
    filenamestr or os.PathLike

    If the image format only has one file associated with it, +this will be the only filename set into the image +.file_map attribute. Otherwise, the image instance will +try and guess the other filenames from this given filename.

    +
    +
    +
    +
    +
    + +
    +
    +to_file_map(file_map: Mapping[str, FileHolder] | None = None, **kwargs) None
    +
    + +
    +
    +to_filename(filename: FileSpec, **kwargs) None
    +

    Write image to files implied by filename string

    +
    +
    Parameters:
    +
    +
    filenamestr or os.PathLike

    filename to which to save image. We will parse filename +with filespec_to_file_map to work out names for image, +header etc.

    +
    +
    **kwargskeyword arguments

    Keyword arguments to format-specific save

    +
    +
    +
    +
    Returns:
    +
    +
    None
    +
    +
    +
    +
    + +
    +
    +valid_exts: tuple[str, ...] = ()
    +
    + +
    + +
    +
    +

    ImageFileError

    +
    +
    +class nibabel.filebasedimages.ImageFileError
    +

    Bases: Exception

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    + +
    +
    +

    SerializableImage

    +
    +
    +class nibabel.filebasedimages.SerializableImage(header: FileBasedHeader | Mapping | None = None, extra: Mapping | None = None, file_map: Mapping[str, FileHolder] | None = None)
    +

    Bases: FileBasedImage

    +

    Abstract image class for (de)serializing images to/from byte streams/strings.

    +

    The class doesn’t define any image properties.

    +

    It has:

    +

    methods:

    +
    +
      +
    • to_bytes() - serialize image to byte string

    • +
    +
    +

    classmethods:

    +
    +
      +
    • from_bytes(bytestring) - make instance by deserializing a byte string

    • +
    • from_http://github.com/url(url) - make instance by fetching and deserializing a URL

    • +
    +
    +

    Loading from byte strings should provide round-trip equivalence:

    +
    img_a = klass.from_bytes(bstr)
    +img_b = klass.from_bytes(img_a.to_bytes())
    +
    +np.allclose(img_a.get_fdata(), img_b.get_fdata())
    +np.allclose(img_a.affine, img_b.affine)
    +
    +
    +

    Further, for images that are single files on disk, the following methods of loading +the image must be equivalent:

    +
    img = klass.from_filename(fname)
    +
    +with open(fname, 'rb') as fobj:
    +    img = klass.from_bytes(fobj.read())
    +
    +
    +

    And the following methods of saving a file must be equivalent:

    +
    img.to_filename(fname)
    +
    +with open(fname, 'wb') as fobj:
    +    fobj.write(img.to_bytes())
    +
    +
    +

    Images that consist of separate header and data files (e.g., Analyze +images) currently do not support this interface. +For multi-file images, to_bytes() and from_bytes() must be +overridden, and any encoding details should be documented.

    +

    Initialize image

    +

    The image is a combination of (header), with +optional metadata in extra, and filename / file-like objects +contained in the file_map mapping.

    +
    +
    Parameters:
    +
    +
    headerNone or mapping or header instance, optional

    metadata for this image format

    +
    +
    extraNone or mapping, optional

    metadata to associate with image that cannot be stored in the +metadata of this image type

    +
    +
    file_mapmapping, optional

    mapping giving file information for this image format

    +
    +
    +
    +
    +
    +
    +__init__(header: FileBasedHeader | Mapping | None = None, extra: Mapping | None = None, file_map: Mapping[str, FileHolder] | None = None)
    +

    Initialize image

    +

    The image is a combination of (header), with +optional metadata in extra, and filename / file-like objects +contained in the file_map mapping.

    +
    +
    Parameters:
    +
    +
    headerNone or mapping or header instance, optional

    metadata for this image format

    +
    +
    extraNone or mapping, optional

    metadata to associate with image that cannot be stored in the +metadata of this image type

    +
    +
    file_mapmapping, optional

    mapping giving file information for this image format

    +
    +
    +
    +
    +
    + +
    +
    +classmethod from_bytes(bytestring: bytes) StreamImgT
    +

    Construct image from a byte string

    +

    Class method

    +
    +
    Parameters:
    +
    +
    bytestringbytes

    Byte string containing the on-disk representation of an image

    +
    +
    +
    +
    +
    + +
    +
    +classmethod from_stream(io_obj: IOBase) StreamImgT
    +

    Load image from readable IO stream

    +

    Convert to BytesIO to enable seeking, if input stream is not seekable

    +
    +
    Parameters:
    +
    +
    io_objIOBase object

    Readable stream

    +
    +
    +
    +
    +
    + +
    +
    +classmethod from_url(url: str | Request, timeout: float = 5) StreamImgT
    +

    Retrieve and load an image from a URL

    +

    Class method

    +
    +
    Parameters:
    +
    +
    urlstr or urllib.request.Request object

    URL of file to retrieve

    +
    +
    timeoutfloat, optional

    Time (in seconds) to wait for a response

    +
    +
    +
    +
    +
    + +
    +
    +to_bytes(**kwargs) bytes
    +

    Return a bytes object with the contents of the file that would +be written if the image were saved.

    +
    +
    Parameters:
    +
    +
    **kwargskeyword arguments

    Keyword arguments that may be passed to img.to_file_map()

    +
    +
    +
    +
    Returns:
    +
    +
    bytes

    Serialized image

    +
    +
    +
    +
    +
    + +
    +
    +to_stream(io_obj: IOBase, **kwargs) None
    +

    Save image to writable IO stream

    +
    +
    Parameters:
    +
    +
    io_objIOBase object

    Writable stream

    +
    +
    **kwargskeyword arguments

    Keyword arguments that may be passed to img.to_file_map()

    +
    +
    +
    +
    +
    + +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.fileholders.html b/reference/nibabel.fileholders.html new file mode 100644 index 0000000000..f8414de2e5 --- /dev/null +++ b/reference/nibabel.fileholders.html @@ -0,0 +1,290 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    fileholders

    +

    Fileholder class

    + + + + + + + + + + + + +

    FileHolder([filename, fileobj, pos])

    class to contain filename, fileobj and file position

    FileHolderError

    copy_file_map(file_map)

    Copy mapping of fileholders given by file_map

    +
    +

    FileHolder

    +
    +
    +class nibabel.fileholders.FileHolder(filename: str | None = None, fileobj: io.IOBase | None = None, pos: int = 0)
    +

    Bases: object

    +

    class to contain filename, fileobj and file position

    +

    Initialize FileHolder instance

    +
    +
    Parameters:
    +
    +
    filenamestr, optional

    filename. Default is None

    +
    +
    fileobjfile-like object, optional

    Should implement at least ‘seek’ (for the purposes for this +class). Default is None

    +
    +
    posint, optional

    position in filename or fileobject at which to start reading +or writing data; defaults to 0

    +
    +
    +
    +
    +
    +
    +__init__(filename: str | None = None, fileobj: io.IOBase | None = None, pos: int = 0)
    +

    Initialize FileHolder instance

    +
    +
    Parameters:
    +
    +
    filenamestr, optional

    filename. Default is None

    +
    +
    fileobjfile-like object, optional

    Should implement at least ‘seek’ (for the purposes for this +class). Default is None

    +
    +
    posint, optional

    position in filename or fileobject at which to start reading +or writing data; defaults to 0

    +
    +
    +
    +
    +
    + +
    +
    +property file_like: str | io.IOBase | None
    +

    Return self.fileobj if not None, otherwise self.filename

    +
    + +
    +
    +get_prepare_fileobj(*args, **kwargs) ImageOpener
    +

    Return fileobj if present, or return fileobj from filename

    +

    Set position to that given in self.pos

    +
    +
    Parameters:
    +
    +
    *argstuple

    positional arguments to file open. Ignored if there is a +defined self.fileobj. These might include the mode, such +as ‘rb’

    +
    +
    **kwargsdict

    named arguments to file open. Ignored if there is a +defined self.fileobj

    +
    +
    +
    +
    Returns:
    +
    +
    fileobjfile-like object

    object has position set (via fileobj.seek()) to +self.pos

    +
    +
    +
    +
    +
    + +
    +
    +same_file_as(other: FileHolder) bool
    +

    Test if self refers to same files / fileobj as other

    +
    +
    Parameters:
    +
    +
    otherobject

    object with filename and fileobj attributes

    +
    +
    +
    +
    Returns:
    +
    +
    tfbool

    True if other has the same filename (or both have None) and the +same fileobj (or both have None

    +
    +
    +
    +
    +
    + +
    + +
    +
    +

    FileHolderError

    +
    +
    +class nibabel.fileholders.FileHolderError
    +

    Bases: Exception

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    + +
    +
    +

    copy_file_map

    +
    +
    +nibabel.fileholders.copy_file_map(file_map: Mapping[str, FileHolder]) Mapping[str, FileHolder]
    +

    Copy mapping of fileholders given by file_map

    +
    +
    Parameters:
    +
    +
    file_mapmapping

    mapping of FileHolder instances

    +
    +
    +
    +
    Returns:
    +
    +
    fm_copydict

    Copy of file_map, using shallow copy of FileHolders

    +
    +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.filename_parser.html b/reference/nibabel.filename_parser.html new file mode 100644 index 0000000000..56fcd9adf7 --- /dev/null +++ b/reference/nibabel.filename_parser.html @@ -0,0 +1,333 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    filename_parser

    +

    Create filename pairs, triplets etc, with expected extensions

    + + + + + + + + + + + + + + + +

    TypesFilenamesError

    parse_filename(filename, types_exts, ...[, ...])

    Split filename into fileroot, extension, trailing suffix; guess type.

    splitext_addext(filename[, addexts, match_case])

    Split /pth/fname.ext.gz into /pth/fname, .ext, .gz

    types_filenames(template_fname, types_exts)

    Return filenames with standard extensions from template name

    +
    +

    TypesFilenamesError

    +
    +
    +class nibabel.filename_parser.TypesFilenamesError
    +

    Bases: Exception

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    + +
    +
    +

    parse_filename

    +
    +
    +nibabel.filename_parser.parse_filename(filename: FileSpec, types_exts: ty.Sequence[ExtensionSpec], trailing_suffixes: ty.Sequence[str], match_case: bool = False) tuple[str, str, str | None, str | None]
    +

    Split filename into fileroot, extension, trailing suffix; guess type.

    +
    +
    Parameters:
    +
    +
    filenamestr or os.PathLike

    filename in which to search for type extensions

    +
    +
    types_extssequence of sequences

    sequence of (name, extension) str sequences defining type to +extension mapping.

    +
    +
    trailing_suffixessequence of strings

    suffixes that should be ignored when looking for +extensions

    +
    +
    match_casebool, optional

    If True, match case of extensions and trailing suffixes when +searching in filename, otherwise do case-insensitive match.

    +
    +
    +
    +
    Returns:
    +
    +
    pthstr

    path with any matching extensions or trailing suffixes removed

    +
    +
    extstr

    If there were any matching extensions, in types_exts return +that; otherwise return extension derived from +os.path.splitext.

    +
    +
    trailingstr

    If there were any matching trailing_suffixes return that +matching suffix, otherwise ‘’

    +
    +
    guessed_typestr

    If we found a matching extension in types_exts return the +corresponding type

    +
    +
    +
    +
    +

    Examples

    +
    >>> types_exts = (('t1', 'ext1'),('t2', 'ext2'))
    +>>> parse_filename('/path/fname.funny', types_exts, ())
    +('/path/fname', '.funny', None, None)
    +>>> parse_filename('/path/fnameext2', types_exts, ())
    +('/path/fname', 'ext2', None, 't2')
    +>>> parse_filename('/path/fnameext2', types_exts, ('.gz',))
    +('/path/fname', 'ext2', None, 't2')
    +>>> parse_filename('/path/fnameext2.gz', types_exts, ('.gz',))
    +('/path/fname', 'ext2', '.gz', 't2')
    +
    +
    +
    + +
    +
    +

    splitext_addext

    +
    +
    +nibabel.filename_parser.splitext_addext(filename: FileSpec, addexts: ty.Sequence[str] = ('.gz', '.bz2', '.zst'), match_case: bool = False) tuple[str, str, str]
    +

    Split /pth/fname.ext.gz into /pth/fname, .ext, .gz

    +

    where .gz may be any of passed addext trailing suffixes.

    +
    +
    Parameters:
    +
    +
    filenamestr or os.PathLike

    filename that may end in any or none of addexts

    +
    +
    match_casebool, optional

    If True, match case of addexts and filename, otherwise do +case-insensitive match.

    +
    +
    +
    +
    Returns:
    +
    +
    frootstr

    Root of filename - e.g. /pth/fname in example above

    +
    +
    extstr

    Extension, where extension is not in addexts - e.g. .ext in +example above

    +
    +
    addextstr

    Any suffixes appearing in addext occurring at end of filename

    +
    +
    +
    +
    +

    Examples

    +
    >>> splitext_addext('fname.ext.gz')
    +('fname', '.ext', '.gz')
    +>>> splitext_addext('fname.ext')
    +('fname', '.ext', '')
    +>>> splitext_addext('fname.ext.foo', ('.foo', '.bar'))
    +('fname', '.ext', '.foo')
    +
    +
    +
    + +
    +
    +

    types_filenames

    +
    +
    +nibabel.filename_parser.types_filenames(template_fname: FileSpec, types_exts: ty.Sequence[ExtensionSpec], trailing_suffixes: ty.Sequence[str] = ('.gz', '.bz2'), enforce_extensions: bool = True, match_case: bool = False) dict[str, str]
    +

    Return filenames with standard extensions from template name

    +

    The typical case is returning image and header filenames for an +Analyze image, that expects an ‘image’ file type with extension .img, +and a ‘header’ file type, with extension .hdr.

    +
    +
    Parameters:
    +
    +
    template_fnamestr or os.PathLike

    template filename from which to construct output dict of +filenames, with given types_exts type to extension mapping. If +self.enforce_extensions is True, then filename must have one +of the defined extensions from the types list. If +self.enforce_extensions is False, then the other filenames +are guessed at by adding extensions to the base filename. +Ignored suffixes (from trailing_suffixes) append themselves to +the end of all the filenames.

    +
    +
    types_extssequence of sequences

    sequence of (name, extension) str sequences defining type to +extension mapping.

    +
    +
    trailing_suffixessequence of strings, optional

    suffixes that should be ignored when looking for +extensions - default is ('.gz', '.bz2')

    +
    +
    enforce_extensions{True, False}, optional

    If True, raise an error when attempting to set value to +type which has the wrong extension

    +
    +
    match_casebool, optional

    If True, match case of extensions and trailing suffixes when +searching in template_fname, otherwise do case-insensitive +match.

    +
    +
    +
    +
    Returns:
    +
    +
    types_fnamesdict

    dict with types as keys, and generated filenames as values. The +types are given by the first elements of the tuples in +types_exts.

    +
    +
    +
    +
    +

    Examples

    +
    >>> types_exts = (('t1','.ext1'),('t2', '.ext2'))
    +>>> tfns = types_filenames('/path/test.ext1', types_exts)
    +>>> tfns == {'t1': '/path/test.ext1', 't2': '/path/test.ext2'}
    +True
    +
    +
    +

    Bare file roots without extensions get them added

    +
    >>> tfns = types_filenames('/path/test', types_exts)
    +>>> tfns == {'t1': '/path/test.ext1', 't2': '/path/test.ext2'}
    +True
    +
    +
    +

    With enforce_extensions == False, allow first type to have any +extension.

    +
    >>> tfns = types_filenames('/path/test.funny', types_exts,
    +...                        enforce_extensions=False)
    +>>> tfns == {'t1': '/path/test.funny', 't2': '/path/test.ext2'}
    +True
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.fileslice.html b/reference/nibabel.fileslice.html new file mode 100644 index 0000000000..a1d682d5b6 --- /dev/null +++ b/reference/nibabel.fileslice.html @@ -0,0 +1,746 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    fileslice

    +

    Utilities for getting array slices out of file-like objects

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    calc_slicedefs(sliceobj, in_shape, itemsize, ...)

    Return parameters for slicing array with sliceobj given memory layout

    canonical_slicers(sliceobj, shape[, check_inds])

    Return canonical version of sliceobj for array shape shape

    fileslice(fileobj, sliceobj, shape, dtype[, ...])

    Slice array in fileobj using sliceobj slicer and array definitions

    fill_slicer(slicer, in_len)

    Return slice object with Nones filled out to match in_len

    is_fancy(sliceobj)

    Returns True if sliceobj is attempting fancy indexing

    optimize_read_slicers(sliceobj, in_shape, ...)

    Calculates slices to read from disk, and apply after reading

    optimize_slicer(slicer, dim_len, all_full, ...)

    Return maybe modified slice and post-slice slicing for slicer

    predict_shape(sliceobj, in_shape)

    Predict shape of array from slicing array shape shape with sliceobj

    read_segments(fileobj, segments, n_bytes[, lock])

    Read n_bytes byte data implied by segments from fileobj

    slice2len(slicer, in_len)

    Output length after slicing original length in_len with slicer Parameters ---------- slicer : slice object in_len : int

    slice2outax(ndim, sliceobj)

    Matching output axes for input array ndim ndim and slice sliceobj

    slicers2segments(read_slicers, in_shape, ...)

    Get segments from read_slicers given in_shape and memory steps

    strided_scalar(shape[, scalar])

    Return array shape shape where all entries point to value scalar

    threshold_heuristic(slicer, dim_len, stride)

    Whether to force full axis read or contiguous read of stepped slice

    +
    +

    calc_slicedefs

    +
    +
    +nibabel.fileslice.calc_slicedefs(sliceobj, in_shape, itemsize, offset, order, heuristic=<function threshold_heuristic>)
    +

    Return parameters for slicing array with sliceobj given memory layout

    +

    Calculate the best combination of skips / (read + discard) to use for +reading the data from disk / memory, then generate corresponding +segments, the disk offsets and read lengths to read the memory. If we +have chosen some (read + discard) optimization, then we need to discard the +surplus values from the read array using post_slicers, a slicing tuple +that takes the array as read from a file-like object, and returns the array +we want.

    +
    +
    Parameters:
    +
    +
    sliceobjobject

    something that can be used to slice an array as in arr[sliceobj]

    +
    +
    in_shapesequence

    shape of underlying array to be sliced

    +
    +
    itemsizeint

    element size in array (in bytes)

    +
    +
    offsetint

    offset of array data in underlying file or memory buffer

    +
    +
    order{‘C’, ‘F’}

    memory layout of underlying array

    +
    +
    heuristiccallable, optional

    function taking slice object, dim_len, stride length as arguments, +returning one of ‘full’, ‘contiguous’, None. See +optimize_slicer() and threshold_heuristic()

    +
    +
    +
    +
    Returns:
    +
    +
    segmentslist

    list of 2 element lists where lists are (offset, length), giving +absolute memory offset in bytes and number of bytes to read

    +
    +
    read_shapetuple

    shape with which to interpret memory as read from segments. +Interpreting the memory read from segments with this shape, and a +dtype, gives an intermediate array - call this R

    +
    +
    post_slicerstuple

    Any new slicing to be applied to the array R after reading via +segments and reshaping via read_shape. Slices are in terms of +read_shape. If empty, no new slicing to apply

    +
    +
    +
    +
    +
    + +
    +
    +

    canonical_slicers

    +
    +
    +nibabel.fileslice.canonical_slicers(sliceobj, shape, check_inds=True)
    +

    Return canonical version of sliceobj for array shape shape

    +

    sliceobj is a slicer for an array A implied by shape.

    +
      +
    • Expand sliceobj with slice(None) to add any missing (implied) axes +in sliceobj

    • +
    • Find any slicers in sliceobj that do a full axis slice and replace by +slice(None)

    • +
    • Replace any floating point values for slicing with integers

    • +
    • Replace negative integer slice values with equivalent positive integers.

    • +
    +

    Does not handle fancy indexing (indexing with arrays or array-like indices)

    +
    +
    Parameters:
    +
    +
    sliceobjobject

    something that can be used to slice an array as in arr[sliceobj]

    +
    +
    shapesequence

    shape of array that will be indexed by sliceobj

    +
    +
    check_inds{True, False}, optional

    Whether to check if integer indices are out of bounds

    +
    +
    +
    +
    Returns:
    +
    +
    can_slicerstuple

    version of sliceobj for which Ellipses have been expanded, missing +(implied) dimensions have been appended, and slice objects equivalent +to slice(None) have been replaced by slice(None), integer axes +have been checked, and negative indices set to positive equivalent

    +
    +
    +
    +
    +
    + +
    +
    +

    fileslice

    +
    +
    +nibabel.fileslice.fileslice(fileobj, sliceobj, shape, dtype, offset=0, order='C', heuristic=<function threshold_heuristic>, lock=None)
    +

    Slice array in fileobj using sliceobj slicer and array definitions

    +

    fileobj contains the contiguous binary data for an array A of shape, +dtype, memory layout shape, dtype, order, with the binary data +starting at file offset offset.

    +

    Our job is to return the sliced array A[sliceobj] in the most efficient +way in terms of memory and time.

    +

    Sometimes it will be quicker to read memory that we will later throw away, +to save time we might lose doing short seeks on fileobj. Call these +alternatives: (read + discard); and skip. This routine guesses when to +(read+discard) or skip using the callable heuristic, with a default using +a hard threshold for the memory gap large enough to prefer a skip.

    +
    +
    Parameters:
    +
    +
    fileobjfile-like object

    file-like object, opened for reading in binary mode. Implements +read and seek.

    +
    +
    sliceobjobject

    something that can be used to slice an array as in arr[sliceobj].

    +
    +
    shapesequence

    shape of full array inside fileobj.

    +
    +
    dtypedtype specifier

    dtype of array inside fileobj, or input to numpy.dtype to specify +array dtype.

    +
    +
    offsetint, optional

    offset of array data within fileobj

    +
    +
    order{‘C’, ‘F’}, optional

    memory layout of array in fileobj.

    +
    +
    heuristiccallable, optional

    function taking slice object, axis length, stride length as arguments, +returning one of ‘full’, ‘contiguous’, None. See +optimize_slicer() and see threshold_heuristic() for an +example.

    +
    +
    lock{None, threading.Lock, lock-like} optional

    If provided, used to ensure that paired calls to seek and read +cannot be interrupted by another thread accessing the same fileobj. +Each thread which accesses the same file via read_segments must +share a lock in order to ensure that the file access is thread-safe. +A lock does not need to be provided for single-threaded access. The +default value (None) results in a lock-like object (a +_NullLock) which does not do anything.

    +
    +
    +
    +
    Returns:
    +
    +
    sliced_arrarray

    Array in fileobj as sliced with sliceobj

    +
    +
    +
    +
    +
    + +
    +
    +

    fill_slicer

    +
    +
    +nibabel.fileslice.fill_slicer(slicer, in_len)
    +

    Return slice object with Nones filled out to match in_len

    +

    Also fixes too large stop / start values according to slice() slicing +rules.

    +

    The returned slicer can have a None as slicer.stop if slicer.step is +negative and the input slicer.stop is None. This is because we can’t +represent the stop as an integer, because -1 has a different meaning.

    +
    +
    Parameters:
    +
    +
    slicerslice object
    +
    in_lenint

    length of axis on which slicer will be applied

    +
    +
    +
    +
    Returns:
    +
    +
    can_slicerslice object

    slice with start, stop, step set to explicit values, with the exception +of stop for negative step, which is None for the case of slicing +down through the first element

    +
    +
    +
    +
    +
    + +
    +
    +

    is_fancy

    +
    +
    +nibabel.fileslice.is_fancy(sliceobj)
    +

    Returns True if sliceobj is attempting fancy indexing

    +
    +
    Parameters:
    +
    +
    sliceobjobject

    something that can be used to slice an array as in arr[sliceobj]

    +
    +
    +
    +
    Returns:
    +
    +
    tf: bool

    True if sliceobj represents fancy indexing, False for basic indexing

    +
    +
    +
    +
    +
    + +
    +
    +

    optimize_read_slicers

    +
    +
    +nibabel.fileslice.optimize_read_slicers(sliceobj, in_shape, itemsize, heuristic)
    +

    Calculates slices to read from disk, and apply after reading

    +
    +
    Parameters:
    +
    +
    sliceobjobject

    something that can be used to slice an array as in arr[sliceobj]. +Can be assumed to be canonical in the sense of canonical_slicers

    +
    +
    in_shapesequence

    shape of underlying array to be sliced. Array for in_shape assumed +to be already in ‘F’ order. Reorder shape / sliceobj for slicing a ‘C’ +array before passing to this function.

    +
    +
    itemsizeint

    element size in array (bytes)

    +
    +
    heuristiccallable

    function taking slice object, axis length, and stride length as +arguments, returning one of ‘full’, ‘contiguous’, None. See +optimize_slicer(); see threshold_heuristic() for an +example.

    +
    +
    +
    +
    Returns:
    +
    +
    read_slicerstuple

    sliceobj maybe rephrased to fill out dimensions that are better read +from disk and later trimmed to their original size with post_slicers. +read_slicers implies a block of memory to be read from disk. The +actual disk positions come from slicers2segments run over +read_slicers. Includes any newaxis dimensions in sliceobj

    +
    +
    post_slicerstuple

    Any new slicing to be applied to the read array after reading. The +post_slicers discard any memory that we read to save time, but that +we don’t need for the slice. Include any newaxis dimension added +by sliceobj

    +
    +
    +
    +
    +
    + +
    +
    +

    optimize_slicer

    +
    +
    +nibabel.fileslice.optimize_slicer(slicer, dim_len, all_full, is_slowest, stride, heuristic=<function threshold_heuristic>)
    +

    Return maybe modified slice and post-slice slicing for slicer

    +
    +
    Parameters:
    +
    +
    slicerslice object or int
    +
    dim_lenint

    length of axis along which to slice

    +
    +
    all_fullbool

    Whether dimensions up until now have been full (all elements)

    +
    +
    is_slowestbool

    Whether this dimension is the slowest changing in memory / on disk

    +
    +
    strideint

    size of one step along this axis

    +
    +
    heuristiccallable, optional

    function taking slice object, dim_len, stride length as arguments, +returning one of ‘full’, ‘contiguous’, None. See +threshold_heuristic() for an example.

    +
    +
    +
    +
    Returns:
    +
    +
    to_readslice object or int

    maybe modified slice based on slicer expressing what data should be +read from an underlying file or buffer. to_read must always have +positive step (because we don’t want to go backwards in the buffer +/ file)

    +
    +
    post_sliceslice object

    slice to be applied after array has been read. Applies any +transformations in slicer that have not been applied in to_read. If +axis will be dropped by to_read slicing, so no slicing would make +sense, return string dropped

    +
    +
    +
    +
    +

    Notes

    +

    This is the heart of the algorithm for making segments from slice objects.

    +

    A contiguous slice is a slice with slice.step in (1, -1)

    +

    A full slice is a continuous slice returning all elements.

    +

    The main question we have to ask is whether we should transform to_read, +post_slice to prefer a full read and partial slice. We only do this in +the case of all_full==True. In this case we might benefit from reading a +continuous chunk of data even if the slice is not continuous, or reading +all the data even if the slice is not full. Apply a heuristic heuristic +to decide whether to do this, and adapt to_read and post_slice slice +accordingly.

    +

    Otherwise (apart from constraint to be positive) return to_read unaltered +and post_slice as slice(None)

    +
    + +
    +
    +

    predict_shape

    +
    +
    +nibabel.fileslice.predict_shape(sliceobj, in_shape)
    +

    Predict shape of array from slicing array shape shape with sliceobj

    +
    +
    Parameters:
    +
    +
    sliceobjobject

    something that can be used to slice an array as in arr[sliceobj]

    +
    +
    in_shapesequence

    shape of array that could be sliced by sliceobj

    +
    +
    +
    +
    Returns:
    +
    +
    out_shapetuple

    predicted shape arising from slicing array shape in_shape with +sliceobj

    +
    +
    +
    +
    +
    + +
    +
    +

    read_segments

    +
    +
    +nibabel.fileslice.read_segments(fileobj, segments, n_bytes, lock=None)
    +

    Read n_bytes byte data implied by segments from fileobj

    +
    +
    Parameters:
    +
    +
    fileobjfile-like object

    Implements seek and read

    +
    +
    segmentssequence

    list of 2 sequences where sequences are (offset, length), giving +absolute file offset in bytes and number of bytes to read

    +
    +
    n_bytesint

    total number of bytes that will be read

    +
    +
    lock{None, threading.Lock, lock-like} optional

    If provided, used to ensure that paired calls to seek and read +cannot be interrupted by another thread accessing the same fileobj. +Each thread which accesses the same file via read_segments must +share a lock in order to ensure that the file access is thread-safe. +A lock does not need to be provided for single-threaded access. The +default value (None) results in a lock-like object (a +_NullLock) which does not do anything.

    +
    +
    +
    +
    Returns:
    +
    +
    bufferbuffer object

    object implementing buffer protocol, such as byte string or ndarray or +mmap or ctypes c_char_array

    +
    +
    +
    +
    +
    + +
    +
    +

    slice2len

    +
    +
    +nibabel.fileslice.slice2len(slicer, in_len)
    +

    Output length after slicing original length in_len with slicer +Parameters +———- +slicer : slice object +in_len : int

    +
    +
    Returns:
    +
    +
    out_lenint

    Length after slicing

    +
    +
    +
    +
    +

    Notes

    +

    Returns same as len(np.arange(in_len)[slicer])

    +
    + +
    +
    +

    slice2outax

    +
    +
    +nibabel.fileslice.slice2outax(ndim, sliceobj)
    +

    Matching output axes for input array ndim ndim and slice sliceobj

    +
    +
    Parameters:
    +
    +
    ndimint

    number of axes in input array

    +
    +
    sliceobjobject

    something that can be used to slice an array as in arr[sliceobj]

    +
    +
    +
    +
    Returns:
    +
    +
    out_ax_indstuple

    Say A` is a (pretend) input array of `ndim` dimensions. Say ``B = +A[sliceobj]. out_ax_inds has one value per axis in A giving +corresponding axis in B.

    +
    +
    +
    +
    +
    + +
    +
    +

    slicers2segments

    +
    +
    +nibabel.fileslice.slicers2segments(read_slicers, in_shape, offset, itemsize)
    +

    Get segments from read_slicers given in_shape and memory steps

    +
    +
    Parameters:
    +
    +
    read_slicersobject

    something that can be used to slice an array as in arr[sliceobj] +Slice objects can by be assumed canonical as in canonical_slicers, +and positive as in _positive_slice

    +
    +
    in_shapesequence

    shape of underlying array on disk before reading

    +
    +
    offsetint

    offset of array data in underlying file or memory buffer

    +
    +
    itemsizeint

    element size in array (in bytes)

    +
    +
    +
    +
    Returns:
    +
    +
    segmentslist

    list of 2 element lists where lists are [offset, length], giving +absolute memory offset in bytes and number of bytes to read

    +
    +
    +
    +
    +
    + +
    +
    +

    strided_scalar

    +
    +
    +nibabel.fileslice.strided_scalar(shape, scalar=0.0)
    +

    Return array shape shape where all entries point to value scalar

    +
    +
    Parameters:
    +
    +
    shapesequence

    Shape of output array.

    +
    +
    scalarscalar

    Scalar value with which to fill array.

    +
    +
    +
    +
    Returns:
    +
    +
    strided_arrarray

    Array of shape shape for which all values == scalar, built by +setting all strides of strided_arr to 0, so the scalar is broadcast +out to the full array shape. strided_arr is flagged as not +writeable.

    +

    The array is set read-only to avoid a numpy error when broadcasting - +see https://github.com/numpy/numpy/issues/6491

    +
    +
    +
    +
    +
    + +
    +
    +

    threshold_heuristic

    +
    +
    +nibabel.fileslice.threshold_heuristic(slicer, dim_len, stride, skip_thresh=256)
    +

    Whether to force full axis read or contiguous read of stepped slice

    +

    Allows fileslice() to sometimes read memory that it will throw away +in order to get maximum speed. In other words, trade memory for fewer disk +reads.

    +
    +
    Parameters:
    +
    +
    slicerslice object, or int

    If slice, can be assumed to be full as in fill_slicer

    +
    +
    dim_lenint

    length of axis being sliced

    +
    +
    strideint

    memory distance between elements on this axis

    +
    +
    skip_threshint, optional

    Memory gap threshold in bytes above which to prefer skipping memory +rather than reading it and later discarding.

    +
    +
    +
    +
    Returns:
    +
    +
    action{‘full’, ‘contiguous’, None}

    Gives the suggested optimization for reading the data

    +
      +
    • ‘full’ - read whole axis

    • +
    • ‘contiguous’ - read all elements between start and stop

    • +
    • None - read only memory needed for output

    • +
    +
    +
    +
    +
    +

    Notes

    +

    Let’s say we are in the middle of reading a file at the start of some +memory length \(B\) bytes. We don’t need the memory, and we are considering +whether to read it anyway (then throw it away) (READ) or stop reading, skip +\(B\) bytes and restart reading from there (SKIP).

    +

    After trying some more fancy algorithms, a hard threshold (skip_thresh) +for the maximum skip distance seemed to work well, as measured by times on +nibabel.benchmarks.bench_fileslice

    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.fileutils.html b/reference/nibabel.fileutils.html new file mode 100644 index 0000000000..eb072e06c2 --- /dev/null +++ b/reference/nibabel.fileutils.html @@ -0,0 +1,166 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    fileutils

    +

    Utilities for reading and writing to binary file formats

    + + + + + + +

    read_zt_byte_strings(fobj[, n_strings, bufsize])

    Read zero-terminated byte strings from a file object fobj

    +
    +

    read_zt_byte_strings

    +
    +
    +nibabel.fileutils.read_zt_byte_strings(fobj, n_strings=1, bufsize=1024)
    +

    Read zero-terminated byte strings from a file object fobj

    +

    Returns byte strings with terminal zero stripped.

    +

    Found strings can be of any length.

    +

    The file position of fobj on exit will be at the byte after the terminal +0 of the final read byte string.

    +
    +
    Parameters:
    +
    +
    ffileobj

    File object to use. Should implement read, returning byte objects, +and seek(n, 1) to seek from current file position.

    +
    +
    n_stringsint, optional

    Number of byte strings to return

    +
    +
    bufsize: int, optional

    Define chunk size to load from file while searching for zero terminals. +We load this many bytes at a time from the file, but the returned +strings can be longer than bufsize.

    +
    +
    +
    +
    Returns:
    +
    +
    byte_stringslist

    List of byte strings, where strings do not include the terminal 0

    +
    +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.freesurfer.html b/reference/nibabel.freesurfer.html new file mode 100644 index 0000000000..60eac7e846 --- /dev/null +++ b/reference/nibabel.freesurfer.html @@ -0,0 +1,1018 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    freesurfer

    +

    Reading functions for freesurfer files

    + + + +
    +
    +

    Module: freesurfer.io

    +

    Read / write FreeSurfer geometry, morphometry, label, annotation formats

    + + + + + + + + + + + + + + + + + + + + + + + + +

    read_annot(filepath[, orig_ids])

    Read in a Freesurfer annotation from a .annot file.

    read_geometry(filepath[, read_metadata, ...])

    Read a triangular format Freesurfer surface mesh.

    read_label(filepath[, read_scalars])

    Load in a Freesurfer .label file.

    read_morph_data(filepath)

    Read a Freesurfer morphometry data file.

    write_annot(filepath, labels, ctab, names[, ...])

    Write out a "new-style" Freesurfer annotation file.

    write_geometry(filepath, coords, faces[, ...])

    Write a triangular format Freesurfer surface mesh.

    write_morph_data(file_like, values[, fnum])

    Write Freesurfer morphometry data values to file-like file_like

    +
    +
    +

    Module: freesurfer.mghformat

    +

    Header and image reading / writing functions for MGH image format

    +

    Author: Krish Subramaniam

    + + + + + + + + + + + + +

    MGHError

    Exception for MGH format related problems.

    MGHHeader([binaryblock, check])

    Class for MGH format header

    MGHImage(dataobj, affine[, header, extra, ...])

    Class for MGH format image

    +
    +

    read_annot

    +
    +
    +nibabel.freesurfer.io.read_annot(filepath, orig_ids=False)
    +

    Read in a Freesurfer annotation from a .annot file.

    +

    An .annot file contains a sequence of vertices with a label (also known +as an “annotation value”) associated with each vertex, and then a sequence +of colors corresponding to each label.

    +

    Annotation file format versions 1 and 2 are supported, corresponding to +the “old-style” and “new-style” color table layout.

    +

    Note that the output color table ctab is in RGBT form, where T +(transparency) is 255 - alpha.

    +
    +
    See:
    +
    +
    +
    +
    Parameters:
    +
    +
    filepathstr

    Path to annotation file.

    +
    +
    orig_idsbool

    Whether to return the vertex ids as stored in the annotation +file or the positional colortable ids. With orig_ids=False +vertices with no id have an id set to -1.

    +
    +
    +
    +
    Returns:
    +
    +
    labelsndarray, shape (n_vertices,)

    Annotation id at each vertex. If a vertex does not belong +to any label and orig_ids=False, its id will be set to -1.

    +
    +
    ctabndarray, shape (n_labels, 5)

    RGBT + label id colortable array.

    +
    +
    nameslist of bytes

    The names of the labels. The length of the list is n_labels.

    +
    +
    +
    +
    +
    + +
    +
    +

    read_geometry

    +
    +
    +nibabel.freesurfer.io.read_geometry(filepath, read_metadata=False, read_stamp=False)
    +

    Read a triangular format Freesurfer surface mesh.

    +
    +
    Parameters:
    +
    +
    filepathstr

    Path to surface file.

    +
    +
    read_metadatabool, optional

    If True, read and return metadata as key-value pairs.

    +

    Valid keys:

    +
      +
    • ‘head’ : array of int

    • +
    • ‘valid’ : str

    • +
    • ‘filename’ : str

    • +
    • ‘volume’ : array of int, shape (3,)

    • +
    • ‘voxelsize’ : array of float, shape (3,)

    • +
    • ‘xras’ : array of float, shape (3,)

    • +
    • ‘yras’ : array of float, shape (3,)

    • +
    • ‘zras’ : array of float, shape (3,)

    • +
    • ‘cras’ : array of float, shape (3,)

    • +
    +
    +
    read_stampbool, optional

    Return the comment from the file

    +
    +
    +
    +
    Returns:
    +
    +
    coordsnumpy array

    nvtx x 3 array of vertex (x, y, z) coordinates.

    +
    +
    facesnumpy array

    nfaces x 3 array of defining mesh triangles.

    +
    +
    volume_infoOrderedDict

    Returned only if read_metadata is True. Key-value pairs found in the +geometry file.

    +
    +
    create_stampstr

    Returned only if read_stamp is True. The comment added by the +program that saved the file.

    +
    +
    +
    +
    +
    + +
    +
    +

    read_label

    +
    +
    +nibabel.freesurfer.io.read_label(filepath, read_scalars=False)
    +

    Load in a Freesurfer .label file.

    +
    +
    Parameters:
    +
    +
    filepathstr

    Path to label file.

    +
    +
    read_scalarsbool, optional

    If True, read and return scalars associated with each vertex.

    +
    +
    +
    +
    Returns:
    +
    +
    label_arraynumpy array

    Array with indices of vertices included in label.

    +
    +
    scalar_arraynumpy array (floats)

    Only returned if read_scalars is True. Array of scalar data for each +vertex.

    +
    +
    +
    +
    +
    + +
    +
    +

    read_morph_data

    +
    +
    +nibabel.freesurfer.io.read_morph_data(filepath)
    +

    Read a Freesurfer morphometry data file.

    +

    This function reads in what Freesurfer internally calls “curv” file types, +(e.g. ?h. curv, ?h.thickness), but as that has the potential to cause +confusion where “curv” also refers to the surface curvature values, +we refer to these files as “morphometry” files with PySurfer.

    +
    +
    Parameters:
    +
    +
    filepathstr

    Path to morphometry file

    +
    +
    +
    +
    Returns:
    +
    +
    curvnumpy array

    Vector representation of surface morpometry values

    +
    +
    +
    +
    +
    + +
    +
    +

    write_annot

    +
    +
    +nibabel.freesurfer.io.write_annot(filepath, labels, ctab, names, fill_ctab=True)
    +

    Write out a “new-style” Freesurfer annotation file.

    +

    Note that the color table ctab is in RGBT form, where T (transparency) +is 255 - alpha.

    +
    +
    See:
    +
    +
    +
    +
    Parameters:
    +
    +
    filepathstr

    Path to annotation file to be written

    +
    +
    labelsndarray, shape (n_vertices,)

    Annotation id at each vertex.

    +
    +
    ctabndarray, shape (n_labels, 5)

    RGBT + label id colortable array.

    +
    +
    nameslist of str

    The names of the labels. The length of the list is n_labels.

    +
    +
    fill_ctab{True, False} optional

    If True, the annotation values for each vertex are automatically +generated. In this case, the provided ctab may have shape +(n_labels, 4) or (n_labels, 5) - if the latter, the final column is +ignored.

    +
    +
    +
    +
    +
    + +
    +
    +

    write_geometry

    +
    +
    +nibabel.freesurfer.io.write_geometry(filepath, coords, faces, create_stamp=None, volume_info=None)
    +

    Write a triangular format Freesurfer surface mesh.

    +
    +
    Parameters:
    +
    +
    filepathstr

    Path to surface file.

    +
    +
    coordsnumpy array

    nvtx x 3 array of vertex (x, y, z) coordinates.

    +
    +
    facesnumpy array

    nfaces x 3 array of defining mesh triangles.

    +
    +
    create_stampstr, optional

    User/time stamp (default: “created by <user> on <ctime>”)

    +
    +
    volume_infodict-like or None, optional

    Key-value pairs to encode at the end of the file.

    +

    Valid keys:

    +
      +
    • ‘head’ : array of int

    • +
    • ‘valid’ : str

    • +
    • ‘filename’ : str

    • +
    • ‘volume’ : array of int, shape (3,)

    • +
    • ‘voxelsize’ : array of float, shape (3,)

    • +
    • ‘xras’ : array of float, shape (3,)

    • +
    • ‘yras’ : array of float, shape (3,)

    • +
    • ‘zras’ : array of float, shape (3,)

    • +
    • ‘cras’ : array of float, shape (3,)

    • +
    +
    +
    +
    +
    +
    + +
    +
    +

    write_morph_data

    +
    +
    +nibabel.freesurfer.io.write_morph_data(file_like, values, fnum=0)
    +

    Write Freesurfer morphometry data values to file-like file_like

    +

    Equivalent to FreeSurfer’s write_curv.m

    +

    See also: +http://www.grahamwideman.com/gw/brain/fs/surfacefileformats.htm#CurvNew

    +
    +
    Parameters:
    +
    +
    file_likefile-like

    String containing path of file to be written, or file-like object, open +in binary write (‘wb’ mode, implementing the write method)

    +
    +
    valuesarray-like

    Surface morphometry values. Shape must be (N,), (N, 1), (1, N) or (N, +1, 1)

    +
    +
    fnumint, optional

    Number of faces in the associated surface.

    +
    +
    +
    +
    +
    + +
    +
    +

    MGHError

    +
    +
    +class nibabel.freesurfer.mghformat.MGHError
    +

    Bases: Exception

    +

    Exception for MGH format related problems.

    +

    To be raised whenever MGH is not happy, or we are not happy with +MGH.

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    + +
    +
    +

    MGHHeader

    +
    +
    +class nibabel.freesurfer.mghformat.MGHHeader(binaryblock=None, check=True)
    +

    Bases: LabeledWrapStruct, SpatialHeader

    +

    Class for MGH format header

    +

    The header also consists of the footer data which MGH places after the data +chunk.

    +

    Initialize header from binary data block

    +
    +
    Parameters:
    +
    +
    binaryblock{None, string} optional

    binary block to set into header. By default, None, in +which case we insert the default empty header block

    +
    +
    checkbool, optional

    Whether to check content of header in initialization. +Default is True.

    +
    +
    +
    +
    +
    +
    +__init__(binaryblock=None, check=True)
    +

    Initialize header from binary data block

    +
    +
    Parameters:
    +
    +
    binaryblock{None, string} optional

    binary block to set into header. By default, None, in +which case we insert the default empty header block

    +
    +
    checkbool, optional

    Whether to check content of header in initialization. +Default is True.

    +
    +
    +
    +
    +
    + +
    +
    +as_byteswapped(endianness=None)
    +

    Return new object with given endianness

    +

    If big endian, returns a copy of the object. Otherwise raises ValueError.

    +
    +
    Parameters:
    +
    +
    endiannessNone or string, optional

    endian code to which to swap. None means swap from current +endianness, and is the default

    +
    +
    +
    +
    Returns:
    +
    +
    wstrMGHHeader

    MGHHeader object

    +
    +
    +
    +
    +
    + +
    +
    +static chk_version(hdr, fix=False)
    +
    + +
    +
    +copy()
    +

    Return copy of structure

    +
    + +
    +
    +data_from_fileobj(fileobj)
    +

    Read data array from fileobj

    +
    +
    Parameters:
    +
    +
    fileobjfile-like

    Must be open, and implement read and seek methods

    +
    +
    +
    +
    Returns:
    +
    +
    arrndarray

    data array

    +
    +
    +
    +
    +
    + +
    +
    +classmethod default_structarr(endianness=None)
    +

    Return header data for empty header

    +

    Ignores byte order; always big endian

    +
    + +
    +
    +classmethod diagnose_binaryblock(binaryblock, endianness=None)
    +

    Run checks over binary data, return string

    +
    + +
    +
    +classmethod from_fileobj(fileobj, check=True)
    +

    classmethod for loading a MGH fileobject

    +
    + +
    +
    +classmethod from_header(header=None, check=True)
    +

    Class method to create MGH header from another MGH header

    +
    + +
    +
    +get_affine()
    +

    Get the affine transform from the header information.

    +

    MGH format doesn’t store the transform directly. Instead it’s gleaned +from the zooms ( delta ), direction cosines ( Mdc ), RAS centers ( +Pxyz_c ) and the dimensions.

    +
    + +
    +
    +get_best_affine()
    +

    Get the affine transform from the header information.

    +

    MGH format doesn’t store the transform directly. Instead it’s gleaned +from the zooms ( delta ), direction cosines ( Mdc ), RAS centers ( +Pxyz_c ) and the dimensions.

    +
    + +
    +
    +get_data_bytespervox()
    +

    Get the number of bytes per voxel of the data

    +
    + +
    +
    +get_data_dtype()
    +

    Get numpy dtype for MGH data

    +

    For examples see set_data_dtype

    +
    + +
    +
    +get_data_offset()
    +

    Return offset into data file to read data

    +
    + +
    +
    +get_data_shape()
    +

    Get shape of data

    +
    + +
    +
    +get_data_size()
    +

    Get the number of bytes the data chunk occupies.

    +
    + +
    + +

    Return offset where the footer resides. +Occurs immediately after the data chunk.

    +
    + +
    +
    +get_ras2vox()
    +

    return the inverse get_affine()

    +
    + +
    +
    +get_slope_inter()
    +

    MGH format does not do scaling?

    +
    + +
    +
    +get_vox2ras()
    +

    return the get_affine()

    +
    + +
    +
    +get_vox2ras_tkr()
    +

    Get the vox2ras-tkr transform. See “Torig” here: +https://surfer.nmr.mgh.harvard.edu/fswiki/CoordinateSystems

    +
    + +
    +
    +get_zooms()
    +

    Get zooms from header

    +

    Returns the spacing of voxels in the x, y, and z dimensions. +For four-dimensional files, a fourth zoom is included, equal to the +repetition time (TR) in ms (see The MGH/MGZ Volume Format).

    +

    To access only the spatial zooms, use hdr[‘delta’].

    +
    +
    Returns:
    +
    +
    ztuple

    tuple of header zoom values

    +
    +
    +
    +
    +
    + +
    +
    +classmethod guessed_endian(mapping)
    +

    MGHHeader data must be big-endian

    +
    + +
    +
    +set_data_dtype(datatype)
    +

    Set numpy dtype for data from code or dtype or type

    +
    + +
    +
    +set_data_shape(shape)
    +

    Set shape of data

    +
    +
    Parameters:
    +
    +
    shapesequence

    sequence of integers specifying data array shape

    +
    +
    +
    +
    +
    + +
    +
    +set_zooms(zooms)
    +

    Set zooms into header fields

    +

    Sets the spacing of voxels in the x, y, and z dimensions. +For four-dimensional files, a temporal zoom (repetition time, or TR, in +ms) may be provided as a fourth sequence element.

    +
    +
    Parameters:
    +
    +
    zoomssequence

    sequence of floats specifying spatial and (optionally) temporal +zooms

    +
    +
    +
    +
    +
    + +
    +
    +template_dtype = dtype([('version', '>i4'), ('dims', '>i4', (4,)), ('type', '>i4'), ('dof', '>i4'), ('goodRASFlag', '>i2'), ('delta', '>f4', (3,)), ('Mdc', '>f4', (3, 3)), ('Pxyz_c', '>f4', (3,)), ('tr', '>f4'), ('flip_angle', '>f4'), ('te', '>f4'), ('ti', '>f4'), ('fov', '>f4')])
    +
    + +
    +
    +writeftr_to(fileobj)
    +

    Write footer to fileobj

    +

    Footer data is located after the data chunk. So move there and write.

    +
    +
    Parameters:
    +
    +
    fileobjfile-like object

    Should implement write and seek method

    +
    +
    +
    +
    Returns:
    +
    +
    None
    +
    +
    +
    +
    + +
    +
    +writehdr_to(fileobj)
    +

    Write header to fileobj

    +

    Write starts at the beginning.

    +
    +
    Parameters:
    +
    +
    fileobjfile-like object

    Should implement write and seek method

    +
    +
    +
    +
    Returns:
    +
    +
    None
    +
    +
    +
    +
    + +
    + +
    +
    +

    MGHImage

    +
    +
    +class nibabel.freesurfer.mghformat.MGHImage(dataobj, affine, header=None, extra=None, file_map=None)
    +

    Bases: SpatialImage, SerializableImage

    +

    Class for MGH format image

    +

    Initialize image

    +

    The image is a combination of (array-like, affine matrix, header), with +optional metadata in extra, and filename / file-like objects +contained in the file_map mapping.

    +
    +
    Parameters:
    +
    +
    dataobjobject

    Object containing image data. It should be some object that returns an +array from np.asanyarray. It should have a shape attribute +or property

    +
    +
    affineNone or (4,4) array-like

    homogeneous affine giving relationship between voxel coordinates and +world coordinates. Affine can also be None. In this case, +obj.affine also returns None, and the affine as written to disk +will depend on the file format.

    +
    +
    headerNone or mapping or header instance, optional

    metadata for this image format

    +
    +
    extraNone or mapping, optional

    metadata to associate with image that cannot be stored in the +metadata of this image type

    +
    +
    file_mapmapping, optional

    mapping giving file information for this image format

    +
    +
    +
    +
    +
    +
    +__init__(dataobj, affine, header=None, extra=None, file_map=None)
    +

    Initialize image

    +

    The image is a combination of (array-like, affine matrix, header), with +optional metadata in extra, and filename / file-like objects +contained in the file_map mapping.

    +
    +
    Parameters:
    +
    +
    dataobjobject

    Object containing image data. It should be some object that returns an +array from np.asanyarray. It should have a shape attribute +or property

    +
    +
    affineNone or (4,4) array-like

    homogeneous affine giving relationship between voxel coordinates and +world coordinates. Affine can also be None. In this case, +obj.affine also returns None, and the affine as written to disk +will depend on the file format.

    +
    +
    headerNone or mapping or header instance, optional

    metadata for this image format

    +
    +
    extraNone or mapping, optional

    metadata to associate with image that cannot be stored in the +metadata of this image type

    +
    +
    file_mapmapping, optional

    mapping giving file information for this image format

    +
    +
    +
    +
    +
    + +
    +
    +ImageArrayProxy
    +

    alias of ArrayProxy

    +
    + +
    +
    +files_types: tuple[ExtensionSpec, ...] = (('image', '.mgh'),)
    +
    + +
    +
    +classmethod filespec_to_file_map(filespec)
    +

    Make file_map for this class from filename filespec

    +

    Class method

    +
    +
    Parameters:
    +
    +
    filespecstr or os.PathLike

    Filename that might be for this image file type.

    +
    +
    +
    +
    Returns:
    +
    +
    file_mapdict

    file_map dict with (key, value) pairs of (file_type, +FileHolder instance), where file_type is a string giving the +type of the contained file.

    +
    +
    +
    +
    Raises:
    +
    +
    ImageFileError

    if filespec is not recognizable as being a filename for this +image type.

    +
    +
    +
    +
    +
    + +
    +
    +classmethod from_file_map(file_map, *, mmap=True, keep_file_open=None)
    +

    Class method to create image from mapping in file_map

    +
    +
    Parameters:
    +
    +
    file_mapdict

    Mapping with (key, value) pairs of (file_type, FileHolder +instance giving file-likes for each file needed for this image +type.

    +
    +
    mmap{True, False, ‘c’, ‘r’}, optional, keyword only

    mmap controls the use of numpy memory mapping for reading image +array data. If False, do not try numpy memmap for data array. +If one of {‘c’, ‘r’}, try numpy memmap with mode=mmap. A +mmap value of True gives the same behavior as mmap='c'. If +image data file cannot be memory-mapped, ignore mmap value and +read array from file.

    +
    +
    keep_file_open{ None, True, False }, optional, keyword only

    keep_file_open controls whether a new file handle is created +every time the image is accessed, or a single file handle is +created and used for the lifetime of this ArrayProxy. If +True, a single file handle is created and used. If False, +a new file handle is created every time the image is accessed. +If file_map refers to an open file handle, this setting has no +effect. The default value (None) will result in the value of +nibabel.arrayproxy.KEEP_FILE_OPEN_DEFAULT being used.

    +
    +
    +
    +
    Returns:
    +
    +
    imgMGHImage instance
    +
    +
    +
    +
    + +
    +
    +header_class
    +

    alias of MGHHeader

    +
    + +
    +
    +makeable: bool = True
    +
    + +
    +
    +rw: bool = True
    +
    + +
    +
    +to_file_map(file_map=None)
    +

    Write image to file_map or contained self.file_map

    +
    +
    Parameters:
    +
    +
    file_mapNone or mapping, optional

    files mapping. If None (default) use object’s file_map +attribute instead

    +
    +
    +
    +
    +
    + +
    +
    +valid_exts: tuple[str, ...] = ('.mgh', '.mgz')
    +
    + +
    + +
    +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.funcs.html b/reference/nibabel.funcs.html new file mode 100644 index 0000000000..c3f7da2461 --- /dev/null +++ b/reference/nibabel.funcs.html @@ -0,0 +1,301 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    funcs

    +

    Processor functions for images

    + + + + + + + + + + + + + + + +

    as_closest_canonical(img[, enforce_diag])

    Return img with data reordered to be closest to canonical

    concat_images(images[, check_affines, axis])

    Concatenate images in list to single image, along specified dimension

    four_to_three(img)

    Create 3D images from 4D image by slicing over last axis

    squeeze_image(img)

    Return image, remove axes length 1 at end of image shape

    +
    +

    as_closest_canonical

    +
    +
    +nibabel.funcs.as_closest_canonical(img, enforce_diag=False)
    +

    Return img with data reordered to be closest to canonical

    +

    Canonical order is the ordering of the output axes.

    +
    +
    Parameters:
    +
    +
    imgspatialimage
    +
    enforce_diag{False, True}, optional

    If True, before transforming image, check if the resulting image +affine will be close to diagonal, and if not, raise an error

    +
    +
    +
    +
    Returns:
    +
    +
    canonical_imgspatialimage

    Version of img where the underlying array may have been +reordered and / or flipped so that axes 0,1,2 are those axes in +the input data that are, respectively, closest to the output axis +orientation. We modify the affine accordingly. If img is +already has the correct data ordering, we just return img +unmodified.

    +
    +
    +
    +
    +
    + +
    +
    +

    concat_images

    +
    +
    +nibabel.funcs.concat_images(images, check_affines=True, axis=None)
    +

    Concatenate images in list to single image, along specified dimension

    +
    +
    Parameters:
    +
    +
    imagessequence

    sequence of SpatialImage or filenames of the same dimensionalitys

    +
    +
    check_affines{True, False}, optional

    If True, then check that all the affines for images are nearly +the same, raising a ValueError otherwise. Default is True

    +
    +
    axisNone or int, optional

    If None, concatenates on a new dimension. This requires all images to +be the same shape. If not None, concatenates on the specified +dimension. This requires all images to be the same shape, except on +the specified dimension.

    +
    +
    +
    +
    Returns:
    +
    +
    concat_imgSpatialImage

    New image resulting from concatenating images across last +dimension

    +
    +
    +
    +
    +
    + +
    +
    +

    four_to_three

    +
    +
    +nibabel.funcs.four_to_three(img)
    +

    Create 3D images from 4D image by slicing over last axis

    +
    +
    Parameters:
    +
    +
    imgimage

    4D image instance of some class with methods get_data, +header and affine, and a class constructor +allowing klass(data, affine, header)

    +
    +
    +
    +
    Returns:
    +
    +
    imgslist

    list of 3D images

    +
    +
    +
    +
    +
    + +
    +
    +

    squeeze_image

    +
    +
    +nibabel.funcs.squeeze_image(img)
    +

    Return image, remove axes length 1 at end of image shape

    +

    For example, an image may have shape (10,20,30,1,1). In this case +squeeze will result in an image with shape (10,20,30). See doctests +for further description of behavior.

    +
    +
    Parameters:
    +
    +
    imgSpatialImage
    +
    +
    +
    Returns:
    +
    +
    squeezed_imgSpatialImage

    Copy of img, such that data, and data shape have been squeezed, +for dimensions > 3rd, and at the end of the shape list

    +
    +
    +
    +
    +

    Examples

    +
    >>> import nibabel as nf
    +>>> shape = (10,20,30,1,1)
    +>>> data = np.arange(np.prod(shape), dtype='int32').reshape(shape)
    +>>> affine = np.eye(4)
    +>>> img = nf.Nifti1Image(data, affine)
    +>>> img.shape == (10, 20, 30, 1, 1)
    +True
    +>>> img2 = squeeze_image(img)
    +>>> img2.shape == (10, 20, 30)
    +True
    +
    +
    +

    If the data are 3D then last dimensions of 1 are ignored

    +
    >>> shape = (10,1,1)
    +>>> data = np.arange(np.prod(shape), dtype='int32').reshape(shape)
    +>>> img = nf.ni1.Nifti1Image(data, affine)
    +>>> img.shape == (10, 1, 1)
    +True
    +>>> img2 = squeeze_image(img)
    +>>> img2.shape == (10, 1, 1)
    +True
    +
    +
    +

    Only final dimensions of 1 are squeezed

    +
    >>> shape = (1, 1, 5, 1, 2, 1, 1)
    +>>> data = data.reshape(shape)
    +>>> img = nf.ni1.Nifti1Image(data, affine)
    +>>> img.shape == (1, 1, 5, 1, 2, 1, 1)
    +True
    +>>> img2 = squeeze_image(img)
    +>>> img2.shape == (1, 1, 5, 1, 2)
    +True
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.gifti.html b/reference/nibabel.gifti.html new file mode 100644 index 0000000000..77d7edd870 --- /dev/null +++ b/reference/nibabel.gifti.html @@ -0,0 +1,1035 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    gifti

    +

    GIfTI format IO

    + + + + + + +

    gifti

    Classes defining Gifti objects

    + + + +
    +
    +

    Module: gifti.gifti

    +

    Classes defining Gifti objects

    +

    The Gifti specification was (at time of writing) available as a PDF download +from http://www.nitrc.org/projects/gifti/

    + + + + + + + + + + + + + + + + + + + + + + + + +

    GiftiCoordSystem([dataspace, xformspace, xform])

    Gifti coordinate system transform matrix

    GiftiDataArray([data, intent, datatype, ...])

    Container for Gifti numerical data array and associated metadata

    GiftiImage([header, extra, file_map, meta, ...])

    GIFTI image object

    GiftiLabel([key, red, green, blue, alpha])

    Gifti label: association of integer key with optional RGBA values

    GiftiLabelTable()

    Gifti label table: a sequence of key, label pairs

    GiftiMetaData(*args, **kwargs)

    A sequence of GiftiNVPairs containing metadata for a gifti data array

    GiftiNVPairs([name, value])

    Gifti name / value pairs

    +
    +
    +

    Module: gifti.parse_gifti_fast

    + + + + + + + + + + + + +

    GiftiImageParser([encoding, buffer_size, ...])

    GiftiParseError

    Gifti-specific parsing error

    read_data_block(darray, fname, data, mmap)

    Parses data from a <Data> element, or loads from an external file.

    +
    +
    +

    Module: gifti.util

    + + + +
    +
    +

    GiftiCoordSystem

    +
    +
    +class nibabel.gifti.gifti.GiftiCoordSystem(dataspace=0, xformspace=0, xform=None)
    +

    Bases: XmlSerializable

    +

    Gifti coordinate system transform matrix

    +

    Quotes are from the gifti spec dated 2011-01-14.

    +
    +

    “For a DataArray with an Intent NIFTI_INTENT_POINTSET, this element +describes the stereotaxic space of the data before and after the +application of a transformation matrix. The most common stereotaxic +space is the Talairach Space that places the origin at the anterior +commissure and the negative X, Y, and Z axes correspond to left, +posterior, and inferior respectively. At least one +CoordinateSystemTransformMatrix is required in a DataArray with an +intent of NIFTI_INTENT_POINTSET. Multiple +CoordinateSystemTransformMatrix elements may be used to describe the +transformation to multiple spaces.”

    +
    +
    +
    Attributes:
    +
    +
    dataspaceint

    From the spec: Contains the stereotaxic space of a DataArray’s data +prior to application of the transformation matrix. The stereotaxic +space should be one of:

    +
    +
      +
    • NIFTI_XFORM_UNKNOWN

    • +
    • NIFTI_XFORM_SCANNER_ANAT

    • +
    • NIFTI_XFORM_ALIGNED_ANAT

    • +
    • NIFTI_XFORM_TALAIRACH

    • +
    • NIFTI_XFORM_MNI_152

    • +
    +
    +
    +
    xformspaceint

    Spec: “Contains the stereotaxic space of a DataArray’s data after +application of the transformation matrix. See the DataSpace element for +a list of stereotaxic spaces.”

    +
    +
    xformarray-like shape (4, 4)

    Affine transformation matrix

    +
    +
    +
    +
    +
    +
    +__init__(dataspace=0, xformspace=0, xform=None)
    +
    + +
    +
    +print_summary()
    +
    + +
    + +
    +
    +

    GiftiDataArray

    +
    +
    +class nibabel.gifti.gifti.GiftiDataArray(data=None, intent='NIFTI_INTENT_NONE', datatype=None, encoding='GIFTI_ENCODING_B64GZ', endian='little', coordsys=None, ordering='C', meta=None, ext_fname='', ext_offset=0)
    +

    Bases: XmlSerializable

    +

    Container for Gifti numerical data array and associated metadata

    +

    Quotes are from the gifti spec dated 2011-01-14.

    +
    +
    Description of DataArray in spec:

    “This element contains the numeric data and its related metadata. The +CoordinateSystemTransformMatrix child is only used when the DataArray’s +Intent is NIFTI_INTENT_POINTSET. FileName and FileOffset are required +if the data is stored in an external file.”

    +
    +
    +
    +
    Attributes:
    +
    +
    darrayNone or ndarray

    Data array

    +
    +
    intentint

    NIFTI intent code, see nifti1.intent_codes

    +
    +
    datatypeint

    NIFTI data type codes, see nifti1.data_type_codes. From the spec: +“This required attribute describes the numeric type of the data +contained in a Data Array and are limited to the types displayed in the +table:

    +

    NIFTI_TYPE_UINT8 : Unsigned, 8-bit bytes. +NIFTI_TYPE_INT32 : Signed, 32-bit integers. +NIFTI_TYPE_FLOAT32 : 32-bit single precision floating point.”

    +

    At the moment, we do not enforce that the datatype is one of these +three.

    +
    +
    encodingstring

    Encoding of the data, see util.gifti_encoding_codes; default is +GIFTI_ENCODING_B64GZ.

    +
    +
    endianstring

    The Endianness to store the data array. Should correspond to the +machine endianness. Default is system byteorder.

    +
    +
    coordsysGiftiCoordSystem instance

    Input and output coordinate system with transformation matrix between +the two.

    +
    +
    ind_ordint

    The ordering of the array. see util.array_index_order_codes. Default +is RowMajorOrder - C ordering

    +
    +
    metaGiftiMetaData instance

    An instance equivalent to a dictionary for metadata information.

    +
    +
    ext_fnamestr

    Filename in which data is stored, or empty string if no corresponding +filename.

    +
    +
    ext_offsetint

    Position in bytes within ext_fname at which to start reading data.

    +
    +
    +
    +
    +

    Returns a shell object that cannot be saved.

    +
    +
    +__init__(data=None, intent='NIFTI_INTENT_NONE', datatype=None, encoding='GIFTI_ENCODING_B64GZ', endian='little', coordsys=None, ordering='C', meta=None, ext_fname='', ext_offset=0)
    +

    Returns a shell object that cannot be saved.

    +
    + +
    +
    +property metadata
    +

    Returns metadata as dictionary

    +
    + +
    +
    +property num_dim
    +
    + +
    +
    +print_summary()
    +
    + +
    + +
    +
    +

    GiftiImage

    +
    +
    +class nibabel.gifti.gifti.GiftiImage(header=None, extra=None, file_map=None, meta=None, labeltable=None, darrays=None, version='1.0')
    +

    Bases: XmlSerializable, SerializableImage

    +

    GIFTI image object

    +

    The Gifti spec suggests using the following suffixes to your +filename when saving each specific type of data:

    +
    +
    .gii

    Generic GIFTI File

    +
    +
    .coord.gii

    Coordinates

    +
    +
    .func.gii

    Functional

    +
    +
    .label.gii

    Labels

    +
    +
    .rgba.gii

    RGB or RGBA

    +
    +
    .shape.gii

    Shape

    +
    +
    .surf.gii

    Surface

    +
    +
    .tensor.gii

    Tensors

    +
    +
    .time.gii

    Time Series

    +
    +
    .topo.gii

    Topology

    +
    +
    +

    The Gifti file is stored in endian convention of the current machine.

    +
    +
    +__init__(header=None, extra=None, file_map=None, meta=None, labeltable=None, darrays=None, version='1.0')
    +
    + +
    +
    +add_gifti_data_array(dataarr)
    +

    Adds a data array to the GiftiImage

    +
    +
    Parameters:
    +
    +
    dataarrGiftiDataArray instance
    +
    +
    +
    +
    + +
    +
    +agg_data(intent_code=None)
    +

    Aggregate GIFTI data arrays into an ndarray or tuple of ndarray

    +

    In the general case, the numpy data array is extracted from each GiftiDataArray +object and returned in a tuple, in the order they are found in the GIFTI image.

    +

    If all GiftiDataArray s have intent of 2001 (NIFTI_INTENT_TIME_SERIES), +then the data arrays are concatenated as columns, producing a vertex-by-time array. +If an intent_code is passed, data arrays are filtered by the selected intents, +before being aggregated. +This may be useful for images containing several intents, or ensuring an expected +data type in an image of uncertain provenance. +If intent_code is a tuple, then a tuple will be returned with the result of +agg_data for each element, in order. +This may be useful for ensuring that expected data arrives in a consistent order.

    +
    +
    Parameters:
    +
    +
    intent_codeNone, string, integer or tuple of strings or integers, optional

    code(s) specifying nifti intent

    +
    +
    +
    +
    Returns:
    +
    +
    tuple of ndarrays or ndarray

    If the input is a tuple, the returned tuple will match the order.

    +
    +
    +
    +
    +

    Examples

    +

    Consider a surface GIFTI file:

    +
    >>> import nibabel as nib
    +>>> from nibabel.testing import get_test_data
    +>>> surf_img = nib.load(get_test_data('gifti', 'ascii.gii'))
    +
    +
    +

    The coordinate data, which is indicated by the NIFTI_INTENT_POINTSET +intent code, may be retrieved using any of the following equivalent +calls:

    +
    >>> coords = surf_img.agg_data('NIFTI_INTENT_POINTSET')
    +>>> coords_2 = surf_img.agg_data('pointset')
    +>>> coords_3 = surf_img.agg_data(1008)  # Numeric code for pointset
    +>>> print(np.array2string(coords, precision=3))
    +[[-16.072 -66.188  21.267]
    + [-16.706 -66.054  21.233]
    + [-17.614 -65.402  21.071]]
    +>>> np.array_equal(coords, coords_2)
    +True
    +>>> np.array_equal(coords, coords_3)
    +True
    +
    +
    +

    Similarly, the triangle mesh can be retrieved using various intent +specifiers:

    +
    >>> triangles = surf_img.agg_data('NIFTI_INTENT_TRIANGLE')
    +>>> triangles_2 = surf_img.agg_data('triangle')
    +>>> triangles_3 = surf_img.agg_data(1009)  # Numeric code for pointset
    +>>> print(np.array2string(triangles))
    +[[0 1 2]]
    +>>> np.array_equal(triangles, triangles_2)
    +True
    +>>> np.array_equal(triangles, triangles_3)
    +True
    +
    +
    +

    All arrays can be retrieved as a tuple by omitting the intent +code:

    +
    >>> coords_4, triangles_4 = surf_img.agg_data()
    +>>> np.array_equal(coords, coords_4)
    +True
    +>>> np.array_equal(triangles, triangles_4)
    +True
    +
    +
    +

    Finally, a tuple of intent codes may be passed in order to select +the arrays in a specific order:

    +
    >>> triangles_5, coords_5 = surf_img.agg_data(('triangle', 'pointset'))
    +>>> np.array_equal(triangles, triangles_5)
    +True
    +>>> np.array_equal(coords, coords_5)
    +True
    +
    +
    +

    The following image is a GIFTI file with ten (10) data arrays of the same +size, and with intent code 2001 (NIFTI_INTENT_TIME_SERIES):

    +
    >>> func_img = nib.load(get_test_data('gifti', 'task.func.gii'))
    +
    +
    +

    When aggregating time series data, these arrays are concatenated into +a single, vertex-by-timestep array:

    +
    >>> series = func_img.agg_data()
    +>>> series.shape
    +(642, 10)
    +
    +
    +

    In the case of a GIFTI file with unknown data arrays, it may be preferable +to specify the intent code, so that a time series array is always returned:

    +
    >>> series_2 = func_img.agg_data('NIFTI_INTENT_TIME_SERIES')
    +>>> series_3 = func_img.agg_data('time series')
    +>>> series_4 = func_img.agg_data(2001)
    +>>> np.array_equal(series, series_2)
    +True
    +>>> np.array_equal(series, series_3)
    +True
    +>>> np.array_equal(series, series_4)
    +True
    +
    +
    +

    Requesting a data array from a GIFTI file with no matching intent codes +will result in an empty tuple:

    +
    >>> surf_img.agg_data('time series')
    +()
    +>>> func_img.agg_data('triangle')
    +()
    +
    +
    +
    + +
    +
    +files_types: tuple[ExtensionSpec, ...] = (('image', '.gii'),)
    +
    + +
    +
    +classmethod from_file_map(file_map, buffer_size=35000000, mmap=True)
    +

    Load a Gifti image from a file_map

    +
    +
    Parameters:
    +
    +
    file_mapdict

    Dictionary with single key image with associated value which is +a FileHolder instance pointing to the image file.

    +
    +
    buffer_size: None or int, optional

    size of read buffer. None uses default buffer_size +from xml.parsers.expat.

    +
    +
    mmap{True, False, ‘c’, ‘r’, ‘r+’}

    Controls the use of numpy memory mapping for reading data. Only +has an effect when loading GIFTI images with data stored in +external files (DataArray elements with an Encoding equal +to ExternalFileBinary). If False, do not try numpy +memmap for data array. If one of {'c', 'r', 'r+'}, try +numpy memmap with mode=mmap. A mmap value of True +gives the same behavior as mmap='c'. If the file cannot be +memory-mapped, ignore mmap value and read array from file.

    +
    +
    +
    +
    Returns:
    +
    +
    imgGiftiImage
    +
    +
    +
    +
    + +
    +
    +classmethod from_filename(filename, buffer_size=35000000, mmap=True)
    +
    + +
    +
    +get_arrays_from_intent(intent)
    +

    Return list of GiftiDataArray elements matching given intent

    +
    + +
    +
    +property labeltable
    +
    + +
    +
    +property meta
    +
    + +
    +
    +property numDA
    +
    + +
    +
    +parser
    +

    alias of GiftiImageParser

    +
    + +
    +
    +print_summary()
    +
    + +
    +
    +remove_gifti_data_array(ith)
    +

    Removes the ith data array element from the GiftiImage

    +
    + +
    +
    +remove_gifti_data_array_by_intent(intent)
    +

    Removes all the data arrays with the given intent type

    +
    + +
    +
    +to_bytes(enc='utf-8', *, mode='strict')
    +

    Return a bytes object with the contents of the file that would +be written if the image were saved.

    +
    +
    Parameters:
    +
    +
    **kwargskeyword arguments

    Keyword arguments that may be passed to img.to_file_map()

    +
    +
    +
    +
    Returns:
    +
    +
    bytes

    Serialized image

    +
    +
    +
    +
    +
    + +
    +
    +to_file_map(file_map=None, enc='utf-8', *, mode='strict')
    +

    Save the current image to the specified file_map

    +
    +
    Parameters:
    +
    +
    file_mapdict

    Dictionary with single key image with associated value which is +a FileHolder instance pointing to the image file.

    +
    +
    +
    +
    Returns:
    +
    +
    None
    +
    +
    +
    +
    + +
    +
    +to_xml(enc='utf-8', *, mode='strict', **kwargs) bytes
    +

    Return XML corresponding to image content

    +
    + +
    +
    +valid_exts: tuple[str, ...] = ('.gii',)
    +
    + +
    + +
    +
    +

    GiftiLabel

    +
    +
    +class nibabel.gifti.gifti.GiftiLabel(key=0, red=None, green=None, blue=None, alpha=None)
    +

    Bases: XmlSerializable

    +

    Gifti label: association of integer key with optional RGBA values

    +

    Quotes are from the gifti spec dated 2011-01-14.

    +
    +
    Attributes:
    +
    +
    keyint

    (From the spec): “This required attribute contains a non-negative +integer value. If a DataArray’s Intent is NIFTI_INTENT_LABEL and a +value in the DataArray is ‘X’, its corresponding label is the label +with the Key attribute containing the value ‘X’. In early versions of +the GIFTI file format, the attribute Index was used instead of Key. If +an Index attribute is encountered, it should be processed like the Key +attribute.”

    +
    +
    redNone or float

    Optional value for red.

    +
    +
    greenNone or float

    Optional value for green.

    +
    +
    blueNone or float

    Optional value for blue.

    +
    +
    alphaNone or float

    Optional value for alpha.

    +
    +
    +
    +
    +

    Notes

    +

    freesurfer examples seem not to conform to datatype “NIFTI_TYPE_RGBA32” +because they are floats, not 4 8-bit integers.

    +
    +
    +__init__(key=0, red=None, green=None, blue=None, alpha=None)
    +
    + +
    +
    +property rgba
    +

    Returns RGBA as tuple

    +
    + +
    + +
    +
    +

    GiftiLabelTable

    +
    +
    +class nibabel.gifti.gifti.GiftiLabelTable
    +

    Bases: XmlSerializable

    +

    Gifti label table: a sequence of key, label pairs

    +
    +
    From the gifti spec dated 2011-01-14:

    The label table is used by DataArrays whose values are an key into the +LabelTable’s labels. A file should contain at most one LabelTable and +it must be located in the file prior to any DataArray elements.

    +
    +
    +
    +
    +__init__()
    +
    + +
    +
    +get_labels_as_dict()
    +
    + +
    +
    +print_summary()
    +
    + +
    + +
    +
    +

    GiftiMetaData

    +
    +
    +class nibabel.gifti.gifti.GiftiMetaData(*args, **kwargs)
    +

    Bases: CaretMetaData

    +

    A sequence of GiftiNVPairs containing metadata for a gifti data array

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    +
    +property data
    +

    The data attribute is deprecated. Use GiftiMetaData object directly as a dict.

    +
      +
    • deprecated from version: 4.0

    • +
    • Will raise <class ‘nibabel.deprecator.ExpiredDeprecationError’> as of version: 6.0

    • +
    +
    + +
    +
    +classmethod from_dict(data_dict)
    +

    from_dict class method deprecated. Use GiftiMetaData directly.

    +
      +
    • deprecated from version: 4.0

    • +
    • Will raise <class ‘nibabel.deprecator.ExpiredDeprecationError’> as of version: 6.0

    • +
    +
    + +
    +
    +property metadata
    +

    Returns metadata as dictionary

    +

    metadata property deprecated. Use GiftiMetaData object as dict or pass to dict() for a standard dictionary.

    +
      +
    • deprecated from version: 4.0

    • +
    • Will raise <class ‘nibabel.deprecator.ExpiredDeprecationError’> as of version: 6.0

    • +
    +
    + +
    +
    +print_summary()
    +
    + +
    + +
    +
    +

    GiftiNVPairs

    +
    +
    +class nibabel.gifti.gifti.GiftiNVPairs(name='', value='')
    +

    Bases: object

    +

    Gifti name / value pairs

    +
    +
    Attributes:
    +
    +
    namestr
    +
    valuestr
    +
    +
    +
    +

    GiftiNVPairs objects are deprecated. Use the GiftiMetaData object as a dict, instead.

    +
      +
    • deprecated from version: 4.0

    • +
    • Will raise <class ‘nibabel.deprecator.ExpiredDeprecationError’> as of version: 6.0

    • +
    +
    +
    +__init__(name='', value='')
    +

    GiftiNVPairs objects are deprecated. Use the GiftiMetaData object as a dict, instead.

    +
      +
    • deprecated from version: 4.0

    • +
    • Will raise <class ‘nibabel.deprecator.ExpiredDeprecationError’> as of version: 6.0

    • +
    +
    + +
    +
    +property name
    +
    + +
    +
    +property value
    +
    + +
    + +
    +
    +

    GiftiImageParser

    +
    +
    +class nibabel.gifti.parse_gifti_fast.GiftiImageParser(encoding=None, buffer_size=35000000, verbose=0, mmap=True)
    +

    Bases: XmlParser

    +
    +
    Parameters:
    +
    +
    encodingstr

    string containing xml document

    +
    +
    buffer_size: None or int, optional

    size of read buffer. None uses default buffer_size +from xml.parsers.expat.

    +
    +
    verboseint, optional

    amount of output during parsing (0=silent, by default).

    +
    +
    +
    +
    +
    +
    +__init__(encoding=None, buffer_size=35000000, verbose=0, mmap=True)
    +
    +
    Parameters:
    +
    +
    encodingstr

    string containing xml document

    +
    +
    buffer_size: None or int, optional

    size of read buffer. None uses default buffer_size +from xml.parsers.expat.

    +
    +
    verboseint, optional

    amount of output during parsing (0=silent, by default).

    +
    +
    +
    +
    +
    + +
    +
    +CharacterDataHandler(data)
    +

    Collect character data chunks pending collation

    +

    The parser breaks the data up into chunks of size depending on the +buffer_size of the parser. A large bit of character data, with +standard parser buffer_size (such as 8K) can easily span many calls to +this function. We thus collect the chunks and process them when we +hit start or end tags.

    +
    + +
    +
    +EndElementHandler(name)
    +
    + +
    +
    +StartElementHandler(name, attrs)
    +
    + +
    +
    +flush_chardata()
    +

    Collate and process collected character data

    +
    + +
    +
    +property pending_data
    +

    True if there is character data pending for processing

    +
    + +
    + +
    +
    +

    GiftiParseError

    +
    +
    +class nibabel.gifti.parse_gifti_fast.GiftiParseError
    +

    Bases: ExpatError

    +

    Gifti-specific parsing error

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    + +
    +
    +

    read_data_block

    +
    +
    +nibabel.gifti.parse_gifti_fast.read_data_block(darray, fname, data, mmap)
    +

    Parses data from a <Data> element, or loads from an external file.

    +
    +
    Parameters:
    +
    +
    darrayGiftiDataArray

    GiftiDataArray object representing the parent <DataArray> of this +<Data> element

    +
    +
    fnamestr or None

    Name of GIFTI file being loaded, or None if in-memory

    +
    +
    datastr or None

    Data to parse, or None if data is in an external file

    +
    +
    mmap{True, False, ‘c’, ‘r’, ‘r+’}

    Controls the use of numpy memory mapping for reading data. Only has +an effect when loading GIFTI images with data stored in external files +(DataArray elements with an Encoding equal to +ExternalFileBinary). If False, do not try numpy memmap +for data array. If one of {'c', 'r', 'r+'}, try numpy memmap +with mode=mmap. A mmap value of True gives the same +behavior as mmap='c'. If the file cannot be memory-mapped, ignore +mmap value and read array from file.

    +
    +
    +
    +
    Returns:
    +
    +
    numpy.ndarray or numpy.memmap containing the parsed data
    +
    +
    +
    +
    + +
    +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.html b/reference/nibabel.html new file mode 100644 index 0000000000..a6732b3b27 --- /dev/null +++ b/reference/nibabel.html @@ -0,0 +1,306 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    nibabel

    +

    Read and write access to common neuroimaging file formats, including: +ANALYZE (plain, SPM99, SPM2 and later), GIFTI, NIfTI1, NIfTI2, CIFTI-2, +MINC1, MINC2, AFNI BRIK/HEAD, ECAT and Philips PAR/REC. +In addition, NiBabel also supports FreeSurfer’s MGH, geometry, annotation and +morphometry files, and provides some limited support for DICOM.

    +

    NiBabel’s API gives full or selective access to header information (metadata), +and image data is made available via NumPy arrays. For more information, see +NiBabel’s documentation site and API reference.

    +
    +

    Installation

    +

    To install NiBabel’s current release with pip, run:

    +
    pip install nibabel
    +
    +
    +

    To install the latest development version, run:

    +
    pip install git+https://github.com/nipy/nibabel
    +
    +
    +

    When working on NiBabel itself, it may be useful to install in “editable” mode:

    +
    git clone https://github.com/nipy/nibabel.git
    +pip install -e ./nibabel
    +
    +
    +

    For more information on previous releases, see the release archive or +development changelog.

    +
    +
    +

    Testing

    +

    During development, we recommend using tox to run nibabel tests:

    +
    git clone https://github.com/nipy/nibabel.git
    +cd nibabel
    +tox
    +
    +
    +

    To test an installed version of nibabel, install the test dependencies +and run pytest:

    +
    pip install nibabel[test]
    +pytest --pyargs nibabel
    +
    +
    +

    For more information, consult the developer guidelines.

    +
    +
    +

    Mailing List

    +

    Please send any questions or suggestions to the neuroimaging mailing list.

    +
    +
    +

    License

    +

    NiBabel is licensed under the terms of the MIT license. +Some code included with NiBabel is licensed under the BSD license. +For more information, please see the COPYING file.

    +
    +
    +

    Citation

    +

    NiBabel releases have a Zenodo Digital Object Identifier (DOI) badge at +the top of the release notes. Click on the badge for more information.

    +
    +
    +

    Quickstart

    +
    import nibabel as nib
    +
    +img1 = nib.load('my_file.nii')
    +img2 = nib.load('other_file.nii.gz')
    +img3 = nib.load('spm_file.img')
    +
    +data = img1.get_fdata()
    +affine = img1.affine
    +
    +print(img1)
    +
    +nib.save(img1, 'my_file_copy.nii.gz')
    +
    +new_image = nib.Nifti1Image(data, affine)
    +nib.save(new_image, 'new_image.nii.gz')
    +
    +
    +

    For more detailed information see the NiBabel Manual.

    +
    + + + + + + + + + + + + +

    bench([label, verbose, extra_argv])

    Run benchmarks for nibabel using pytest

    get_info()

    test([label, verbose, extra_argv, doctests, ...])

    Run tests for nibabel using pytest

    +
    +

    bench

    +
    +
    +nibabel.bench(label=None, verbose=1, extra_argv=None)
    +

    Run benchmarks for nibabel using pytest

    +

    The protocol mimics the numpy.testing.NoseTester.bench(). +Not all features are currently implemented.

    +
    +
    Parameters:
    +
    +
    labelNone

    Unused.

    +
    +
    verbose: int, optional

    Verbosity value for test outputs. Positive values increase verbosity, and +negative values decrease it. Default is 1.

    +
    +
    extra_argvlist, optional

    List with any extra arguments to pass to pytest.

    +
    +
    +
    +
    Returns:
    +
    +
    codeExitCode

    Returns the result of running the tests as a pytest.ExitCode enum

    +
    +
    +
    +
    +
    + +
    +
    +

    get_info

    +
    +
    +nibabel.get_info()
    +
    + +
    +
    +

    test

    +
    +
    +nibabel.test(label=None, verbose=1, extra_argv=None, doctests=False, coverage=False, raise_warnings=None, timer=False)
    +

    Run tests for nibabel using pytest

    +

    The protocol mimics the numpy.testing.NoseTester.test(). +Not all features are currently implemented.

    +
    +
    Parameters:
    +
    +
    labelNone

    Unused.

    +
    +
    verbose: int, optional

    Verbosity value for test outputs. Positive values increase verbosity, and +negative values decrease it. Default is 1.

    +
    +
    extra_argvlist, optional

    List with any extra arguments to pass to pytest.

    +
    +
    doctests: bool, optional

    If True, run doctests in module. Default is False.

    +
    +
    coverage: bool, optional

    If True, report coverage of NumPy code. Default is False. +(This requires the +coverage module).

    +
    +
    raise_warningsNone

    Unused.

    +
    +
    timerFalse

    Unused.

    +
    +
    +
    +
    Returns:
    +
    +
    codeExitCode

    Returns the result of running the tests as a pytest.ExitCode enum

    +
    +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.imageclasses.html b/reference/nibabel.imageclasses.html new file mode 100644 index 0000000000..5d99914226 --- /dev/null +++ b/reference/nibabel.imageclasses.html @@ -0,0 +1,155 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    imageclasses

    +

    Define supported image classes and names

    + + + + + + +

    spatial_axes_first(img)

    True if spatial image axes for img always precede other axes

    +
    +

    spatial_axes_first

    +
    +
    +nibabel.imageclasses.spatial_axes_first(img: DataobjImage) bool
    +

    True if spatial image axes for img always precede other axes

    +
    +
    Parameters:
    +
    +
    imgobject

    Image object implementing at least shape attribute.

    +
    +
    +
    +
    Returns:
    +
    +
    spatial_axes_firstbool

    True if image only has spatial axes (number of axes < 4) or image type +known to have spatial axes preceding other axes.

    +
    +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.imageglobals.html b/reference/nibabel.imageglobals.html new file mode 100644 index 0000000000..a17bb26d84 --- /dev/null +++ b/reference/nibabel.imageglobals.html @@ -0,0 +1,184 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    imageglobals

    +

    Defaults for images and headers

    +

    error_level is the problem level (see BatteryRunners) at which an error will be +raised, by the batteryrunners log_raise method. Thus a level of 0 will +result in an error for any problem at all, and a level of 50 will mean no errors +will be raised (unless someone’s put some strange problem_level > 50 code in).

    +

    logger is the default logger (python log instance)

    +

    To set the log level (log message appears for problem of level >= log level), +use e.g. logger.level = 40.

    +

    As for most loggers, if logger.level == 0 then a default log level is used - +use logger.getEffectiveLevel() to see what that default is.

    +

    Use logger.level = 1 to see all messages.

    + + + + + + + + + +

    ErrorLevel(level)

    Context manager to set log error level

    LoggingOutputSuppressor()

    Context manager to prevent global logger from printing

    +
    +

    ErrorLevel

    +
    +
    +class nibabel.imageglobals.ErrorLevel(level)
    +

    Bases: object

    +

    Context manager to set log error level

    +
    +
    +__init__(level)
    +
    + +
    + +
    +
    +

    LoggingOutputSuppressor

    +
    +
    +class nibabel.imageglobals.LoggingOutputSuppressor
    +

    Bases: object

    +

    Context manager to prevent global logger from printing

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.imagestats.html b/reference/nibabel.imagestats.html new file mode 100644 index 0000000000..4e131cc780 --- /dev/null +++ b/reference/nibabel.imagestats.html @@ -0,0 +1,195 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    imagestats

    +

    Functions for computing image statistics

    + + + + + + + + + +

    count_nonzero_voxels(img)

    Count number of non-zero voxels

    mask_volume(img)

    Compute volume of mask image.

    +
    +

    count_nonzero_voxels

    +
    +
    +nibabel.imagestats.count_nonzero_voxels(img)
    +

    Count number of non-zero voxels

    +
    +
    Parameters:
    +
    +
    imgSpatialImage

    All voxels of the mask should be of value 1, background should have value 0.

    +
    +
    +
    +
    Returns:
    +
    +
    countint

    Number of non-zero voxels

    +
    +
    +
    +
    +
    + +
    +
    +

    mask_volume

    +
    +
    +nibabel.imagestats.mask_volume(img)
    +

    Compute volume of mask image.

    +

    Equivalent to “fslstats /path/file.nii -V”

    +
    +
    Parameters:
    +
    +
    imgSpatialImage

    All voxels of the mask should be of value 1, background should have value 0.

    +
    +
    +
    +
    Returns:
    +
    +
    volumefloat

    Volume of mask expressed in mm3.

    +
    +
    +
    +
    +

    Examples

    +
    >>> import numpy as np
    +>>> import nibabel as nb
    +>>> mask_data = np.zeros((20, 20, 20), dtype='u1')
    +>>> mask_data[5:15, 5:15, 5:15] = 1
    +>>> nb.imagestats.mask_volume(nb.Nifti1Image(mask_data, np.eye(4)))
    +1000.0
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.loadsave.html b/reference/nibabel.loadsave.html new file mode 100644 index 0000000000..8d736e5e7a --- /dev/null +++ b/reference/nibabel.loadsave.html @@ -0,0 +1,231 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    loadsave

    +

    Utilities to load and save image objects

    + + + + + + + + + + + + + + + +

    guessed_image_type(filename)

    Guess image type from file filename

    load(filename, **kwargs)

    Load file given filename, guessing at file type

    read_img_data(img[, prefer])

    Read data from image associated with files

    save(img, filename, **kwargs)

    Save an image to file adapting format to filename

    +
    +

    guessed_image_type

    +
    +
    +nibabel.loadsave.guessed_image_type(filename)
    +

    Guess image type from file filename

    +

    guessed_image_type deprecated.

    +
      +
    • deprecated from version: 3.2

    • +
    • Raises <class ‘nibabel.deprecator.ExpiredDeprecationError’> as of version: 5.0

    • +
    +
    + +
    +
    +

    load

    +
    +
    +nibabel.loadsave.load(filename: FileSpec, **kwargs) FileBasedImage
    +

    Load file given filename, guessing at file type

    +
    +
    Parameters:
    +
    +
    filenamestr or os.PathLike

    specification of file to load

    +
    +
    **kwargskeyword arguments

    Keyword arguments to format-specific load

    +
    +
    +
    +
    Returns:
    +
    +
    imgSpatialImage

    Image of guessed type

    +
    +
    +
    +
    +
    + +
    +
    +

    read_img_data

    +
    +
    +nibabel.loadsave.read_img_data(img, prefer='scaled')
    +

    Read data from image associated with files

    +

    read_img_data deprecated. Please use img.dataobj.get_unscaled() instead.

    +
      +
    • deprecated from version: 3.2

    • +
    • Raises <class ‘nibabel.deprecator.ExpiredDeprecationError’> as of version: 5.0

    • +
    +
    + +
    +
    +

    save

    +
    +
    +nibabel.loadsave.save(img: FileBasedImage, filename: FileSpec, **kwargs) None
    +

    Save an image to file adapting format to filename

    +
    +
    Parameters:
    +
    +
    imgSpatialImage

    image to save

    +
    +
    filenamestr or os.PathLike

    filename (often implying filenames) to which to save img.

    +
    +
    **kwargskeyword arguments

    Keyword arguments to format-specific save

    +
    +
    +
    +
    Returns:
    +
    +
    None
    +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.minc1.html b/reference/nibabel.minc1.html new file mode 100644 index 0000000000..24651ab0d9 --- /dev/null +++ b/reference/nibabel.minc1.html @@ -0,0 +1,499 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    minc1

    +

    Read MINC1 format images

    + + + + + + + + + + + + + + + + + + + + + +

    Minc1File(mincfile)

    Class to wrap MINC1 format opened netcdf object

    Minc1Header(data_dtype, shape, zooms)

    Minc1Image(dataobj, affine[, header, extra, ...])

    Class for MINC1 format images

    MincError

    Error when reading MINC files

    MincHeader(data_dtype, shape, zooms)

    Class to contain header for MINC formats

    MincImageArrayProxy(minc_file)

    MINC implementation of array proxy protocol

    +
    +

    Minc1File

    +
    +
    +class nibabel.minc1.Minc1File(mincfile)
    +

    Bases: object

    +

    Class to wrap MINC1 format opened netcdf object

    +

    Although it has some of the same methods as a Header, we use +this only when reading a MINC file, to pull out useful header +information, and for the method of reading the data out

    +
    +
    +__init__(mincfile)
    +
    + +
    +
    +get_affine()
    +
    + +
    +
    +get_data_dtype()
    +
    + +
    +
    +get_data_shape()
    +
    + +
    +
    +get_scaled_data(sliceobj=())
    +

    Return scaled data for slice definition sliceobj

    +
    +
    Parameters:
    +
    +
    sliceobjtuple, optional

    slice definition. If not specified, return whole array

    +
    +
    +
    +
    Returns:
    +
    +
    scaled_arrarray

    array from minc file with scaling applied

    +
    +
    +
    +
    +
    + +
    +
    +get_zooms()
    +

    Get real-world sizes of voxels

    +
    + +
    + +
    +
    +

    Minc1Header

    +
    +
    +class nibabel.minc1.Minc1Header(data_dtype: npt.DTypeLike = <class 'numpy.float32'>, shape: Sequence[int] = (0, ), zooms: Sequence[float] | None = None)
    +

    Bases: MincHeader

    +
    +
    +__init__(data_dtype: npt.DTypeLike = <class 'numpy.float32'>, shape: Sequence[int] = (0, ), zooms: Sequence[float] | None = None)
    +
    + +
    +
    +classmethod may_contain_header(binaryblock)
    +
    + +
    + +
    +
    +

    Minc1Image

    +
    +
    +class nibabel.minc1.Minc1Image(dataobj: ArrayLike, affine: np.ndarray | None, header: FileBasedHeader | ty.Mapping | None = None, extra: ty.Mapping | None = None, file_map: FileMap | None = None)
    +

    Bases: SpatialImage

    +

    Class for MINC1 format images

    +

    The MINC1 image class uses the default header type, rather than a specific +MINC header type - and reads the relevant information from the MINC file on +load.

    +

    Initialize image

    +

    The image is a combination of (array-like, affine matrix, header), with +optional metadata in extra, and filename / file-like objects +contained in the file_map mapping.

    +
    +
    Parameters:
    +
    +
    dataobjobject

    Object containing image data. It should be some object that returns an +array from np.asanyarray. It should have a shape attribute +or property

    +
    +
    affineNone or (4,4) array-like

    homogeneous affine giving relationship between voxel coordinates and +world coordinates. Affine can also be None. In this case, +obj.affine also returns None, and the affine as written to disk +will depend on the file format.

    +
    +
    headerNone or mapping or header instance, optional

    metadata for this image format

    +
    +
    extraNone or mapping, optional

    metadata to associate with image that cannot be stored in the +metadata of this image type

    +
    +
    file_mapmapping, optional

    mapping giving file information for this image format

    +
    +
    +
    +
    +
    +
    +__init__(dataobj: ArrayLike, affine: np.ndarray | None, header: FileBasedHeader | ty.Mapping | None = None, extra: ty.Mapping | None = None, file_map: FileMap | None = None)
    +

    Initialize image

    +

    The image is a combination of (array-like, affine matrix, header), with +optional metadata in extra, and filename / file-like objects +contained in the file_map mapping.

    +
    +
    Parameters:
    +
    +
    dataobjobject

    Object containing image data. It should be some object that returns an +array from np.asanyarray. It should have a shape attribute +or property

    +
    +
    affineNone or (4,4) array-like

    homogeneous affine giving relationship between voxel coordinates and +world coordinates. Affine can also be None. In this case, +obj.affine also returns None, and the affine as written to disk +will depend on the file format.

    +
    +
    headerNone or mapping or header instance, optional

    metadata for this image format

    +
    +
    extraNone or mapping, optional

    metadata to associate with image that cannot be stored in the +metadata of this image type

    +
    +
    file_mapmapping, optional

    mapping giving file information for this image format

    +
    +
    +
    +
    +
    + +
    +
    +ImageArrayProxy
    +

    alias of MincImageArrayProxy

    +
    + +
    +
    +files_types: tuple[tuple[str, str], ...] = (('image', '.mnc'),)
    +
    + +
    +
    +classmethod from_file_map(file_map, *, mmap=True, keep_file_open=None)
    +

    Class method to create image from mapping in file_map

    +
    +
    Parameters:
    +
    +
    file_mapdict

    Mapping with (key, value) pairs of (file_type, FileHolder +instance giving file-likes for each file needed for this image +type.

    +
    +
    mmap{True, False, ‘c’, ‘r’}, optional, keyword only

    mmap controls the use of numpy memory mapping for reading image +array data. If False, do not try numpy memmap for data array. +If one of {‘c’, ‘r’}, try numpy memmap with mode=mmap. A +mmap value of True gives the same behavior as mmap='c'. If +image data file cannot be memory-mapped, ignore mmap value and +read array from file.

    +
    +
    keep_file_open{ None, True, False }, optional, keyword only

    keep_file_open controls whether a new file handle is created +every time the image is accessed, or a single file handle is +created and used for the lifetime of this ArrayProxy. If +True, a single file handle is created and used. If False, +a new file handle is created every time the image is accessed. +If file_map refers to an open file handle, this setting has no +effect. The default value (None) will result in the value of +nibabel.arrayproxy.KEEP_FILE_OPEN_DEFAULT being used.

    +
    +
    +
    +
    Returns:
    +
    +
    imgDataobjImage instance
    +
    +
    +
    +
    + +
    +
    +header_class
    +

    alias of Minc1Header

    +
    + +
    +
    +makeable: bool = True
    +
    + +
    +
    +rw: bool = False
    +
    + +
    +
    +valid_exts: tuple[str, ...] = ('.mnc',)
    +
    + +
    + +
    +
    +

    MincError

    +
    +
    +class nibabel.minc1.MincError
    +

    Bases: Exception

    +

    Error when reading MINC files

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    + +
    +
    +

    MincHeader

    +
    +
    +class nibabel.minc1.MincHeader(data_dtype: npt.DTypeLike = <class 'numpy.float32'>, shape: Sequence[int] = (0, ), zooms: Sequence[float] | None = None)
    +

    Bases: SpatialHeader

    +

    Class to contain header for MINC formats

    +
    +
    +__init__(data_dtype: npt.DTypeLike = <class 'numpy.float32'>, shape: Sequence[int] = (0, ), zooms: Sequence[float] | None = None)
    +
    + +
    +
    +data_from_fileobj(fileobj)
    +

    See Header class for an implementation we can’t use

    +
    + +
    +
    +data_layout: Literal['F', 'C'] = 'C'
    +
    + +
    +
    +data_to_fileobj(data, fileobj, rescale=True)
    +

    See Header class for an implementation we can’t use

    +
    + +
    + +
    +
    +

    MincImageArrayProxy

    +
    +
    +class nibabel.minc1.MincImageArrayProxy(minc_file)
    +

    Bases: object

    +

    MINC implementation of array proxy protocol

    +

    The array proxy allows us to freeze the passed fileobj and +header such that it returns the expected data array.

    +
    +
    +__init__(minc_file)
    +
    + +
    +
    +property is_proxy
    +
    + +
    +
    +property ndim
    +
    + +
    +
    +property shape
    +
    + +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.minc2.html b/reference/nibabel.minc2.html new file mode 100644 index 0000000000..efffbab001 --- /dev/null +++ b/reference/nibabel.minc2.html @@ -0,0 +1,380 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    minc2

    +

    Preliminary MINC2 support

    +

    Use with care; I haven’t tested this against a wide range of MINC files.

    +

    If you have a file that isn’t read correctly, please send an example.

    +

    Test reading with something like:

    +
    import nibabel as nib
    +img = nib.load('my_funny.mnc')
    +data = img.get_fdata()
    +print(data.mean())
    +print(data.max())
    +print(data.min())
    +
    +
    +

    and compare against command line output of:

    +
    mincstats my_funny.mnc
    +
    +
    + + + + + + + + + + + + + + + +

    Hdf5Bunch(var)

    Make object for accessing attributes of variable

    Minc2File(mincfile)

    Class to wrap MINC2 format file

    Minc2Header(data_dtype, shape, zooms)

    Minc2Image(dataobj, affine[, header, extra, ...])

    Class for MINC2 images

    +
    +

    Hdf5Bunch

    +
    +
    +class nibabel.minc2.Hdf5Bunch(var)
    +

    Bases: object

    +

    Make object for accessing attributes of variable

    +
    +
    +__init__(var)
    +
    + +
    + +
    +
    +

    Minc2File

    +
    +
    +class nibabel.minc2.Minc2File(mincfile)
    +

    Bases: Minc1File

    +

    Class to wrap MINC2 format file

    +

    Although it has some of the same methods as a Header, we use +this only when reading a MINC2 file, to pull out useful header +information, and for the method of reading the data out

    +
    +
    +__init__(mincfile)
    +
    + +
    +
    +get_data_dtype()
    +
    + +
    +
    +get_data_shape()
    +
    + +
    +
    +get_scaled_data(sliceobj=())
    +

    Return scaled data for slice definition sliceobj

    +
    +
    Parameters:
    +
    +
    sliceobjtuple, optional

    slice definition. If not specified, return whole array

    +
    +
    +
    +
    Returns:
    +
    +
    scaled_arrarray

    array from minc file with scaling applied

    +
    +
    +
    +
    +
    + +
    + +
    +
    +

    Minc2Header

    +
    +
    +class nibabel.minc2.Minc2Header(data_dtype: npt.DTypeLike = <class 'numpy.float32'>, shape: Sequence[int] = (0, ), zooms: Sequence[float] | None = None)
    +

    Bases: MincHeader

    +
    +
    +__init__(data_dtype: npt.DTypeLike = <class 'numpy.float32'>, shape: Sequence[int] = (0, ), zooms: Sequence[float] | None = None)
    +
    + +
    +
    +classmethod may_contain_header(binaryblock)
    +
    + +
    + +
    +
    +

    Minc2Image

    +
    +
    +class nibabel.minc2.Minc2Image(dataobj: ArrayLike, affine: np.ndarray | None, header: FileBasedHeader | ty.Mapping | None = None, extra: ty.Mapping | None = None, file_map: FileMap | None = None)
    +

    Bases: Minc1Image

    +

    Class for MINC2 images

    +

    The MINC2 image class uses the default header type, rather than a +specific MINC header type - and reads the relevant information from +the MINC file on load.

    +

    Initialize image

    +

    The image is a combination of (array-like, affine matrix, header), with +optional metadata in extra, and filename / file-like objects +contained in the file_map mapping.

    +
    +
    Parameters:
    +
    +
    dataobjobject

    Object containing image data. It should be some object that returns an +array from np.asanyarray. It should have a shape attribute +or property

    +
    +
    affineNone or (4,4) array-like

    homogeneous affine giving relationship between voxel coordinates and +world coordinates. Affine can also be None. In this case, +obj.affine also returns None, and the affine as written to disk +will depend on the file format.

    +
    +
    headerNone or mapping or header instance, optional

    metadata for this image format

    +
    +
    extraNone or mapping, optional

    metadata to associate with image that cannot be stored in the +metadata of this image type

    +
    +
    file_mapmapping, optional

    mapping giving file information for this image format

    +
    +
    +
    +
    +
    +
    +__init__(dataobj: ArrayLike, affine: np.ndarray | None, header: FileBasedHeader | ty.Mapping | None = None, extra: ty.Mapping | None = None, file_map: FileMap | None = None)
    +

    Initialize image

    +

    The image is a combination of (array-like, affine matrix, header), with +optional metadata in extra, and filename / file-like objects +contained in the file_map mapping.

    +
    +
    Parameters:
    +
    +
    dataobjobject

    Object containing image data. It should be some object that returns an +array from np.asanyarray. It should have a shape attribute +or property

    +
    +
    affineNone or (4,4) array-like

    homogeneous affine giving relationship between voxel coordinates and +world coordinates. Affine can also be None. In this case, +obj.affine also returns None, and the affine as written to disk +will depend on the file format.

    +
    +
    headerNone or mapping or header instance, optional

    metadata for this image format

    +
    +
    extraNone or mapping, optional

    metadata to associate with image that cannot be stored in the +metadata of this image type

    +
    +
    file_mapmapping, optional

    mapping giving file information for this image format

    +
    +
    +
    +
    +
    + +
    +
    +classmethod from_file_map(file_map, *, mmap=True, keep_file_open=None)
    +

    Class method to create image from mapping in file_map

    +
    +
    Parameters:
    +
    +
    file_mapdict

    Mapping with (key, value) pairs of (file_type, FileHolder +instance giving file-likes for each file needed for this image +type.

    +
    +
    mmap{True, False, ‘c’, ‘r’}, optional, keyword only

    mmap controls the use of numpy memory mapping for reading image +array data. If False, do not try numpy memmap for data array. +If one of {‘c’, ‘r’}, try numpy memmap with mode=mmap. A +mmap value of True gives the same behavior as mmap='c'. If +image data file cannot be memory-mapped, ignore mmap value and +read array from file.

    +
    +
    keep_file_open{ None, True, False }, optional, keyword only

    keep_file_open controls whether a new file handle is created +every time the image is accessed, or a single file handle is +created and used for the lifetime of this ArrayProxy. If +True, a single file handle is created and used. If False, +a new file handle is created every time the image is accessed. +If file_map refers to an open file handle, this setting has no +effect. The default value (None) will result in the value of +nibabel.arrayproxy.KEEP_FILE_OPEN_DEFAULT being used.

    +
    +
    +
    +
    Returns:
    +
    +
    imgDataobjImage instance
    +
    +
    +
    +
    + +
    +
    +header_class
    +

    alias of Minc2Header

    +
    + +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.mriutils.html b/reference/nibabel.mriutils.html new file mode 100644 index 0000000000..eb86c5e60e --- /dev/null +++ b/reference/nibabel.mriutils.html @@ -0,0 +1,189 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    mriutils

    +

    Utilities for calculations related to MRI

    + + + + + + + + + +

    MRIError

    calculate_dwell_time(water_fat_shift, ...)

    Calculate the dwell time

    +
    +

    MRIError

    +
    +
    +class nibabel.mriutils.MRIError
    +

    Bases: ValueError

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    + +
    +
    +

    calculate_dwell_time

    +
    +
    +nibabel.mriutils.calculate_dwell_time(water_fat_shift, echo_train_length, field_strength)
    +

    Calculate the dwell time

    +
    +
    Parameters:
    +
    +
    water_fat_shiftfloat

    The water fat shift of the recording, in pixels.

    +
    +
    echo_train_lengthint

    The echo train length of the imaging sequence.

    +
    +
    field_strengthfloat

    Strength of the magnet in Tesla, e.g. 3.0 for a 3T magnet recording.

    +
    +
    +
    +
    Returns:
    +
    +
    dwell_timefloat

    The dwell time in seconds.

    +
    +
    +
    +
    Raises:
    +
    +
    MRIError

    if values are out of range

    +
    +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.nicom.html b/reference/nibabel.nicom.html new file mode 100644 index 0000000000..ba7c61ebc0 --- /dev/null +++ b/reference/nibabel.nicom.html @@ -0,0 +1,2358 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    nicom

    +

    DICOM reader

    + + + + + + + + + + + + + + + + + + +

    csareader

    CSA header reader from SPM spec

    dicomreaders

    dicomwrappers

    Classes to wrap DICOM objects and files

    dwiparams

    Process diffusion imaging parameters

    structreader

    Stream-like reader for packed data

    + + + +
    +
    +

    Module: nicom.ascconv

    +

    Parse the “ASCCONV” meta data format found in a variety of Siemens MR files.

    + + + + + + + + + + + + + + + + + + + + + +

    AscconvParseError

    Error parsing ascconv file

    Atom(op, obj_type, obj_id)

    Object to hold operation, object type and object identifier

    NoValue()

    Signals no value present

    assign2atoms(assign_ast[, default_class])

    Parse single assignment ast from ascconv line into atoms

    obj_from_atoms(atoms, namespace)

    Return object defined by list atoms in dict-like namespace

    parse_ascconv(ascconv_str[, str_delim])

    Parse the 'ASCCONV' format from input_str.

    +
    +
    +

    Module: nicom.csareader

    +

    CSA header reader from SPM spec

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    CSAError

    CSAReadError

    get_acq_mat_txt(csa_dict)

    get_b_matrix(csa_dict)

    get_b_value(csa_dict)

    get_csa_header(dcm_data[, csa_type])

    Get CSA header information from DICOM header

    get_g_vector(csa_dict)

    get_ice_dims(csa_dict)

    get_n_mosaic(csa_dict)

    get_scalar(csa_dict, tag_name)

    get_slice_normal(csa_dict)

    get_vector(csa_dict, tag_name, n)

    is_mosaic(csa_dict)

    Return True if the data is of Mosaic type

    nt_str(s)

    Strip string to first null

    read(csa_str)

    Read CSA header from string csa_str

    +
    +
    +

    Module: nicom.dicomreaders

    + + + + + + + + + + + + + + + + + + +

    DicomReadError

    mosaic_to_nii(dcm_data)

    Get Nifti file from Siemens

    read_mosaic_dir(dicom_path[, globber, ...])

    Read all Siemens mosaic DICOMs in directory, return arrays, params

    read_mosaic_dwi_dir(dicom_path[, globber, ...])

    slices_to_series(wrappers)

    Sort sequence of slice wrappers into series

    +
    +
    +

    Module: nicom.dicomwrappers

    +

    Classes to wrap DICOM objects and files

    +

    The wrappers encapsulate the capabilities of the different DICOM +formats.

    +

    They also allow dictionary-like access to named fields.

    +

    For calculated attributes, we return None where needed data is missing. +It seemed strange to raise an error during attribute processing, other +than an AttributeError - breaking the ‘properties manifesto’. So, any +processing that needs to raise an error, should be in a method, rather +than in a property, or property-like thing.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    FilterDwiIso()

    Filter out derived ISOTROPIC frames from DWI series

    FilterMultiStack([keep_id])

    Filter out all but one StackID

    FrameFilter()

    Base class for defining how to filter out (ignore) frames from a multiframe file

    MosaicWrapper(dcm_data[, csa_header, n_mosaic])

    Class for Siemens mosaic format data

    MultiframeWrapper(dcm_data[, frame_filters])

    Wrapper for Enhanced MR Storage SOP Class

    SiemensWrapper(dcm_data[, csa_header])

    Wrapper for Siemens format DICOMs

    Wrapper(dcm_data)

    Class to wrap general DICOM files

    WrapperError

    WrapperPrecisionError

    none_or_close(val1, val2[, rtol, atol])

    Match if val1 and val2 are both None, or are close

    wrapper_from_data(dcm_data[, frame_filters])

    Create DICOM wrapper from DICOM data object

    wrapper_from_file(file_like, *args, **kwargs)

    Create DICOM wrapper from file_like object

    +
    +
    +

    Module: nicom.dwiparams

    +

    Process diffusion imaging parameters

    +
      +
    • q is a vector in Q space

    • +
    • b is a b value

    • +
    • g is the unit vector along the direction of q (the gradient +direction)

    • +
    +

    Thus:

    +
    +

    b = norm(q)

    +

    g = q / norm(q)

    +
    +

    (norm(q) is the Euclidean norm of q)

    +

    The B matrix B is a symmetric positive semi-definite matrix. If +q_est is the closest q vector equivalent to the B matrix, then:

    +
    +

    B ~ (q_est . q_est.T) / norm(q_est)

    +
    + + + + + + + + + + + + +

    B2q(B[, tol])

    Estimate q vector from input B matrix B

    nearest_pos_semi_def(B)

    Least squares positive semi-definite tensor estimation

    q2bg(q_vector[, tol])

    Return b value and q unit vector from q vector q_vector

    +
    +
    +

    Module: nicom.structreader

    +

    Stream-like reader for packed data

    + + + + + + +

    Unpacker(buf[, ptr, endian])

    Class to unpack values from buffer object

    +
    +
    +

    Module: nicom.utils

    +

    Utilities for working with DICOM datasets

    + + + + + + + + + + + + +

    Vendor(value[, names, module, qualname, ...])

    find_private_section(dcm_data, group_no, creator)

    Return start element in group group_no given creator name creator

    vendor_from_private(dcm_data)

    Try to determine the vendor by looking for specific private tags

    +
    +

    AscconvParseError

    +
    +
    +class nibabel.nicom.ascconv.AscconvParseError
    +

    Bases: Exception

    +

    Error parsing ascconv file

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    + +
    +
    +

    Atom

    +
    +
    +class nibabel.nicom.ascconv.Atom(op, obj_type, obj_id)
    +

    Bases: object

    +

    Object to hold operation, object type and object identifier

    +

    An atom represents an element in an expression. For example:

    +
    a.b[0].c
    +
    +
    +

    has four elements. We call these elements “atoms”.

    +

    We represent objects (like a) as dicts for convenience.

    +

    The last element (.c) is an op = ast.Attribute operation where the +object type (obj_type) of c is not constrained (we can’t tell from +the operation what type it is). The obj_id is the name of the object – +“c”.

    +

    The second to last element [0], is op = ast.Subscript, with object type +dict (we know from the subsequent operation .c that this must be an +object, we represent the object by a dict). The obj_id is the index 0.

    +
    +
    Parameters:
    +
    +
    op{‘name’, ‘attr’, ‘list’}

    Assignment type. Assignment to name (root namespace), attribute or +list element.

    +
    +
    obj_type{list, dict, other}

    Object type being assigned to.

    +
    +
    obj_idstr or int

    Key (obj_type is dict) or index (obj_type is list)

    +
    +
    +
    +
    +
    +
    +__init__(op, obj_type, obj_id)
    +
    + +
    + +
    +
    +

    NoValue

    +
    +
    +class nibabel.nicom.ascconv.NoValue
    +

    Bases: object

    +

    Signals no value present

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    + +
    +
    +

    assign2atoms

    +
    +
    +nibabel.nicom.ascconv.assign2atoms(assign_ast, default_class=<class 'int'>)
    +

    Parse single assignment ast from ascconv line into atoms

    +
    +
    Parameters:
    +
    +
    assign_astassignment statement ast

    ast derived from single line of ascconv file.

    +
    +
    default_classclass, optional

    Class that will create an object where we cannot yet know the object +type in the assignment.

    +
    +
    +
    +
    Returns:
    +
    +
    atomslist

    List of atoms. See docstring for atoms. Defines +left to right sequence of assignment in line_ast.

    +
    +
    +
    +
    +
    + +
    +
    +

    obj_from_atoms

    +
    +
    +nibabel.nicom.ascconv.obj_from_atoms(atoms, namespace)
    +

    Return object defined by list atoms in dict-like namespace

    +
    +
    Parameters:
    +
    +
    atomslist

    List of atoms

    +
    +
    namespacedict-like

    Namespace in which object will be defined.

    +
    +
    +
    +
    Returns:
    +
    +
    obj_rootobject

    Namespace such that we can set a desired value to the object defined in +atoms with obj_root[obj_key] = value.

    +
    +
    obj_keystr or int

    Index into list or key into dictionary for obj_root.

    +
    +
    +
    +
    +
    + +
    +
    +

    parse_ascconv

    +
    +
    +nibabel.nicom.ascconv.parse_ascconv(ascconv_str, str_delim='"')
    +

    Parse the ‘ASCCONV’ format from input_str.

    +
    +
    Parameters:
    +
    +
    ascconv_strstr

    The string we are parsing

    +
    +
    str_delimstr, optional

    String delimiter. Typically ‘”’ or ‘””’

    +
    +
    +
    +
    Returns:
    +
    +
    prot_dictOrderedDict

    Meta data pulled from the ASCCONV section.

    +
    +
    attrsOrderedDict

    Any attributes stored in the ‘ASCCONV BEGIN’ line

    +
    +
    +
    +
    Raises:
    +
    +
    AsconvParseError

    A line of the ASCCONV section could not be parsed.

    +
    +
    +
    +
    +
    + +
    +
    +

    CSAError

    +
    +
    +class nibabel.nicom.csareader.CSAError
    +

    Bases: Exception

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    + +
    +
    +

    CSAReadError

    +
    +
    +class nibabel.nicom.csareader.CSAReadError
    +

    Bases: CSAError

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    + +
    +
    +

    get_acq_mat_txt

    +
    +
    +nibabel.nicom.csareader.get_acq_mat_txt(csa_dict)
    +
    + +
    +
    +

    get_b_matrix

    +
    +
    +nibabel.nicom.csareader.get_b_matrix(csa_dict)
    +
    + +
    +
    +

    get_b_value

    +
    +
    +nibabel.nicom.csareader.get_b_value(csa_dict)
    +
    + +
    +
    +

    get_csa_header

    +
    +
    +nibabel.nicom.csareader.get_csa_header(dcm_data, csa_type='image')
    +

    Get CSA header information from DICOM header

    +

    Return None if the header does not contain CSA information of the +specified csa_type

    +
    +
    Parameters:
    +
    +
    dcm_datadicom.Dataset

    DICOM dataset. Should implement __getitem__ and, if initial check +for presence of dcm_data[(0x29, 0x10)] passes, should satisfy +interface for find_private_section.

    +
    +
    csa_type{‘image’, ‘series’}, optional

    Type of CSA field to read; default is ‘image’

    +
    +
    +
    +
    Returns:
    +
    +
    csa_infoNone or dict

    Parsed CSA field of csa_type or None, if we cannot find the CSA +information.

    +
    +
    +
    +
    +
    + +
    +
    +

    get_g_vector

    +
    +
    +nibabel.nicom.csareader.get_g_vector(csa_dict)
    +
    + +
    +
    +

    get_ice_dims

    +
    +
    +nibabel.nicom.csareader.get_ice_dims(csa_dict)
    +
    + +
    +
    +

    get_n_mosaic

    +
    +
    +nibabel.nicom.csareader.get_n_mosaic(csa_dict)
    +
    + +
    +
    +

    get_scalar

    +
    +
    +nibabel.nicom.csareader.get_scalar(csa_dict, tag_name)
    +
    + +
    +
    +

    get_slice_normal

    +
    +
    +nibabel.nicom.csareader.get_slice_normal(csa_dict)
    +
    + +
    +
    +

    get_vector

    +
    +
    +nibabel.nicom.csareader.get_vector(csa_dict, tag_name, n)
    +
    + +
    +
    +

    is_mosaic

    +
    +
    +nibabel.nicom.csareader.is_mosaic(csa_dict)
    +

    Return True if the data is of Mosaic type

    +
    +
    Parameters:
    +
    +
    csa_dictdict

    dict containing read CSA data

    +
    +
    +
    +
    Returns:
    +
    +
    tfbool

    True if the dcm_data appears to be of Siemens mosaic type, +False otherwise

    +
    +
    +
    +
    +
    + +
    +
    +

    nt_str

    +
    +
    +nibabel.nicom.csareader.nt_str(s)
    +

    Strip string to first null

    +
    +
    Parameters:
    +
    +
    sbytes
    +
    +
    +
    Returns:
    +
    +
    sdashstr

    s stripped to first occurrence of null (0)

    +
    +
    +
    +
    +
    + +
    +
    +

    read

    +
    +
    +nibabel.nicom.csareader.read(csa_str)
    +

    Read CSA header from string csa_str

    +
    +
    Parameters:
    +
    +
    csa_strstr

    byte string containing CSA header information

    +
    +
    +
    +
    Returns:
    +
    +
    headerdict

    header information as dict, where header has fields (at least) +type, n_tags, tags. header['tags'] is also a dictionary +with one key, value pair for each tag in the header.

    +
    +
    +
    +
    +
    + +
    +
    +

    DicomReadError

    +
    +
    +class nibabel.nicom.dicomreaders.DicomReadError
    +

    Bases: Exception

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    + +
    +
    +

    mosaic_to_nii

    +
    +
    +nibabel.nicom.dicomreaders.mosaic_to_nii(dcm_data)
    +

    Get Nifti file from Siemens

    +
    +
    Parameters:
    +
    +
    dcm_datadicom.DataSet

    DICOM header / image as read by dicom package

    +
    +
    +
    +
    Returns:
    +
    +
    imgNifti1Image

    Nifti image object

    +
    +
    +
    +
    +
    + +
    +
    +

    read_mosaic_dir

    +
    +
    +nibabel.nicom.dicomreaders.read_mosaic_dir(dicom_path, globber='*.dcm', check_is_dwi=False, dicom_kwargs=None)
    +

    Read all Siemens mosaic DICOMs in directory, return arrays, params

    +
    +
    Parameters:
    +
    +
    dicom_pathstr

    path containing mosaic DICOM images

    +
    +
    globberstr, optional

    glob to apply within dicom_path to select DICOM files. Default +is *.dcm

    +
    +
    check_is_dwibool, optional

    If True, raises an error if we don’t find DWI information in the +DICOM headers.

    +
    +
    dicom_kwargsNone or dict

    Extra keyword arguments to pass to the pydicom dcmread function.

    +
    +
    +
    +
    Returns:
    +
    +
    data4D array

    data array with last dimension being acquisition. If there were N +acquisitions, each of shape (X, Y, Z), data will be shape (X, +Y, Z, N)

    +
    +
    affine(4,4) array

    affine relating 3D voxel space in data to RAS world space

    +
    +
    b_values(N,) array

    b values for each acquisition. nan if we did not find diffusion +information for these images.

    +
    +
    unit_gradients(N, 3) array

    gradient directions of unit length for each acquisition. (nan, +nan, nan) if we did not find diffusion information.

    +
    +
    +
    +
    +
    + +
    +
    +

    read_mosaic_dwi_dir

    +
    +
    +nibabel.nicom.dicomreaders.read_mosaic_dwi_dir(dicom_path, globber='*.dcm', dicom_kwargs=None)
    +
    + +
    +
    +

    slices_to_series

    +
    +
    +nibabel.nicom.dicomreaders.slices_to_series(wrappers)
    +

    Sort sequence of slice wrappers into series

    +

    This follows the SPM model fairly closely

    +
    +
    Parameters:
    +
    +
    wrapperssequence

    sequence of Wrapper objects for sorting into volumes

    +
    +
    +
    +
    Returns:
    +
    +
    seriessequence

    sequence of sequences of wrapper objects, where each sequence is +wrapper objects comprising a series, sorted into slice order

    +
    +
    +
    +
    +
    + +
    +
    +

    FilterDwiIso

    +
    +
    +class nibabel.nicom.dicomwrappers.FilterDwiIso
    +

    Bases: FrameFilter

    +

    Filter out derived ISOTROPIC frames from DWI series

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    +
    +applies(dcm_wrp) bool
    +

    Returns true if the filter should be applied to a dataset

    +
    + +
    +
    +keep(frame) bool
    +

    Return true if the frame should be kept

    +
    + +
    + +
    +
    +

    FilterMultiStack

    +
    +
    +class nibabel.nicom.dicomwrappers.FilterMultiStack(keep_id=None)
    +

    Bases: FrameFilter

    +

    Filter out all but one StackID

    +
    +
    +__init__(keep_id=None)
    +
    + +
    +
    +applies(dcm_wrp) bool
    +

    Returns true if the filter should be applied to a dataset

    +
    + +
    +
    +keep(frame) bool
    +

    Return true if the frame should be kept

    +
    + +
    + +
    +
    +

    FrameFilter

    +
    +
    +class nibabel.nicom.dicomwrappers.FrameFilter
    +

    Bases: object

    +

    Base class for defining how to filter out (ignore) frames from a multiframe file

    +

    It is guaranteed that the applies method will on a dataset before the keep method +is called on any of the frames inside.

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    +
    +applies(dcm_wrp) bool
    +

    Returns true if the filter should be applied to a dataset

    +
    + +
    +
    +keep(frame_data) bool
    +

    Return true if the frame should be kept

    +
    + +
    + +
    +
    +

    MosaicWrapper

    +
    +
    +class nibabel.nicom.dicomwrappers.MosaicWrapper(dcm_data, csa_header=None, n_mosaic=None)
    +

    Bases: SiemensWrapper

    +

    Class for Siemens mosaic format data

    +

    Mosaic format is a way of storing a 3D image in a 2D slice - and +it’s as simple as you’d imagine it would be - just storing the slices +in a mosaic similar to a light-box print.

    +

    We need to allow for this when getting the data and (because of an +idiosyncrasy in the way Siemens stores the images) calculating the +position of the first voxel.

    +

    Adds attributes:

    +
      +
    • n_mosaic : int

    • +
    • mosaic_size : int

    • +
    +

    Initialize Siemens Mosaic wrapper

    +

    The Siemens-specific information is in the csa_header, either +passed in here, or read from the input dcm_data.

    +
    +
    Parameters:
    +
    +
    dcm_dataobject

    object should allow ‘get’ and ‘__getitem__’ access. If csa_header +is None, it should also be possible for to extract a CSA header from +dcm_data. Usually this will be a dicom.dataset.Dataset object +resulting from reading a DICOM file. A dict should also work.

    +
    +
    csa_headerNone or mapping, optional

    mapping giving values for Siemens CSA image sub-header.

    +
    +
    n_mosaicNone or int, optional

    number of images in mosaic. If None, try to get this number +from csa_header. If this fails, raise an error

    +
    +
    +
    +
    +
    +
    +__init__(dcm_data, csa_header=None, n_mosaic=None)
    +

    Initialize Siemens Mosaic wrapper

    +

    The Siemens-specific information is in the csa_header, either +passed in here, or read from the input dcm_data.

    +
    +
    Parameters:
    +
    +
    dcm_dataobject

    object should allow ‘get’ and ‘__getitem__’ access. If csa_header +is None, it should also be possible for to extract a CSA header from +dcm_data. Usually this will be a dicom.dataset.Dataset object +resulting from reading a DICOM file. A dict should also work.

    +
    +
    csa_headerNone or mapping, optional

    mapping giving values for Siemens CSA image sub-header.

    +
    +
    n_mosaicNone or int, optional

    number of images in mosaic. If None, try to get this number +from csa_header. If this fails, raise an error

    +
    +
    +
    +
    +
    + +
    +
    +get_unscaled_data()
    +

    Get scaled image data from DICOMs

    +

    Resorts data block from mosaic to 3D

    +
    +
    Returns:
    +
    +
    dataarray

    array with data as scaled from any scaling in the DICOM +fields.

    +
    +
    +
    +
    +

    Notes

    +

    The apparent image in the DICOM file is a 2D array that consists of +blocks, that are the output 2D slices. Let’s call the original array +the slab, and the contained slices slices. The slices are of +pixel dimension n_slice_rows x n_slice_cols. The slab is of +pixel dimension n_slab_rows x n_slab_cols. Because the +arrangement of blocks in the slab is defined as being square, the +number of blocks per slab row and slab column is the same. Let +n_blocks be the number of blocks contained in the slab. There is +also n_slices - the number of slices actually collected, some +number <= n_blocks. We have the value n_slices from the +‘NumberOfImagesInMosaic’ field of the Siemens private (CSA) header. +n_row_blocks and n_col_blocks are therefore given by +ceil(sqrt(n_slices)), and n_blocks is n_row_blocks ** 2. +Also n_slice_rows == n_slab_rows / n_row_blocks, etc. Using these +numbers we can therefore reconstruct the slices from the 2D DICOM pixel +array.

    +
    + +
    +
    +property image_position
    +

    Return position of first voxel in data block

    +

    Adjusts Siemens mosaic position vector for bug in mosaic format +position. See dicom_mosaic in doc/theory for details.

    +
    +
    Parameters:
    +
    +
    None
    +
    +
    +
    Returns:
    +
    +
    img_pos(3,) array

    position in mm of voxel (0,0,0) in Mosaic array

    +
    +
    +
    +
    +
    + +
    +
    +property image_shape
    +

    Return image shape as returned by get_data()

    +
    + +
    +
    +is_mosaic = True
    +
    + +
    + +
    +
    +

    MultiframeWrapper

    +
    +
    +class nibabel.nicom.dicomwrappers.MultiframeWrapper(dcm_data, frame_filters=None)
    +

    Bases: Wrapper

    +

    Wrapper for Enhanced MR Storage SOP Class

    +

    Tested with Philips’ Enhanced DICOM implementation.

    +

    The specification for the Enhanced MR image IOP / SOP began life as DICOM +supplement 49, +but as of 2016 it is part of the standard. In particular see:

    + +
    +
    Attributes:
    +
    +
    is_multiframeboolean

    Identifies dcmdata as multi-frame

    +
    +
    framessequence

    A sequence of dicom.dataset.Dataset objects populated by the +dicom.dataset.Dataset.PerFrameFunctionalGroupsSequence attribute

    +
    +
    sharedobject

    The first (and only) dicom.dataset.Dataset object from a +dicom.dataset.Dataset.SharedFunctionalgroupSequence.

    +
    +
    +
    +
    +

    Methods

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    vendor(self)

    frame_order(self)

    image_shape(self)

    image_orient_patient(self)

    voxel_sizes(self)

    image_position(self)

    series_signature(self)

    scale_factors(self)

    get_data(self)

    +

    Initializes MultiframeWrapper

    +
    +
    Parameters:
    +
    +
    dcm_dataobject

    object should allow ‘get’ and ‘__getitem__’ access. Usually this +will be a dicom.dataset.Dataset object resulting from reading a +DICOM file.

    +
    +
    frame_filtersIterable of FrameFilter

    defines which frames inside the dataset should be ignored. If None then +dicomwrappers.DEFAULT_FRAME_FILTERS will be used.

    +
    +
    +
    +
    +
    +
    +__init__(dcm_data, frame_filters=None)
    +

    Initializes MultiframeWrapper

    +
    +
    Parameters:
    +
    +
    dcm_dataobject

    object should allow ‘get’ and ‘__getitem__’ access. Usually this +will be a dicom.dataset.Dataset object resulting from reading a +DICOM file.

    +
    +
    frame_filtersIterable of FrameFilter

    defines which frames inside the dataset should be ignored. If None then +dicomwrappers.DEFAULT_FRAME_FILTERS will be used.

    +
    +
    +
    +
    +
    + +
    +
    +property frame_order
    +
    + +
    +
    +get_unscaled_data()
    +

    Return pixel array that is potentially reshaped, but without any scaling

    +
    +
    Returns:
    +
    +
    dataarray

    array with raw pixel data from DICOM

    +
    +
    +
    +
    +
    + +
    +
    +property image_orient_patient
    +

    Note that this is _not_ LR flipped

    +
    + +
    +
    +property image_position
    +

    Return position of first voxel in data block

    +
    +
    Parameters:
    +
    +
    None
    +
    +
    +
    Returns:
    +
    +
    img_pos(3,) array

    position in mm of voxel (0,0) in image array

    +
    +
    +
    +
    +
    + +
    +
    +property image_shape
    +

    The array shape as it will be returned by get_data()

    +

    The shape is determined by the Rows DICOM attribute, Columns +DICOM attribute, and the set of frame indices given by the +FrameContentSequence[0].DimensionIndexValues DICOM attribute of each +element in the PerFrameFunctionalGroupsSequence. The first two +axes of the returned shape correspond to the rows, and columns +respectively. The remaining axes correspond to those of the frame +indices with order preserved.

    +

    What each axis in the frame indices refers to is given by the +corresponding entry in the DimensionIndexSequence DICOM attribute. +WARNING: Any axis referring to the StackID DICOM attribute will +have been removed from the frame indices in determining the shape. This +is because only a file containing a single stack is currently allowed by +this wrapper.

    +

    References

    + +
    + +
    +
    +is_multiframe = True
    +
    + +
    +
    +property scale_factors
    +

    Return (2, N) array of slope/intercept pairs

    +

    If there is a single global scale factor then N will be one, otherwise it will +be the number of frames

    +
    + +
    +
    +property series_signature
    +

    Signature for matching slices into series

    +

    We use signature in self.is_same_series(other).

    +
    +
    Returns:
    +
    +
    signaturedict

    with values of 2-element sequences, where first element is +value, and second element is function to compare this value +with another. This allows us to pass things like arrays, +that might need to be allclose instead of equal

    +
    +
    +
    +
    +
    + +
    +
    +property vendor
    +

    The vendor of the instrument that produced the DICOM

    +
    + +
    +
    +property voxel_sizes
    +

    Get i, j, k voxel sizes

    +
    + +
    + +
    +
    +

    SiemensWrapper

    +
    +
    +class nibabel.nicom.dicomwrappers.SiemensWrapper(dcm_data, csa_header=None)
    +

    Bases: Wrapper

    +

    Wrapper for Siemens format DICOMs

    +

    Adds attributes:

    +
      +
    • csa_header : mapping

    • +
    • b_matrix : (3,3) array

    • +
    • q_vector : (3,) array

    • +
    +

    Initialize Siemens wrapper

    +

    The Siemens-specific information is in the csa_header, either +passed in here, or read from the input dcm_data.

    +
    +
    Parameters:
    +
    +
    dcm_dataobject

    object should allow ‘get’ and ‘__getitem__’ access. If csa_header +is None, it should also be possible to extract a CSA header from +dcm_data. Usually this will be a dicom.dataset.Dataset object +resulting from reading a DICOM file.

    +
    +
    csa_headerNone or mapping, optional

    mapping giving values for Siemens CSA image sub-header. If +None, we try and read the CSA information from dcm_data. +If this fails, we fall back to an empty dict.

    +
    +
    +
    +
    +
    +
    +__init__(dcm_data, csa_header=None)
    +

    Initialize Siemens wrapper

    +

    The Siemens-specific information is in the csa_header, either +passed in here, or read from the input dcm_data.

    +
    +
    Parameters:
    +
    +
    dcm_dataobject

    object should allow ‘get’ and ‘__getitem__’ access. If csa_header +is None, it should also be possible to extract a CSA header from +dcm_data. Usually this will be a dicom.dataset.Dataset object +resulting from reading a DICOM file.

    +
    +
    csa_headerNone or mapping, optional

    mapping giving values for Siemens CSA image sub-header. If +None, we try and read the CSA information from dcm_data. +If this fails, we fall back to an empty dict.

    +
    +
    +
    +
    +
    + +
    +
    +property b_matrix
    +

    Get DWI B matrix referring to voxel space

    +
    +
    Parameters:
    +
    +
    None
    +
    +
    +
    Returns:
    +
    +
    B(3,3) array or None

    B matrix in voxel orientation space. Returns None if this is +not a Siemens header with the required information. We return +None if this is a b0 acquisition

    +
    +
    +
    +
    +
    + +
    +
    +is_csa = True
    +
    + +
    +
    +property q_vector
    +

    Get DWI q vector referring to voxel space

    +
    +
    Parameters:
    +
    +
    None
    +
    +
    +
    Returns:
    +
    +
    q: (3,) array

    Estimated DWI q vector in voxel orientation space. Returns +None if this is not (detectably) a DWI

    +
    +
    +
    +
    +
    + +
    +
    +property series_signature
    +

    Add ICE dims from CSA header to signature

    +
    + +
    +
    +property slice_normal
    +
    + +
    +
    +property vendor
    +

    The vendor of the instrument that produced the DICOM

    +
    + +
    + +
    +
    +

    Wrapper

    +
    +
    +class nibabel.nicom.dicomwrappers.Wrapper(dcm_data)
    +

    Bases: object

    +

    Class to wrap general DICOM files

    +

    Methods:

    +
      +
    • get_data()

    • +
    • get_unscaled_data()

    • +
    • get_pixel_array()

    • +
    • is_same_series(other)

    • +
    • __getitem__ : return attributes from dcm_data

    • +
    • get(key[, default]) - as usual given __getitem__ above

    • +
    +

    Attributes and things that look like attributes:

    +
      +
    • affine : (4, 4) array

    • +
    • dcm_data : object

    • +
    • image_shape : tuple

    • +
    • image_orient_patient : (3,2) array

    • +
    • slice_normal : (3,) array

    • +
    • rotation_matrix : (3,3) array

    • +
    • voxel_sizes : tuple length 3

    • +
    • image_position : sequence length 3

    • +
    • slice_indicator : float

    • +
    • series_signature : tuple

    • +
    • scale_factors : (N, 2) array

    • +
    • vendor : Vendor

    • +
    +

    Initialize wrapper

    +
    +
    Parameters:
    +
    +
    dcm_dataobject

    object should allow ‘get’ and ‘__getitem__’ access. Usually this +will be a dicom.dataset.Dataset object resulting from reading a +DICOM file.

    +
    +
    +
    +
    +
    +
    +__init__(dcm_data)
    +

    Initialize wrapper

    +
    +
    Parameters:
    +
    +
    dcm_dataobject

    object should allow ‘get’ and ‘__getitem__’ access. Usually this +will be a dicom.dataset.Dataset object resulting from reading a +DICOM file.

    +
    +
    +
    +
    +
    + +
    +
    +property affine
    +

    Mapping between voxel and DICOM coordinate system

    +

    (4, 4) affine matrix giving transformation between voxels in data array +and mm in the DICOM patient coordinate system.

    +
    + +
    +
    +b_matrix = None
    +
    + +
    +
    +property b_value
    +

    Return b value for diffusion or None if not available

    +
    + +
    +
    +property b_vector
    +

    Return b vector for diffusion or None if not available

    +
    + +
    +
    +get(key, default=None)
    +

    Get values from underlying dicom data

    +
    + +
    +
    +get_data()
    +

    Get potentially scaled and reshaped image data from DICOMs

    +

    We return the data as DICOM understands it, first dimension is +rows, second dimension is columns

    +
    +
    Returns:
    +
    +
    dataarray

    array with data as scaled from any scaling in the DICOM +fields.

    +
    +
    +
    +
    +
    + +
    +
    +get_pixel_array()
    +

    Return raw pixel array without reshaping or scaling

    +
    +
    Returns:
    +
    +
    dataarray

    array with raw pixel data from DICOM

    +
    +
    +
    +
    +
    + +
    +
    +get_unscaled_data()
    +

    Return pixel array that is potentially reshaped, but without any scaling

    +
    +
    Returns:
    +
    +
    dataarray

    array with raw pixel data from DICOM

    +
    +
    +
    +
    +
    + +
    +
    +property image_orient_patient
    +

    Note that this is _not_ LR flipped

    +
    + +
    +
    +property image_position
    +

    Return position of first voxel in data block

    +
    +
    Parameters:
    +
    +
    None
    +
    +
    +
    Returns:
    +
    +
    img_pos(3,) array

    position in mm of voxel (0,0) in image array

    +
    +
    +
    +
    +
    + +
    +
    +property image_shape
    +

    The array shape as it will be returned by get_data()

    +
    + +
    +
    +property instance_number
    +

    Just because we use this a lot for sorting

    +
    + +
    +
    +is_csa = False
    +
    + +
    +
    +is_mosaic = False
    +
    + +
    +
    +is_multiframe = False
    +
    + +
    +
    +is_same_series(other)
    +

    Return True if other appears to be in same series

    +
    +
    Parameters:
    +
    +
    otherobject

    object with series_signature attribute that is a +mapping. Usually it’s a Wrapper or sub-class instance.

    +
    +
    +
    +
    Returns:
    +
    +
    tfbool

    True if other might be in the same series as self, False +otherwise.

    +
    +
    +
    +
    +
    + +
    +
    +q_vector = None
    +
    + +
    +
    +property rotation_matrix
    +

    Return rotation matrix between array indices and mm

    +

    Note that we swap the two columns of the ‘ImageOrientPatient’ +when we create the rotation matrix. This is takes into account +the slightly odd ij transpose construction of the DICOM +orientation fields - see doc/theory/dicom_orientaiton.rst.

    +
    + +
    +
    +property scale_factors
    +

    Return (2, N) array of slope/intercept pairs

    +
    + +
    +
    +property series_signature
    +

    Signature for matching slices into series

    +

    We use signature in self.is_same_series(other).

    +
    +
    Returns:
    +
    +
    signaturedict

    with values of 2-element sequences, where first element is +value, and second element is function to compare this value +with another. This allows us to pass things like arrays, +that might need to be allclose instead of equal

    +
    +
    +
    +
    +
    + +
    +
    +property slice_indicator
    +

    A number that is higher for higher slices in Z

    +

    Comparing this number between two adjacent slices should give a +difference equal to the voxel size in Z.

    +

    See doc/theory/dicom_orientation for description

    +
    + +
    +
    +property slice_normal
    +
    + +
    +
    +property vendor
    +

    The vendor of the instrument that produced the DICOM

    +
    + +
    +
    +property voxel_sizes
    +

    voxel sizes for array as returned by get_data()

    +
    + +
    + +
    +
    +

    WrapperError

    +
    +
    +class nibabel.nicom.dicomwrappers.WrapperError
    +

    Bases: Exception

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    + +
    +
    +

    WrapperPrecisionError

    +
    +
    +class nibabel.nicom.dicomwrappers.WrapperPrecisionError
    +

    Bases: WrapperError

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    + +
    +
    +

    none_or_close

    +
    +
    +nibabel.nicom.dicomwrappers.none_or_close(val1, val2, rtol=1e-05, atol=1e-06)
    +

    Match if val1 and val2 are both None, or are close

    +
    +
    Parameters:
    +
    +
    val1None or array-like
    +
    val2None or array-like
    +
    rtolfloat, optional

    Relative tolerance; see np.allclose

    +
    +
    atolfloat, optional

    Absolute tolerance; see np.allclose

    +
    +
    +
    +
    Returns:
    +
    +
    tfbool

    True iff (both val1 and val2 are None) or (val1 and val2 +are close arrays, as detected by np.allclose with parameters +rtol and atal).

    +
    +
    +
    +
    +

    Examples

    +
    >>> none_or_close(None, None)
    +True
    +>>> none_or_close(1, None)
    +False
    +>>> none_or_close(None, 1)
    +False
    +>>> none_or_close([1,2], [1,2])
    +True
    +>>> none_or_close([0,1], [0,2])
    +False
    +
    +
    +
    + +
    +
    +

    wrapper_from_data

    +
    +
    +nibabel.nicom.dicomwrappers.wrapper_from_data(dcm_data, frame_filters=None)
    +

    Create DICOM wrapper from DICOM data object

    +
    +
    Parameters:
    +
    +
    dcm_datadicom.dataset.Dataset instance or similar

    Object allowing attribute access, with DICOM attributes. +Probably a dataset as read by pydicom.

    +
    +
    frame_filters

    Optionally override the frame_filters used to create a MultiFrameWrapper

    +
    +
    +
    +
    Returns:
    +
    +
    dcm_wdicomwrappers.Wrapper or subclass

    DICOM wrapper corresponding to DICOM data type

    +
    +
    +
    +
    +
    + +
    +
    +

    wrapper_from_file

    +
    +
    +nibabel.nicom.dicomwrappers.wrapper_from_file(file_like, *args, **kwargs)
    +

    Create DICOM wrapper from file_like object

    +
    +
    Parameters:
    +
    +
    file_likeobject

    filename string or file-like object, pointing to a valid DICOM +file readable by pydicom

    +
    +
    *argspositional

    args to dicom.dcmread command.

    +
    +
    **kwargskeyword

    args to dicom.dcmread command. force=True might be a +likely keyword argument.

    +
    +
    +
    +
    Returns:
    +
    +
    dcm_wdicomwrappers.Wrapper or subclass

    DICOM wrapper corresponding to DICOM data type

    +
    +
    +
    +
    +
    + +
    +
    +

    B2q

    +
    +
    +nibabel.nicom.dwiparams.B2q(B, tol=None)
    +

    Estimate q vector from input B matrix B

    +

    We require that the input B is symmetric positive definite.

    +

    Because the solution is a square root, the sign of the returned +vector is arbitrary. We set the vector to have a positive x +component by convention.

    +
    +
    Parameters:
    +
    +
    B(3,3) array-like

    B matrix - symmetric. We do not check the symmetry.

    +
    +
    tolNone or float

    absolute tolerance below which to consider eigenvalues of the B +matrix to be small enough not to worry about them being negative, +in check for positive semi-definite-ness. None (default) results +in a fairly tight numerical threshold proportional to the maximum +eigenvalue

    +
    +
    +
    +
    Returns:
    +
    +
    q(3,) vector

    Estimated q vector from B matrix B

    +
    +
    +
    +
    +
    + +
    +
    +

    nearest_pos_semi_def

    +
    +
    +nibabel.nicom.dwiparams.nearest_pos_semi_def(B)
    +

    Least squares positive semi-definite tensor estimation

    +

    Reference: Niethammer M, San Jose Estepar R, Bouix S, Shenton M, +Westin CF. On diffusion tensor estimation. Conf Proc IEEE Eng Med +Biol Soc. 2006;1:2622-5. PubMed PMID: 17946125; PubMed Central +PMCID: PMC2791793.

    +
    +
    Parameters:
    +
    +
    B(3,3) array-like

    B matrix - symmetric. We do not check the symmetry.

    +
    +
    +
    +
    Returns:
    +
    +
    npds(3,3) array

    Estimated nearest positive semi-definite array to matrix B.

    +
    +
    +
    +
    +

    Examples

    +
    >>> B = np.diag([1, 1, -1])
    +>>> nearest_pos_semi_def(B)
    +array([[0.75, 0.  , 0.  ],
    +       [0.  , 0.75, 0.  ],
    +       [0.  , 0.  , 0.  ]])
    +
    +
    +
    + +
    +
    +

    q2bg

    +
    +
    +nibabel.nicom.dwiparams.q2bg(q_vector, tol=1e-05)
    +

    Return b value and q unit vector from q vector q_vector

    +
    +
    Parameters:
    +
    +
    q_vector(3,) array-like

    q vector

    +
    +
    tolfloat, optional

    q vector L2 norm below which q_vector considered to be b_value of +zero, and therefore g_vector also considered to zero.

    +
    +
    +
    +
    Returns:
    +
    +
    b_valuefloat

    L2 Norm of q_vector or 0 if L2 norm < tol

    +
    +
    g_vectorshape (3,) ndarray

    q_vector / b_value or 0 if L2 norma < tol

    +
    +
    +
    +
    +

    Examples

    +
    >>> q2bg([1, 0, 0])
    +(1.0, array([1., 0., 0.]))
    +>>> q2bg([0, 10, 0])
    +(10.0, array([0., 1., 0.]))
    +>>> q2bg([0, 0, 0])
    +(0.0, array([0., 0., 0.]))
    +
    +
    +
    + +
    +
    +

    Unpacker

    +
    +
    +class nibabel.nicom.structreader.Unpacker(buf, ptr=0, endian=None)
    +

    Bases: object

    +

    Class to unpack values from buffer object

    +

    The buffer object is usually a string. Caches compiled struct +format strings so that repeated unpacking with the same format +string should be faster than using struct.unpack directly.

    +

    Examples

    +
    >>> a = b'1234567890'
    +>>> upk = Unpacker(a)
    +>>> upk.unpack('2s') == (b'12',)
    +True
    +>>> upk.unpack('2s') == (b'34',)
    +True
    +>>> upk.ptr
    +4
    +>>> upk.read(3) == b'567'
    +True
    +>>> upk.ptr
    +7
    +
    +
    +

    Initialize unpacker

    +
    +
    Parameters:
    +
    +
    bufbuffer

    object implementing buffer protocol (e.g. str)

    +
    +
    ptrint, optional

    offset at which to begin reads from buf

    +
    +
    endianNone or str, optional

    endian code to prepend to format, as for unpack endian +codes. None (the default) corresponds to the default +behavior of struct - assuming system endian unless you +specify the byte order specifically in the format string +passed to unpack

    +
    +
    +
    +
    +
    +
    +__init__(buf, ptr=0, endian=None)
    +

    Initialize unpacker

    +
    +
    Parameters:
    +
    +
    bufbuffer

    object implementing buffer protocol (e.g. str)

    +
    +
    ptrint, optional

    offset at which to begin reads from buf

    +
    +
    endianNone or str, optional

    endian code to prepend to format, as for unpack endian +codes. None (the default) corresponds to the default +behavior of struct - assuming system endian unless you +specify the byte order specifically in the format string +passed to unpack

    +
    +
    +
    +
    +
    + +
    +
    +read(n_bytes=-1)
    +

    Return byte string of length n_bytes at current position

    +

    Returns sub-string from self.buf and updates self.ptr to the +position after the read data.

    +
    +
    Parameters:
    +
    +
    n_bytesint, optional

    number of bytes to read. Can be -1 (the default) in which +case we return all the remaining bytes in self.buf

    +
    +
    +
    +
    Returns:
    +
    +
    sbyte string
    +
    +
    +
    +
    + +
    +
    +unpack(fmt)
    +

    Unpack values from contained buffer

    +

    Unpacks values from self.buf and updates self.ptr to the +position after the read data.

    +
    +
    Parameters:
    +
    +
    fmtstr

    format string as for unpack

    +
    +
    +
    +
    Returns:
    +
    +
    valuestuple

    values as unpacked from self.buf according to fmt

    +
    +
    +
    +
    +
    + +
    + +
    +
    +

    Vendor

    +
    +
    +class nibabel.nicom.utils.Vendor(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None)
    +

    Bases: Enum

    +
    +
    +__init__(*args, **kwds)
    +
    + +
    +
    +GE = 2
    +
    + +
    +
    +PHILIPS = 3
    +
    + +
    +
    +SIEMENS = 1
    +
    + +
    + +
    +
    +

    find_private_section

    +
    +
    +nibabel.nicom.utils.find_private_section(dcm_data, group_no, creator)
    +

    Return start element in group group_no given creator name creator

    +

    Private attribute tags need to announce where they will go by putting a tag +in the private group (here group_no) between elements 1 and 0xFF. The +element number of these tags give the start of matching information, in the +higher tag numbers.

    +
    +
    Parameters:
    +
    +
    dcm_datadicom dataset

    Iterating over dcm_data produces elements with attributes +tag, VR, value

    +
    +
    group_noint

    Group number in which to search

    +
    +
    creatorstr or bytes or regex

    Name of section - e.g. ‘SIEMENS CSA HEADER’ - or regex to search for +section name. Regex used via creator.search(element_value) where +element_value is the value of the data element.

    +
    +
    +
    +
    Returns:
    +
    +
    element_startint

    Element number at which named section starts.

    +
    +
    +
    +
    +
    + +
    +
    +

    vendor_from_private

    +
    +
    +nibabel.nicom.utils.vendor_from_private(dcm_data)
    +

    Try to determine the vendor by looking for specific private tags

    +
    + +
    +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.nifti1.html b/reference/nibabel.nifti1.html new file mode 100644 index 0000000000..f87d38f851 --- /dev/null +++ b/reference/nibabel.nifti1.html @@ -0,0 +1,2060 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    nifti1

    +

    Read / write access to NIfTI1 image format

    +

    NIfTI1 format defined at http://nifti.nimh.nih.gov/nifti-1/

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Nifti1DicomExtension(code[, content, parent_hdr])

    NIfTI1 DICOM header extension

    Nifti1Extension(code[, content, object])

    Baseclass for NIfTI1 header extensions.

    Nifti1Extensions([iterable])

    Simple extension collection, implemented as a list-subclass.

    Nifti1Header([binaryblock, endianness, ...])

    Class for NIfTI1 header

    Nifti1Image(dataobj, affine[, header, ...])

    Class for single file NIfTI1 format image

    Nifti1Pair(dataobj, affine[, header, extra, ...])

    Class for NIfTI1 format image, header pair

    Nifti1PairHeader([binaryblock, endianness, ...])

    Class for NIfTI1 pair header

    NiftiExtension(code[, content, object])

    Base class for NIfTI header extensions.

    load(filename)

    Load NIfTI1 single or pair from filename

    save(img, filename)

    Save NIfTI1 single or pair to filename

    +
    +

    Nifti1DicomExtension

    +
    +
    +class nibabel.nifti1.Nifti1DicomExtension(code: int | str, content: bytes | Dataset | None = None, parent_hdr: Nifti1Header | None = None)
    +

    Bases: Nifti1Extension[Dataset]

    +

    NIfTI1 DICOM header extension

    +

    This class is a thin wrapper around pydicom to read a binary DICOM +byte string. If pydicom is available, content is exposed as a Dicom Dataset. +Otherwise, this silently falls back to the standard NiftiExtension class +and content is the raw bytestring loaded directly from the nifti file +header.

    +
    +
    Parameters:
    +
    +
    codeint or str

    Canonical extension code as defined in the NIfTI standard, given +either as integer or corresponding label +(see extension_codes)

    +
    +
    contentbytes or pydicom Dataset or None

    Extension content - either a bytestring as read from the NIfTI file +header or an existing pydicom Dataset. If a bystestring, the content +is converted into a Dataset on initialization. If None, a new empty +Dataset is created.

    +
    +
    parent_hdrNifti1Header, optional

    If a dicom extension belongs to an existing +Nifti1Header, it may be provided here to +ensure that the DICOM dataset is written with correctly corresponding +endianness; otherwise it is assumed the dataset is little endian.

    +
    +
    +
    +
    +

    Notes

    +

    code should always be 2 for DICOM.

    +
    +
    +__init__(code: int | str, content: bytes | Dataset | None = None, parent_hdr: Nifti1Header | None = None) None
    +
    +
    Parameters:
    +
    +
    codeint or str

    Canonical extension code as defined in the NIfTI standard, given +either as integer or corresponding label +(see extension_codes)

    +
    +
    contentbytes or pydicom Dataset or None

    Extension content - either a bytestring as read from the NIfTI file +header or an existing pydicom Dataset. If a bystestring, the content +is converted into a Dataset on initialization. If None, a new empty +Dataset is created.

    +
    +
    parent_hdrNifti1Header, optional

    If a dicom extension belongs to an existing +Nifti1Header, it may be provided here to +ensure that the DICOM dataset is written with correctly corresponding +endianness; otherwise it is assumed the dataset is little endian.

    +
    +
    +
    +
    +

    Notes

    +

    code should always be 2 for DICOM.

    +
    + +
    +
    +code: int = 2
    +
    + +
    + +
    +
    +

    Nifti1Extension

    +
    +
    +class nibabel.nifti1.Nifti1Extension(code: int | str, content: bytes = b'', object: T | None = None)
    +

    Bases: NiftiExtension[T]

    +

    Baseclass for NIfTI1 header extensions.

    +

    This class is sufficient to handle very simple text-based extensions, such +as comment. More sophisticated extensions should/will be supported by +dedicated subclasses.

    +
    +
    Parameters:
    +
    +
    codeint or str

    Canonical extension code as defined in the NIfTI standard, given +either as integer or corresponding label +(see extension_codes)

    +
    +
    contentbytes, optional

    Extension content as read from the NIfTI file header.

    +
    +
    objectoptional

    Extension content in runtime form.

    +
    +
    +
    +
    +
    +
    +__init__(code: int | str, content: bytes = b'', object: T | None = None) None
    +
    +
    Parameters:
    +
    +
    codeint or str

    Canonical extension code as defined in the NIfTI standard, given +either as integer or corresponding label +(see extension_codes)

    +
    +
    contentbytes, optional

    Extension content as read from the NIfTI file header.

    +
    +
    objectoptional

    Extension content in runtime form.

    +
    +
    +
    +
    +
    + +
    +
    +code: int = 0
    +
    + +
    + +
    +
    +

    Nifti1Extensions

    +
    +
    +class nibabel.nifti1.Nifti1Extensions(iterable=(), /)
    +

    Bases: list

    +

    Simple extension collection, implemented as a list-subclass.

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    +
    +count(ecode)
    +

    Returns the number of extensions matching a given ecode.

    +
    +
    Parameters:
    +
    +
    codeint | str

    The ecode can be specified either literal or as numerical value.

    +
    +
    +
    +
    +
    + +
    +
    +classmethod from_fileobj(fileobj, size, byteswap)
    +

    Read header extensions from a fileobj

    +
    +
    Parameters:
    +
    +
    fileobjfile-like object

    We begin reading the extensions at the current file position

    +
    +
    sizeint

    Number of bytes to read. If negative, fileobj will be read till its +end.

    +
    +
    byteswapboolean

    Flag if byteswapping the read data is required.

    +
    +
    +
    +
    Returns:
    +
    +
    An extension list. This list might be empty in case not extensions
    +
    were present in fileobj.
    +
    +
    +
    +
    + +
    +
    +get_codes()
    +

    Return a list of the extension code of all available extensions

    +
    + +
    +
    +get_sizeondisk()
    +

    Return the size of the complete header extensions in the NIfTI file.

    +
    + +
    +
    +write_to(fileobj, byteswap)
    +

    Write header extensions to fileobj

    +

    Write starts at fileobj current file position.

    +
    +
    Parameters:
    +
    +
    fileobjfile-like object

    Should implement write method

    +
    +
    byteswapboolean

    Flag if byteswapping the data is required.

    +
    +
    +
    +
    Returns:
    +
    +
    None
    +
    +
    +
    +
    + +
    + +
    +
    +

    Nifti1Header

    +
    +
    +class nibabel.nifti1.Nifti1Header(binaryblock=None, endianness=None, check=True, extensions=())
    +

    Bases: SpmAnalyzeHeader

    +

    Class for NIfTI1 header

    +

    The NIfTI1 header has many more coded fields than the simpler Analyze +variants. NIfTI1 headers also have extensions.

    +

    Nifti allows the header to be a separate file, as part of a nifti image / +header pair, or to precede the data in a single file. The object needs to +know which type it is, in order to manage the voxel offset pointing to the +data, extension reading, and writing the correct magic string.

    +

    This class handles the header-preceding-data case.

    +

    Initialize header from binary data block and extensions

    +
    +
    +__init__(binaryblock=None, endianness=None, check=True, extensions=())
    +

    Initialize header from binary data block and extensions

    +
    + +
    +
    +copy()
    +

    Return copy of header

    +

    Take reference to extensions as well as copy of header contents

    +
    + +
    +
    +classmethod default_structarr(endianness=None)
    +

    Create empty header binary block with given endianness

    +
    + +
    +
    +exts_klass
    +

    alias of Nifti1Extensions

    +
    + +
    +
    +classmethod from_fileobj(fileobj, endianness=None, check=True)
    +

    Return read structure with given or guessed endiancode

    +
    +
    Parameters:
    +
    +
    fileobjfile-like object

    Needs to implement read method

    +
    +
    endiannessNone or endian code, optional

    Code specifying endianness of read data

    +
    +
    +
    +
    Returns:
    +
    +
    wstrWrapStruct object

    WrapStruct object initialized from data in fileobj

    +
    +
    +
    +
    +
    + +
    +
    +classmethod from_header(header=None, check=True)
    +

    Class method to create header from another header

    +

    Extend Analyze header copy by copying extensions from other Nifti +types.

    +
    +
    Parameters:
    +
    +
    headerHeader instance or mapping

    a header of this class, or another class of header for +conversion to this type

    +
    +
    check{True, False}

    whether to check header for integrity

    +
    +
    +
    +
    Returns:
    +
    +
    hdrheader instance

    fresh header instance of our own class

    +
    +
    +
    +
    +
    + +
    +
    +get_best_affine()
    +

    Select best of available transforms

    +
    + +
    +
    +get_data_shape()
    +

    Get shape of data

    +

    Notes

    +

    Applies freesurfer hack for large vectors described in issue 100 and +save_nifti.m.

    +

    Allows for freesurfer hack for 7th order icosahedron surface described +in issue 309, load_nifti.m, and save_nifti.m.

    +

    Examples

    +
    >>> hdr = Nifti1Header()
    +>>> hdr.get_data_shape()
    +(0,)
    +>>> hdr.set_data_shape((1,2,3))
    +>>> hdr.get_data_shape()
    +(1, 2, 3)
    +
    +
    +

    Expanding number of dimensions gets default zooms

    +
    >>> hdr.get_zooms()
    +(1.0, 1.0, 1.0)
    +
    +
    +
    + +
    +
    +get_dim_info()
    +

    Gets NIfTI MRI slice etc dimension information

    +
    +
    Returns:
    +
    +
    freq{None,0,1,2}

    Which data array axis is frequency encode direction

    +
    +
    phase{None,0,1,2}

    Which data array axis is phase encode direction

    +
    +
    slice{None,0,1,2}

    Which data array axis is slice encode direction

    +
    +
    where data array is the array returned by get_data
    +
    Because NIfTI1 files are natively Fortran indexed:

    0 is fastest changing in file +1 is medium changing in file +2 is slowest changing in file

    +
    +
    None means the axis appears not to be specified.
    +
    +
    +
    +

    Examples

    +

    See set_dim_info function

    +
    + +
    +
    +get_intent(code_repr='label')
    +

    Get intent code, parameters and name

    +
    +
    Parameters:
    +
    +
    code_reprstring

    string giving output form of intent code representation. +Default is ‘label’; use ‘code’ for integer representation.

    +
    +
    +
    +
    Returns:
    +
    +
    codestring or integer

    intent code, or string describing code

    +
    +
    parameterstuple

    parameters for the intent

    +
    +
    namestring

    intent name

    +
    +
    +
    +
    +

    Examples

    +
    >>> hdr = Nifti1Header()
    +>>> hdr.set_intent('t test', (10,), name='some score')
    +>>> hdr.get_intent()
    +('t test', (10.0,), 'some score')
    +>>> hdr.get_intent('code')
    +(3, (10.0,), 'some score')
    +
    +
    +
    + +
    +
    +get_n_slices()
    +

    Return the number of slices

    +
    + +
    +
    +get_qform(coded=False)
    +

    Return 4x4 affine matrix from qform parameters in header

    +
    +
    Parameters:
    +
    +
    codedbool, optional

    If True, return {affine or None}, and qform code. If False, just +return affine. {affine or None} means, return None if qform code +== 0, and affine otherwise.

    +
    +
    +
    +
    Returns:
    +
    +
    affineNone or (4,4) ndarray

    If coded is False, always return affine reconstructed from qform +quaternion. If coded is True, return None if qform code is 0, +else return the affine.

    +
    +
    codeint

    Qform code. Only returned if coded is True.

    +
    +
    +
    +
    +
    + +
    +
    +get_qform_quaternion()
    +

    Compute quaternion from b, c, d of quaternion

    +

    Fills a value by assuming this is a unit quaternion

    +
    + +
    +
    +get_sform(coded=False)
    +

    Return 4x4 affine matrix from sform parameters in header

    +
    +
    Parameters:
    +
    +
    codedbool, optional

    If True, return {affine or None}, and sform code. If False, just +return affine. {affine or None} means, return None if sform code +== 0, and affine otherwise.

    +
    +
    +
    +
    Returns:
    +
    +
    affineNone or (4,4) ndarray

    If coded is False, always return affine from sform fields. If +coded is True, return None if sform code is 0, else return the +affine.

    +
    +
    codeint

    Sform code. Only returned if coded is True.

    +
    +
    +
    +
    +
    + +
    +
    +get_slice_duration()
    +

    Get slice duration

    +
    +
    Returns:
    +
    +
    slice_durationfloat

    time to acquire one slice

    +
    +
    +
    +
    +

    Notes

    +

    The NIfTI1 spec appears to require the slice dimension to be +defined for slice_duration to have meaning.

    +

    Examples

    +
    >>> hdr = Nifti1Header()
    +>>> hdr.set_dim_info(slice=2)
    +>>> hdr.set_slice_duration(0.3)
    +>>> print("%0.1f" % hdr.get_slice_duration())
    +0.3
    +
    +
    +
    + +
    +
    +get_slice_times()
    +

    Get slice times from slice timing information

    +
    +
    Returns:
    +
    +
    slice_timestuple

    Times of acquisition of slices, where 0 is the beginning of +the acquisition, ordered by position in file. nifti allows +slices at the top and bottom of the volume to be excluded from +the standard slice timing specification, and calls these +“padding slices”. We give padding slices None as a time +of acquisition

    +
    +
    +
    +
    +

    Examples

    +
    >>> hdr = Nifti1Header()
    +>>> hdr.set_dim_info(slice=2)
    +>>> hdr.set_data_shape((1, 1, 7))
    +>>> hdr.set_slice_duration(0.1)
    +>>> hdr['slice_code'] = slice_order_codes['sequential increasing']
    +>>> slice_times = hdr.get_slice_times()
    +>>> np.allclose(slice_times, [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6])
    +True
    +
    +
    +
    + +
    +
    +get_slope_inter()
    +

    Get data scaling (slope) and DC offset (intercept) from header data

    +
    +
    Returns:
    +
    +
    slopeNone or float

    scaling (slope). None if there is no valid scaling from these +fields

    +
    +
    interNone or float

    offset (intercept). None if there is no valid scaling or if offset +is not finite.

    +
    +
    +
    +
    +

    Examples

    +
    >>> hdr = Nifti1Header()
    +>>> hdr.get_slope_inter()
    +(1.0, 0.0)
    +>>> hdr['scl_slope'] = 0
    +>>> hdr.get_slope_inter()
    +(None, None)
    +>>> hdr['scl_slope'] = np.nan
    +>>> hdr.get_slope_inter()
    +(None, None)
    +>>> hdr['scl_slope'] = 1
    +>>> hdr['scl_inter'] = 1
    +>>> hdr.get_slope_inter()
    +(1.0, 1.0)
    +>>> hdr['scl_inter'] = np.inf
    +>>> hdr.get_slope_inter() 
    +Traceback (most recent call last):
    +    ...
    +HeaderDataError: Valid slope but invalid intercept inf
    +
    +
    +
    + +
    +
    +get_xyzt_units()
    +
    + +
    +
    +has_data_intercept = True
    +
    + +
    +
    +has_data_slope = True
    +
    + +
    +
    +is_single = True
    +
    + +
    +
    +classmethod may_contain_header(binaryblock)
    +
    + +
    +
    +pair_magic = b'ni1'
    +
    + +
    +
    +pair_vox_offset = 0
    +
    + +
    +
    +quaternion_threshold = 3.5762787e-07
    +
    + +
    +
    +set_data_dtype(datatype)
    +

    Set numpy dtype for data from code or dtype or type

    +

    Using int or "int" is disallowed, as these types +will be interpreted as np.int64, which is almost never desired. +np.int64 is permitted for those intent on making poor choices.

    +

    Examples

    +
    >>> hdr = Nifti1Header()
    +>>> hdr.set_data_dtype(np.uint8)
    +>>> hdr.get_data_dtype()
    +dtype('uint8')
    +>>> hdr.set_data_dtype(np.dtype(np.uint8))
    +>>> hdr.get_data_dtype()
    +dtype('uint8')
    +>>> hdr.set_data_dtype('implausible')
    +Traceback (most recent call last):
    +   ...
    +nibabel.spatialimages.HeaderDataError: data dtype "implausible" not recognized
    +>>> hdr.set_data_dtype('none')
    +Traceback (most recent call last):
    +   ...
    +nibabel.spatialimages.HeaderDataError: data dtype "none" known but not supported
    +>>> hdr.set_data_dtype(np.void)
    +Traceback (most recent call last):
    +   ...
    +nibabel.spatialimages.HeaderDataError: data dtype "<class 'numpy.void'>" known
    +but not supported
    +>>> hdr.set_data_dtype('int')
    +Traceback (most recent call last):
    +   ...
    +ValueError: Invalid data type 'int'. Specify a sized integer, e.g., 'uint8' or numpy.int16.
    +>>> hdr.set_data_dtype(int)
    +Traceback (most recent call last):
    +   ...
    +ValueError: Invalid data type <class 'int'>. Specify a sized integer, e.g., 'uint8' or
    +numpy.int16.
    +>>> hdr.set_data_dtype('int64')
    +>>> hdr.get_data_dtype() == np.dtype('int64')
    +True
    +
    +
    +
    + +
    +
    +set_data_shape(shape)
    +

    Set shape of data # noqa

    +

    If ndims == len(shape) then we set zooms for dimensions higher than +ndims to 1.0

    +

    Nifti1 images can have up to seven dimensions. For FreeSurfer-variant +Nifti surface files, the first dimension is assumed to correspond to +vertices/nodes on a surface, and dimensions two and three are +constrained to have depth of 1. Dimensions 4-7 are constrained only by +type bounds.

    +
    +
    Parameters:
    +
    +
    shapesequence

    sequence of integers specifying data array shape

    +
    +
    +
    +
    +

    Notes

    +

    Applies freesurfer hack for large vectors described in issue 100 and +save_nifti.m.

    +

    Allows for freesurfer hack for 7th order icosahedron surface described +in issue 309, load_nifti.m, and save_nifti.m.

    +

    The Nifti1 standard header allows for the following “point set” +definition of a surface, not currently implemented in nibabel.

    +
    To signify that the vector value at each voxel is really a
    +spatial coordinate (e.g., the vertices or nodes of a surface mesh):
    +  - dataset must have a 5th dimension
    +  - intent_code must be NIFTI_INTENT_POINTSET
    +  - dim[0] = 5
    +  - dim[1] = number of points
    +  - dim[2] = dim[3] = dim[4] = 1
    +  - dim[5] must be the dimensionality of space (e.g., 3 => 3D space).
    +  - intent_name may describe the object these points come from
    +    (e.g., "pial", "gray/white" , "EEG", "MEG").
    +
    +
    +
    + +
    +
    +set_dim_info(freq=None, phase=None, slice=None)
    +

    Sets nifti MRI slice etc dimension information

    +
    +
    Parameters:
    +
    +
    freq{None, 0, 1, 2}

    axis of data array referring to frequency encoding

    +
    +
    phase{None, 0, 1, 2}

    axis of data array referring to phase encoding

    +
    +
    slice{None, 0, 1, 2}

    axis of data array referring to slice encoding

    +
    +
    ``None`` means the axis is not specified.
    +
    +
    +
    +

    Notes

    +

    This is stored in one byte in the header

    +

    Examples

    +
    >>> hdr = Nifti1Header()
    +>>> hdr.set_dim_info(1, 2, 0)
    +>>> hdr.get_dim_info()
    +(1, 2, 0)
    +>>> hdr.set_dim_info(freq=1, phase=2, slice=0)
    +>>> hdr.get_dim_info()
    +(1, 2, 0)
    +>>> hdr.set_dim_info()
    +>>> hdr.get_dim_info()
    +(None, None, None)
    +>>> hdr.set_dim_info(freq=1, phase=None, slice=0)
    +>>> hdr.get_dim_info()
    +(1, None, 0)
    +
    +
    +
    + +
    +
    +set_intent(code, params=(), name='', allow_unknown=False)
    +

    Set the intent code, parameters and name

    +

    If parameters are not specified, assumed to be all zero. Each +intent code has a set number of parameters associated. If you +specify any parameters, then it will need to be the correct number +(e.g the “f test” intent requires 2). However, parameters can +also be set in the file data, so we also allow not setting any +parameters (empty parameter tuple).

    +
    +
    Parameters:
    +
    +
    codeinteger or string

    code specifying nifti intent

    +
    +
    paramslist, tuple of scalars

    parameters relating to intent (see intent_codes) +defaults to (). Unspecified parameters are set to 0.0

    +
    +
    namestring

    intent name (description). Defaults to ‘’

    +
    +
    allow_unknown{False, True}, optional

    Allow unknown integer intent codes. If False (the default), +a KeyError is raised on attempts to set the intent +to an unknown code.

    +
    +
    +
    +
    Returns:
    +
    +
    None
    +
    +
    +
    +

    Examples

    +
    >>> hdr = Nifti1Header()
    +>>> hdr.set_intent(0)  # no intent
    +>>> hdr.set_intent('z score')
    +>>> hdr.get_intent()
    +('z score', (), '')
    +>>> hdr.get_intent('code')
    +(5, (), '')
    +>>> hdr.set_intent('t test', (10,), name='some score')
    +>>> hdr.get_intent()
    +('t test', (10.0,), 'some score')
    +>>> hdr.set_intent('f test', (2, 10), name='another score')
    +>>> hdr.get_intent()
    +('f test', (2.0, 10.0), 'another score')
    +>>> hdr.set_intent('f test')
    +>>> hdr.get_intent()
    +('f test', (0.0, 0.0), '')
    +>>> hdr.set_intent(9999, allow_unknown=True) # unknown code
    +>>> hdr.get_intent()
    +('unknown code 9999', (), '')
    +
    +
    +
    + +
    +
    +set_qform(affine, code=None, strip_shears=True)
    +

    Set qform header values from 4x4 affine

    +
    +
    Parameters:
    +
    +
    affineNone or 4x4 array

    affine transform to write into sform. If None, only set code.

    +
    +
    codeNone, string or integer, optional

    String or integer giving meaning of transform in affine. +The default is None. If code is None, then:

    +
      +
    • If affine is None, code-> 0

    • +
    • If affine not None and existing qform code in header == 0, +code-> 2 (aligned)

    • +
    • If affine not None and existing qform code in header != 0, +code-> existing qform code in header

    • +
    +
    +
    strip_shearsbool, optional

    Whether to strip shears in affine. If True, shears will be +silently stripped. If False, the presence of shears will raise a +HeaderDataError

    +
    +
    +
    +
    +

    Notes

    +

    The qform transform only encodes translations, rotations and +zooms. If there are shear components to the affine transform, and +strip_shears is True (the default), the written qform gives the +closest approximation where the rotation matrix is orthogonal. This is +to allow quaternion representation. The orthogonal representation +enforces orthogonal axes.

    +

    Examples

    +
    >>> hdr = Nifti1Header()
    +>>> int(hdr['qform_code'])  # gives 0 - unknown
    +0
    +>>> affine = np.diag([1,2,3,1])
    +>>> np.all(hdr.get_qform() == affine)
    +False
    +>>> hdr.set_qform(affine)
    +>>> np.all(hdr.get_qform() == affine)
    +True
    +>>> int(hdr['qform_code'])  # gives 2 - aligned
    +2
    +>>> hdr.set_qform(affine, code='talairach')
    +>>> int(hdr['qform_code'])
    +3
    +>>> hdr.set_qform(affine, code=None)
    +>>> int(hdr['qform_code'])
    +3
    +>>> hdr.set_qform(affine, code='scanner')
    +>>> int(hdr['qform_code'])
    +1
    +>>> hdr.set_qform(None)
    +>>> int(hdr['qform_code'])
    +0
    +
    +
    +
    + +
    +
    +set_sform(affine, code=None)
    +

    Set sform transform from 4x4 affine

    +
    +
    Parameters:
    +
    +
    affineNone or 4x4 array

    affine transform to write into sform. If None, only set code

    +
    +
    codeNone, string or integer, optional

    String or integer giving meaning of transform in affine. +The default is None. If code is None, then:

    +
      +
    • If affine is None, code-> 0

    • +
    • If affine not None and existing sform code in header == 0, +code-> 2 (aligned)

    • +
    • If affine not None and existing sform code in header != 0, +code-> existing sform code in header

    • +
    +
    +
    +
    +
    +

    Examples

    +
    >>> hdr = Nifti1Header()
    +>>> int(hdr['sform_code'])  # gives 0 - unknown
    +0
    +>>> affine = np.diag([1,2,3,1])
    +>>> np.all(hdr.get_sform() == affine)
    +False
    +>>> hdr.set_sform(affine)
    +>>> np.all(hdr.get_sform() == affine)
    +True
    +>>> int(hdr['sform_code'])  # gives 2 - aligned
    +2
    +>>> hdr.set_sform(affine, code='talairach')
    +>>> int(hdr['sform_code'])
    +3
    +>>> hdr.set_sform(affine, code=None)
    +>>> int(hdr['sform_code'])
    +3
    +>>> hdr.set_sform(affine, code='scanner')
    +>>> int(hdr['sform_code'])
    +1
    +>>> hdr.set_sform(None)
    +>>> int(hdr['sform_code'])
    +0
    +
    +
    +
    + +
    +
    +set_slice_duration(duration)
    +

    Set slice duration

    +
    +
    Parameters:
    +
    +
    durationscalar

    time to acquire one slice

    +
    +
    +
    +
    +

    Examples

    +

    See get_slice_duration

    +
    + +
    +
    +set_slice_times(slice_times)
    +

    Set slice times into hdr

    +
    +
    Parameters:
    +
    +
    slice_timestuple

    tuple of slice times, one value per slice +tuple can include None to indicate no slice time for that slice

    +
    +
    +
    +
    +

    Examples

    +
    >>> hdr = Nifti1Header()
    +>>> hdr.set_dim_info(slice=2)
    +>>> hdr.set_data_shape([1, 1, 7])
    +>>> hdr.set_slice_duration(0.1)
    +>>> times = [None, 0.2, 0.4, 0.1, 0.3, 0.0, None]
    +>>> hdr.set_slice_times(times)
    +>>> hdr.get_value_label('slice_code')
    +'alternating decreasing'
    +>>> int(hdr['slice_start'])
    +1
    +>>> int(hdr['slice_end'])
    +5
    +
    +
    +
    + +
    +
    +set_slope_inter(slope, inter=None)
    +

    Set slope and / or intercept into header

    +

    Set slope and intercept for image data, such that, if the image +data is arr, then the scaled image data will be (arr * +slope) + inter

    +

    (slope, inter) of (NaN, NaN) is a signal to a containing image to +set slope, inter automatically on write.

    +
    +
    Parameters:
    +
    +
    slopeNone or float

    If None, implies slope of NaN. If slope is None or NaN then +inter should be None or NaN. Values of 0, Inf or -Inf raise +HeaderDataError

    +
    +
    interNone or float, optional

    Intercept. If None, implies inter of NaN. If slope is None or +NaN then inter should be None or NaN. Values of Inf or -Inf raise +HeaderDataError

    +
    +
    +
    +
    +
    + +
    +
    +set_xyzt_units(xyz=None, t=None)
    +
    + +
    +
    +single_magic = b'n+1'
    +
    + +
    +
    +single_vox_offset = 352
    +
    + +
    +
    +template_dtype = dtype([('sizeof_hdr', '<i4'), ('data_type', 'S10'), ('db_name', 'S18'), ('extents', '<i4'), ('session_error', '<i2'), ('regular', 'S1'), ('dim_info', 'u1'), ('dim', '<i2', (8,)), ('intent_p1', '<f4'), ('intent_p2', '<f4'), ('intent_p3', '<f4'), ('intent_code', '<i2'), ('datatype', '<i2'), ('bitpix', '<i2'), ('slice_start', '<i2'), ('pixdim', '<f4', (8,)), ('vox_offset', '<f4'), ('scl_slope', '<f4'), ('scl_inter', '<f4'), ('slice_end', '<i2'), ('slice_code', 'u1'), ('xyzt_units', 'u1'), ('cal_max', '<f4'), ('cal_min', '<f4'), ('slice_duration', '<f4'), ('toffset', '<f4'), ('glmax', '<i4'), ('glmin', '<i4'), ('descrip', 'S80'), ('aux_file', 'S24'), ('qform_code', '<i2'), ('sform_code', '<i2'), ('quatern_b', '<f4'), ('quatern_c', '<f4'), ('quatern_d', '<f4'), ('qoffset_x', '<f4'), ('qoffset_y', '<f4'), ('qoffset_z', '<f4'), ('srow_x', '<f4', (4,)), ('srow_y', '<f4', (4,)), ('srow_z', '<f4', (4,)), ('intent_name', 'S16'), ('magic', 'S4')])
    +
    + +
    +
    +write_to(fileobj)
    +

    Write structure to fileobj

    +

    Write starts at fileobj current file position.

    +
    +
    Parameters:
    +
    +
    fileobjfile-like object

    Should implement write method

    +
    +
    +
    +
    Returns:
    +
    +
    None
    +
    +
    +
    +

    Examples

    +
    >>> wstr = WrapStruct()
    +>>> from io import BytesIO
    +>>> str_io = BytesIO()
    +>>> wstr.write_to(str_io)
    +>>> wstr.binaryblock == str_io.getvalue()
    +True
    +
    +
    +
    + +
    + +
    +
    +

    Nifti1Image

    +
    +
    +class nibabel.nifti1.Nifti1Image(dataobj, affine, header=None, extra=None, file_map=None, dtype=None)
    +

    Bases: Nifti1Pair, SerializableImage

    +

    Class for single file NIfTI1 format image

    +

    Initialize image

    +

    The image is a combination of (array-like, affine matrix, header), with +optional metadata in extra, and filename / file-like objects +contained in the file_map mapping.

    +
    +
    Parameters:
    +
    +
    dataobjobject

    Object containing image data. It should be some object that returns an +array from np.asanyarray. It should have a shape attribute +or property

    +
    +
    affineNone or (4,4) array-like

    homogeneous affine giving relationship between voxel coordinates and +world coordinates. Affine can also be None. In this case, +obj.affine also returns None, and the affine as written to disk +will depend on the file format.

    +
    +
    headerNone or mapping or header instance, optional

    metadata for this image format

    +
    +
    extraNone or mapping, optional

    metadata to associate with image that cannot be stored in the +metadata of this image type

    +
    +
    file_mapmapping, optional

    mapping giving file information for this image format

    +
    +
    +
    +
    +

    Notes

    +

    If both a header and an affine are specified, and the affine does +not match the affine that is in the header, the affine will be used, +but the sform_code and qform_code fields in the header will be +re-initialised to their default values. This is performed on the basis +that, if you are changing the affine, you are likely to be changing the +space to which the affine is pointing. The set_sform() and +set_qform() methods can be used to update the codes after an image +has been created - see those methods, and the manual for more details.

    +
    +
    +__init__(dataobj, affine, header=None, extra=None, file_map=None, dtype=None)
    +

    Initialize image

    +

    The image is a combination of (array-like, affine matrix, header), with +optional metadata in extra, and filename / file-like objects +contained in the file_map mapping.

    +
    +
    Parameters:
    +
    +
    dataobjobject

    Object containing image data. It should be some object that returns an +array from np.asanyarray. It should have a shape attribute +or property

    +
    +
    affineNone or (4,4) array-like

    homogeneous affine giving relationship between voxel coordinates and +world coordinates. Affine can also be None. In this case, +obj.affine also returns None, and the affine as written to disk +will depend on the file format.

    +
    +
    headerNone or mapping or header instance, optional

    metadata for this image format

    +
    +
    extraNone or mapping, optional

    metadata to associate with image that cannot be stored in the +metadata of this image type

    +
    +
    file_mapmapping, optional

    mapping giving file information for this image format

    +
    +
    +
    +
    +

    Notes

    +

    If both a header and an affine are specified, and the affine does +not match the affine that is in the header, the affine will be used, +but the sform_code and qform_code fields in the header will be +re-initialised to their default values. This is performed on the basis +that, if you are changing the affine, you are likely to be changing the +space to which the affine is pointing. The set_sform() and +set_qform() methods can be used to update the codes after an image +has been created - see those methods, and the manual for more details.

    +
    + +
    +
    +files_types: tuple[tuple[str, str], ...] = (('image', '.nii'),)
    +
    + +
    +
    +header_class
    +

    alias of Nifti1Header

    +
    + +
    +
    +update_header()
    +

    Harmonize header with image data and affine

    +
    + +
    +
    +valid_exts: tuple[str, ...] = ('.nii',)
    +
    + +
    + +
    +
    +

    Nifti1Pair

    +
    +
    +class nibabel.nifti1.Nifti1Pair(dataobj, affine, header=None, extra=None, file_map=None, dtype=None)
    +

    Bases: AnalyzeImage

    +

    Class for NIfTI1 format image, header pair

    +

    Initialize image

    +

    The image is a combination of (array-like, affine matrix, header), with +optional metadata in extra, and filename / file-like objects +contained in the file_map mapping.

    +
    +
    Parameters:
    +
    +
    dataobjobject

    Object containing image data. It should be some object that returns an +array from np.asanyarray. It should have a shape attribute +or property

    +
    +
    affineNone or (4,4) array-like

    homogeneous affine giving relationship between voxel coordinates and +world coordinates. Affine can also be None. In this case, +obj.affine also returns None, and the affine as written to disk +will depend on the file format.

    +
    +
    headerNone or mapping or header instance, optional

    metadata for this image format

    +
    +
    extraNone or mapping, optional

    metadata to associate with image that cannot be stored in the +metadata of this image type

    +
    +
    file_mapmapping, optional

    mapping giving file information for this image format

    +
    +
    +
    +
    +

    Notes

    +

    If both a header and an affine are specified, and the affine does +not match the affine that is in the header, the affine will be used, +but the sform_code and qform_code fields in the header will be +re-initialised to their default values. This is performed on the basis +that, if you are changing the affine, you are likely to be changing the +space to which the affine is pointing. The set_sform() and +set_qform() methods can be used to update the codes after an image +has been created - see those methods, and the manual for more details.

    +
    +
    +__init__(dataobj, affine, header=None, extra=None, file_map=None, dtype=None)
    +

    Initialize image

    +

    The image is a combination of (array-like, affine matrix, header), with +optional metadata in extra, and filename / file-like objects +contained in the file_map mapping.

    +
    +
    Parameters:
    +
    +
    dataobjobject

    Object containing image data. It should be some object that returns an +array from np.asanyarray. It should have a shape attribute +or property

    +
    +
    affineNone or (4,4) array-like

    homogeneous affine giving relationship between voxel coordinates and +world coordinates. Affine can also be None. In this case, +obj.affine also returns None, and the affine as written to disk +will depend on the file format.

    +
    +
    headerNone or mapping or header instance, optional

    metadata for this image format

    +
    +
    extraNone or mapping, optional

    metadata to associate with image that cannot be stored in the +metadata of this image type

    +
    +
    file_mapmapping, optional

    mapping giving file information for this image format

    +
    +
    +
    +
    +

    Notes

    +

    If both a header and an affine are specified, and the affine does +not match the affine that is in the header, the affine will be used, +but the sform_code and qform_code fields in the header will be +re-initialised to their default values. This is performed on the basis +that, if you are changing the affine, you are likely to be changing the +space to which the affine is pointing. The set_sform() and +set_qform() methods can be used to update the codes after an image +has been created - see those methods, and the manual for more details.

    +
    + +
    +
    +as_reoriented(ornt)
    +

    Apply an orientation change and return a new image

    +

    If ornt is identity transform, return the original image, unchanged

    +
    +
    Parameters:
    +
    +
    ornt(n,2) orientation array

    orientation transform. ornt[N,1]` is flip of axis N of the +array implied by `shape`, where 1 means no flip and -1 means +flip.  For example, if ``N==0 and ornt[0,1] == -1, and +there’s an array arr of shape shape, the flip would +correspond to the effect of np.flipud(arr). ornt[:,0] is +the transpose that needs to be done to the implied array, as in +arr.transpose(ornt[:,0])

    +
    +
    +
    +
    +
    + +
    +
    +get_data_dtype(finalize=False)
    +

    Get numpy dtype for data

    +

    If set_data_dtype() has been called with an alias +and finalize is False, return the alias. +If finalize is True, determine the appropriate dtype +from the image data object and set the final dtype in the +header before returning it.

    +
    + +
    +
    +get_qform(coded=False)
    +

    Return 4x4 affine matrix from qform parameters in header

    +
    +
    Parameters:
    +
    +
    codedbool, optional

    If True, return {affine or None}, and qform code. If False, just +return affine. {affine or None} means, return None if qform code +== 0, and affine otherwise.

    +
    +
    +
    +
    Returns:
    +
    +
    affineNone or (4,4) ndarray

    If coded is False, always return affine reconstructed from qform +quaternion. If coded is True, return None if qform code is 0, +else return the affine.

    +
    +
    codeint

    Qform code. Only returned if coded is True.

    +
    +
    +
    +
    +
    +

    See also

    +
    +
    set_qform
    +
    get_sform
    +
    +
    +
    + +
    +
    +get_sform(coded=False)
    +

    Return 4x4 affine matrix from sform parameters in header

    +
    +
    Parameters:
    +
    +
    codedbool, optional

    If True, return {affine or None}, and sform code. If False, just +return affine. {affine or None} means, return None if sform code +== 0, and affine otherwise.

    +
    +
    +
    +
    Returns:
    +
    +
    affineNone or (4,4) ndarray

    If coded is False, always return affine from sform fields. If +coded is True, return None if sform code is 0, else return the +affine.

    +
    +
    codeint

    Sform code. Only returned if coded is True.

    +
    +
    +
    +
    +
    +

    See also

    +
    +
    set_sform
    +
    get_qform
    +
    +
    +
    + +
    +
    +header_class
    +

    alias of Nifti1PairHeader

    +
    + +
    +
    +rw: bool = True
    +
    + +
    +
    +set_data_dtype(datatype)
    +

    Set numpy dtype for data from code, dtype, type or alias

    +

    Using int or "int" is disallowed, as these types +will be interpreted as np.int64, which is almost never desired. +np.int64 is permitted for those intent on making poor choices.

    +

    The following aliases are defined to allow for flexible specification:

    +
    +
      +
    • 'mask' - Alias for uint8

    • +
    • 'compat' - The nearest Analyze-compatible datatype +(uint8, int16, int32, float32)

    • +
    • 'smallest' - The smallest Analyze-compatible integer +(uint8, int16, int32)

    • +
    +
    +

    Dynamic aliases are resolved when get_data_dtype() is called +with a finalize=True flag. Until then, these aliases are not +written to the header and will not persist to new images.

    +

    Examples

    +
    >>> ints = np.arange(24, dtype='i4').reshape((2,3,4))
    +
    +
    +
    >>> img = Nifti1Image(ints, np.eye(4))
    +>>> img.set_data_dtype(np.uint8)
    +>>> img.get_data_dtype()
    +dtype('uint8')
    +>>> img.set_data_dtype('mask')
    +>>> img.get_data_dtype()
    +dtype('uint8')
    +>>> img.set_data_dtype('compat')
    +>>> img.get_data_dtype()
    +'compat'
    +>>> img.get_data_dtype(finalize=True)
    +dtype('<i4')
    +>>> img.get_data_dtype()
    +dtype('<i4')
    +>>> img.set_data_dtype('smallest')
    +>>> img.get_data_dtype()
    +'smallest'
    +>>> img.get_data_dtype(finalize=True)
    +dtype('uint8')
    +>>> img.get_data_dtype()
    +dtype('uint8')
    +
    +
    +

    Note that floating point values will not be coerced to int

    +
    >>> floats = np.arange(24, dtype='f4').reshape((2,3,4))
    +>>> img = Nifti1Image(floats, np.eye(4))
    +>>> img.set_data_dtype('smallest')
    +>>> img.get_data_dtype(finalize=True)
    +Traceback (most recent call last):
    +   ...
    +ValueError: Cannot automatically cast array (of type float32) to an integer
    +type with fewer than 64 bits. Please set_data_dtype() to an explicit data type.
    +
    +
    +
    >>> arr = np.arange(1000, 1024, dtype='i4').reshape((2,3,4))
    +>>> img = Nifti1Image(arr, np.eye(4))
    +>>> img.set_data_dtype('smallest')
    +>>> img.set_data_dtype('implausible')
    +Traceback (most recent call last):
    +   ...
    +nibabel.spatialimages.HeaderDataError: data dtype "implausible" not recognized
    +>>> img.set_data_dtype('none')
    +Traceback (most recent call last):
    +   ...
    +nibabel.spatialimages.HeaderDataError: data dtype "none" known but not supported
    +>>> img.set_data_dtype(np.void)
    +Traceback (most recent call last):
    +   ...
    +nibabel.spatialimages.HeaderDataError: data dtype "<class 'numpy.void'>" known
    +but not supported
    +>>> img.set_data_dtype('int')
    +Traceback (most recent call last):
    +   ...
    +ValueError: Invalid data type 'int'. Specify a sized integer, e.g., 'uint8' or numpy.int16.
    +>>> img.set_data_dtype(int)
    +Traceback (most recent call last):
    +   ...
    +ValueError: Invalid data type <class 'int'>. Specify a sized integer, e.g., 'uint8' or
    +numpy.int16.
    +>>> img.set_data_dtype('int64')
    +>>> img.get_data_dtype() == np.dtype('int64')
    +True
    +
    +
    +
    + +
    +
    +set_qform(affine, code=None, strip_shears=True, **kwargs)
    +

    Set qform header values from 4x4 affine

    +
    +
    Parameters:
    +
    +
    affineNone or 4x4 array

    affine transform to write into sform. If None, only set code.

    +
    +
    codeNone, string or integer

    String or integer giving meaning of transform in affine. +The default is None. If code is None, then:

    +
      +
    • If affine is None, code-> 0

    • +
    • If affine not None and existing qform code in header == 0, +code-> 2 (aligned)

    • +
    • If affine not None and existing qform code in header != 0, +code-> existing qform code in header

    • +
    +
    +
    strip_shearsbool, optional

    Whether to strip shears in affine. If True, shears will be +silently stripped. If False, the presence of shears will raise a +HeaderDataError

    +
    +
    update_affinebool, optional

    Whether to update the image affine from the header best affine +after setting the qform. Must be keyword argument (because of +different position in set_qform). Default is True

    +
    +
    +
    +
    +
    +

    See also

    +
    +
    get_qform
    +
    set_sform
    +
    +
    +

    Examples

    +
    >>> data = np.arange(24, dtype='f4').reshape((2,3,4))
    +>>> aff = np.diag([2, 3, 4, 1])
    +>>> img = Nifti1Pair(data, aff)
    +>>> img.get_qform()
    +array([[2., 0., 0., 0.],
    +       [0., 3., 0., 0.],
    +       [0., 0., 4., 0.],
    +       [0., 0., 0., 1.]])
    +>>> img.get_qform(coded=True)
    +(None, 0)
    +>>> aff2 = np.diag([3, 4, 5, 1])
    +>>> img.set_qform(aff2, 'talairach')
    +>>> qaff, code = img.get_qform(coded=True)
    +>>> np.all(qaff == aff2)
    +True
    +>>> int(code)
    +3
    +
    +
    +
    + +
    +
    +set_sform(affine, code=None, **kwargs)
    +

    Set sform transform from 4x4 affine

    +
    +
    Parameters:
    +
    +
    affineNone or 4x4 array

    affine transform to write into sform. If None, only set code

    +
    +
    codeNone, string or integer

    String or integer giving meaning of transform in affine. +The default is None. If code is None, then:

    +
      +
    • If affine is None, code-> 0

    • +
    • If affine not None and existing sform code in header == 0, +code-> 2 (aligned)

    • +
    • If affine not None and existing sform code in header != 0, +code-> existing sform code in header

    • +
    +
    +
    update_affinebool, optional

    Whether to update the image affine from the header best affine +after setting the qform. Must be keyword argument (because of +different position in set_qform). Default is True

    +
    +
    +
    +
    +
    +

    See also

    +
    +
    get_sform
    +
    set_qform
    +
    +
    +

    Examples

    +
    >>> data = np.arange(24, dtype='f4').reshape((2,3,4))
    +>>> aff = np.diag([2, 3, 4, 1])
    +>>> img = Nifti1Pair(data, aff)
    +>>> img.get_sform()
    +array([[2., 0., 0., 0.],
    +       [0., 3., 0., 0.],
    +       [0., 0., 4., 0.],
    +       [0., 0., 0., 1.]])
    +>>> saff, code = img.get_sform(coded=True)
    +>>> saff
    +array([[2., 0., 0., 0.],
    +       [0., 3., 0., 0.],
    +       [0., 0., 4., 0.],
    +       [0., 0., 0., 1.]])
    +>>> int(code)
    +2
    +>>> aff2 = np.diag([3, 4, 5, 1])
    +>>> img.set_sform(aff2, 'talairach')
    +>>> saff, code = img.get_sform(coded=True)
    +>>> np.all(saff == aff2)
    +True
    +>>> int(code)
    +3
    +
    +
    +
    + +
    +
    +to_file_map(file_map=None, dtype=None)
    +

    Write image to file_map or contained self.file_map

    +
    +
    Parameters:
    +
    +
    file_mapNone or mapping, optional

    files mapping. If None (default) use object’s file_map +attribute instead

    +
    +
    dtypedtype-like, optional

    The on-disk data type to coerce the data array.

    +
    +
    +
    +
    +
    + +
    +
    +update_header()
    +

    Harmonize header with image data and affine

    +

    See AnalyzeImage.update_header for more examples

    +

    Examples

    +
    >>> data = np.zeros((2,3,4))
    +>>> affine = np.diag([1.0,2.0,3.0,1.0])
    +>>> img = Nifti1Image(data, affine)
    +>>> hdr = img.header
    +>>> np.all(hdr.get_qform() == affine)
    +True
    +>>> np.all(hdr.get_sform() == affine)
    +True
    +
    +
    +
    + +
    + +
    +
    +

    Nifti1PairHeader

    +
    +
    +class nibabel.nifti1.Nifti1PairHeader(binaryblock=None, endianness=None, check=True, extensions=())
    +

    Bases: Nifti1Header

    +

    Class for NIfTI1 pair header

    +

    Initialize header from binary data block and extensions

    +
    +
    +__init__(binaryblock=None, endianness=None, check=True, extensions=())
    +

    Initialize header from binary data block and extensions

    +
    + +
    +
    +is_single = False
    +
    + +
    + +
    +
    +

    NiftiExtension

    +
    +
    +class nibabel.nifti1.NiftiExtension(code: int | str, content: bytes = b'', object: T | None = None)
    +

    Bases: Generic[T]

    +

    Base class for NIfTI header extensions.

    +

    This class provides access to the extension content in various forms. +For simple extensions that expose data as bytes, text or JSON, this class +is sufficient. More complex extensions should be implemented as subclasses +that provide custom serialization/deserialization methods.

    +

    Efficiency note:

    +

    This class assumes that the runtime representation of the extension content +is mutable. Once a runtime representation is set, it is cached and will be +serialized on any attempt to access the extension content as bytes, including +determining the size of the extension in the NIfTI file.

    +

    If the runtime representation is never accessed, the raw bytes will be used +without modification. While avoiding unnecessary deserialization, if there +are bytestrings that do not produce a valid runtime representation, they will +be written as-is, and may cause errors downstream.

    +
    +
    Parameters:
    +
    +
    codeint or str

    Canonical extension code as defined in the NIfTI standard, given +either as integer or corresponding label +(see extension_codes)

    +
    +
    contentbytes, optional

    Extension content as read from the NIfTI file header.

    +
    +
    objectoptional

    Extension content in runtime form.

    +
    +
    +
    +
    +
    +
    +__init__(code: int | str, content: bytes = b'', object: T | None = None) None
    +
    +
    Parameters:
    +
    +
    codeint or str

    Canonical extension code as defined in the NIfTI standard, given +either as integer or corresponding label +(see extension_codes)

    +
    +
    contentbytes, optional

    Extension content as read from the NIfTI file header.

    +
    +
    objectoptional

    Extension content in runtime form.

    +
    +
    +
    +
    +
    + +
    +
    +code: int
    +
    + +
    +
    +property content: bytes
    +

    Return the extension content as raw bytes.

    +
    + +
    +
    +encoding: str | None = None
    +
    + +
    +
    +classmethod from_bytes(content: bytes) Self
    +

    Create an extension from raw bytes.

    +

    This constructor may only be used in extension classes with a class +attribute code to indicate the extension type.

    +
    + +
    +
    +classmethod from_object(obj: T) Self
    +

    Create an extension from a runtime object.

    +

    This constructor may only be used in extension classes with a class +attribute code to indicate the extension type.

    +
    + +
    +
    +get_code()
    +

    Return the canonical extension type code.

    +
    + +
    +
    +get_content() T
    +

    Return the extension content in its runtime representation.

    +

    This method may return a different type for each extension type. +For simple use cases, consider using .content, .text or .json() +instead.

    +
    + +
    +
    +get_object() T
    +

    Return the extension content in its runtime representation.

    +

    This method may return a different type for each extension type. +For simple use cases, consider using .content, .text or .json() +instead.

    +
    + +
    +
    +get_sizeondisk() int
    +

    Return the size of the extension in the NIfTI file.

    +
    + +
    +
    +json() Any
    +

    Attempt to decode the extension content as JSON.

    +

    If the content is not valid JSON, a JSONDecodeError or UnicodeDecodeError +will be raised.

    +
    + +
    +
    +property text: str
    +

    Attempt to decode the extension content as text.

    +

    The encoding is determined by the encoding attribute, which may be +set by the user or subclass. If not set, the default encoding is ‘utf-8’.

    +
    + +
    +
    +write_to(fileobj: BinaryIO, byteswap: bool = False) None
    +

    Write header extensions to fileobj

    +

    Write starts at fileobj current file position.

    +
    +
    Parameters:
    +
    +
    fileobjfile-like object

    Should implement write method

    +
    +
    byteswapboolean

    Flag if byteswapping the data is required.

    +
    +
    +
    +
    Returns:
    +
    +
    None
    +
    +
    +
    +
    + +
    + +
    +
    +

    load

    +
    +
    +nibabel.nifti1.load(filename)
    +

    Load NIfTI1 single or pair from filename

    +
    +
    Parameters:
    +
    +
    filenamestr

    filename of image to be loaded

    +
    +
    +
    +
    Returns:
    +
    +
    imgNifti1Image or Nifti1Pair

    NIfTI1 single or pair image instance

    +
    +
    +
    +
    Raises:
    +
    +
    ImageFileError

    if filename doesn’t look like NIfTI1;

    +
    +
    OSError

    if filename does not exist.

    +
    +
    +
    +
    +
    + +
    +
    +

    save

    +
    +
    +nibabel.nifti1.save(img, filename)
    +

    Save NIfTI1 single or pair to filename

    +
    +
    Parameters:
    +
    +
    filenamestr

    filename to which to save image

    +
    +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.nifti2.html b/reference/nibabel.nifti2.html new file mode 100644 index 0000000000..044afbbbe4 --- /dev/null +++ b/reference/nibabel.nifti2.html @@ -0,0 +1,556 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    nifti2

    +

    Read / write access to NIfTI2 image format

    +

    Format described here:

    +
    +
    + + + + + + + + + + + + + + + + + + + + + +

    Nifti2Header([binaryblock, endianness, ...])

    Class for NIfTI2 header

    Nifti2Image(dataobj, affine[, header, ...])

    Class for single file NIfTI2 format image

    Nifti2Pair(dataobj, affine[, header, extra, ...])

    Class for NIfTI2 format image, header pair

    Nifti2PairHeader([binaryblock, endianness, ...])

    Class for NIfTI2 pair header

    load(filename)

    Load NIfTI2 single or pair image from filename

    save(img, filename)

    Save NIfTI2 single or pair to filename

    +
    +

    Nifti2Header

    +
    +
    +class nibabel.nifti2.Nifti2Header(binaryblock=None, endianness=None, check=True, extensions=())
    +

    Bases: Nifti1Header

    +

    Class for NIfTI2 header

    +

    NIfTI2 is a slightly simplified variant of NIfTI1 which replaces 32-bit +floats with 64-bit floats, and increases some integer widths to 32 or 64 +bits.

    +

    Initialize header from binary data block and extensions

    +
    +
    +__init__(binaryblock=None, endianness=None, check=True, extensions=())
    +

    Initialize header from binary data block and extensions

    +
    + +
    +
    +classmethod default_structarr(endianness=None)
    +

    Create empty header binary block with given endianness

    +
    + +
    +
    +get_data_shape()
    +

    Get shape of data

    +

    Notes

    +

    Does not use Nifti1 freesurfer hack for large vectors described in +Nifti1Header.set_data_shape()

    +

    Examples

    +
    >>> hdr = Nifti2Header()
    +>>> hdr.get_data_shape()
    +(0,)
    +>>> hdr.set_data_shape((1,2,3))
    +>>> hdr.get_data_shape()
    +(1, 2, 3)
    +
    +
    +

    Expanding number of dimensions gets default zooms

    +
    >>> hdr.get_zooms()
    +(1.0, 1.0, 1.0)
    +
    +
    +
    + +
    +
    +classmethod may_contain_header(binaryblock)
    +
    + +
    +
    +pair_magic = b'ni2'
    +
    + +
    +
    +pair_vox_offset = 0
    +
    + +
    +
    +quaternion_threshold = -6.661338147750939e-16
    +
    + +
    +
    +set_data_shape(shape)
    +

    Set shape of data

    +

    If ndims == len(shape) then we set zooms for dimensions higher than +ndims to 1.0

    +
    +
    Parameters:
    +
    +
    shapesequence

    sequence of integers specifying data array shape

    +
    +
    +
    +
    +

    Notes

    +

    Does not apply nifti1 Freesurfer hack for long vectors (see +Nifti1Header.set_data_shape())

    +
    + +
    +
    +single_magic = b'n+2'
    +
    + +
    +
    +single_vox_offset = 544
    +
    + +
    +
    +sizeof_hdr = 540
    +
    + +
    +
    +template_dtype = dtype([('sizeof_hdr', '<i4'), ('magic', 'S4'), ('eol_check', 'i1', (4,)), ('datatype', '<i2'), ('bitpix', '<i2'), ('dim', '<i8', (8,)), ('intent_p1', '<f8'), ('intent_p2', '<f8'), ('intent_p3', '<f8'), ('pixdim', '<f8', (8,)), ('vox_offset', '<i8'), ('scl_slope', '<f8'), ('scl_inter', '<f8'), ('cal_max', '<f8'), ('cal_min', '<f8'), ('slice_duration', '<f8'), ('toffset', '<f8'), ('slice_start', '<i8'), ('slice_end', '<i8'), ('descrip', 'S80'), ('aux_file', 'S24'), ('qform_code', '<i4'), ('sform_code', '<i4'), ('quatern_b', '<f8'), ('quatern_c', '<f8'), ('quatern_d', '<f8'), ('qoffset_x', '<f8'), ('qoffset_y', '<f8'), ('qoffset_z', '<f8'), ('srow_x', '<f8', (4,)), ('srow_y', '<f8', (4,)), ('srow_z', '<f8', (4,)), ('slice_code', '<i4'), ('xyzt_units', '<i4'), ('intent_code', '<i4'), ('intent_name', 'S16'), ('dim_info', 'u1'), ('unused_str', 'S15')])
    +
    + +
    + +
    +
    +

    Nifti2Image

    +
    +
    +class nibabel.nifti2.Nifti2Image(dataobj, affine, header=None, extra=None, file_map=None, dtype=None)
    +

    Bases: Nifti1Image

    +

    Class for single file NIfTI2 format image

    +

    Initialize image

    +

    The image is a combination of (array-like, affine matrix, header), with +optional metadata in extra, and filename / file-like objects +contained in the file_map mapping.

    +
    +
    Parameters:
    +
    +
    dataobjobject

    Object containing image data. It should be some object that returns an +array from np.asanyarray. It should have a shape attribute +or property

    +
    +
    affineNone or (4,4) array-like

    homogeneous affine giving relationship between voxel coordinates and +world coordinates. Affine can also be None. In this case, +obj.affine also returns None, and the affine as written to disk +will depend on the file format.

    +
    +
    headerNone or mapping or header instance, optional

    metadata for this image format

    +
    +
    extraNone or mapping, optional

    metadata to associate with image that cannot be stored in the +metadata of this image type

    +
    +
    file_mapmapping, optional

    mapping giving file information for this image format

    +
    +
    +
    +
    +

    Notes

    +

    If both a header and an affine are specified, and the affine does +not match the affine that is in the header, the affine will be used, +but the sform_code and qform_code fields in the header will be +re-initialised to their default values. This is performed on the basis +that, if you are changing the affine, you are likely to be changing the +space to which the affine is pointing. The set_sform() and +set_qform() methods can be used to update the codes after an image +has been created - see those methods, and the manual for more details.

    +
    +
    +__init__(dataobj, affine, header=None, extra=None, file_map=None, dtype=None)
    +

    Initialize image

    +

    The image is a combination of (array-like, affine matrix, header), with +optional metadata in extra, and filename / file-like objects +contained in the file_map mapping.

    +
    +
    Parameters:
    +
    +
    dataobjobject

    Object containing image data. It should be some object that returns an +array from np.asanyarray. It should have a shape attribute +or property

    +
    +
    affineNone or (4,4) array-like

    homogeneous affine giving relationship between voxel coordinates and +world coordinates. Affine can also be None. In this case, +obj.affine also returns None, and the affine as written to disk +will depend on the file format.

    +
    +
    headerNone or mapping or header instance, optional

    metadata for this image format

    +
    +
    extraNone or mapping, optional

    metadata to associate with image that cannot be stored in the +metadata of this image type

    +
    +
    file_mapmapping, optional

    mapping giving file information for this image format

    +
    +
    +
    +
    +

    Notes

    +

    If both a header and an affine are specified, and the affine does +not match the affine that is in the header, the affine will be used, +but the sform_code and qform_code fields in the header will be +re-initialised to their default values. This is performed on the basis +that, if you are changing the affine, you are likely to be changing the +space to which the affine is pointing. The set_sform() and +set_qform() methods can be used to update the codes after an image +has been created - see those methods, and the manual for more details.

    +
    + +
    +
    +header_class
    +

    alias of Nifti2Header

    +
    + +
    + +
    +
    +

    Nifti2Pair

    +
    +
    +class nibabel.nifti2.Nifti2Pair(dataobj, affine, header=None, extra=None, file_map=None, dtype=None)
    +

    Bases: Nifti1Pair

    +

    Class for NIfTI2 format image, header pair

    +

    Initialize image

    +

    The image is a combination of (array-like, affine matrix, header), with +optional metadata in extra, and filename / file-like objects +contained in the file_map mapping.

    +
    +
    Parameters:
    +
    +
    dataobjobject

    Object containing image data. It should be some object that returns an +array from np.asanyarray. It should have a shape attribute +or property

    +
    +
    affineNone or (4,4) array-like

    homogeneous affine giving relationship between voxel coordinates and +world coordinates. Affine can also be None. In this case, +obj.affine also returns None, and the affine as written to disk +will depend on the file format.

    +
    +
    headerNone or mapping or header instance, optional

    metadata for this image format

    +
    +
    extraNone or mapping, optional

    metadata to associate with image that cannot be stored in the +metadata of this image type

    +
    +
    file_mapmapping, optional

    mapping giving file information for this image format

    +
    +
    +
    +
    +

    Notes

    +

    If both a header and an affine are specified, and the affine does +not match the affine that is in the header, the affine will be used, +but the sform_code and qform_code fields in the header will be +re-initialised to their default values. This is performed on the basis +that, if you are changing the affine, you are likely to be changing the +space to which the affine is pointing. The set_sform() and +set_qform() methods can be used to update the codes after an image +has been created - see those methods, and the manual for more details.

    +
    +
    +__init__(dataobj, affine, header=None, extra=None, file_map=None, dtype=None)
    +

    Initialize image

    +

    The image is a combination of (array-like, affine matrix, header), with +optional metadata in extra, and filename / file-like objects +contained in the file_map mapping.

    +
    +
    Parameters:
    +
    +
    dataobjobject

    Object containing image data. It should be some object that returns an +array from np.asanyarray. It should have a shape attribute +or property

    +
    +
    affineNone or (4,4) array-like

    homogeneous affine giving relationship between voxel coordinates and +world coordinates. Affine can also be None. In this case, +obj.affine also returns None, and the affine as written to disk +will depend on the file format.

    +
    +
    headerNone or mapping or header instance, optional

    metadata for this image format

    +
    +
    extraNone or mapping, optional

    metadata to associate with image that cannot be stored in the +metadata of this image type

    +
    +
    file_mapmapping, optional

    mapping giving file information for this image format

    +
    +
    +
    +
    +

    Notes

    +

    If both a header and an affine are specified, and the affine does +not match the affine that is in the header, the affine will be used, +but the sform_code and qform_code fields in the header will be +re-initialised to their default values. This is performed on the basis +that, if you are changing the affine, you are likely to be changing the +space to which the affine is pointing. The set_sform() and +set_qform() methods can be used to update the codes after an image +has been created - see those methods, and the manual for more details.

    +
    + +
    +
    +header_class
    +

    alias of Nifti2PairHeader

    +
    + +
    + +
    +
    +

    Nifti2PairHeader

    +
    +
    +class nibabel.nifti2.Nifti2PairHeader(binaryblock=None, endianness=None, check=True, extensions=())
    +

    Bases: Nifti2Header

    +

    Class for NIfTI2 pair header

    +

    Initialize header from binary data block and extensions

    +
    +
    +__init__(binaryblock=None, endianness=None, check=True, extensions=())
    +

    Initialize header from binary data block and extensions

    +
    + +
    +
    +is_single = False
    +
    + +
    + +
    +
    +

    load

    +
    +
    +nibabel.nifti2.load(filename)
    +

    Load NIfTI2 single or pair image from filename

    +
    +
    Parameters:
    +
    +
    filenamestr

    filename of image to be loaded

    +
    +
    +
    +
    Returns:
    +
    +
    imgNifti2Image or Nifti2Pair

    nifti2 single or pair image instance

    +
    +
    +
    +
    Raises:
    +
    +
    ImageFileError

    if filename doesn’t look like nifti2;

    +
    +
    OSError

    if filename does not exist.

    +
    +
    +
    +
    +
    + +
    +
    +

    save

    +
    +
    +nibabel.nifti2.save(img, filename)
    +

    Save NIfTI2 single or pair to filename

    +
    +
    Parameters:
    +
    +
    filenamestr

    filename to which to save image

    +
    +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.onetime.html b/reference/nibabel.onetime.html new file mode 100644 index 0000000000..5d2287e110 --- /dev/null +++ b/reference/nibabel.onetime.html @@ -0,0 +1,240 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    onetime

    +

    Descriptor support for NIPY

    +

    Utilities to support special Python descriptors [1,2], in particular +cached_property(), which has been available in the Python +standard library since Python 3.8. We currently maintain aliases from +earlier names for this descriptor, specifically OneTimeProperty and auto_attr.

    +

    cached_property() creates properties that are computed once +and then stored as regular attributes. They can thus be evaluated +later in the object’s life cycle, but once evaluated they become normal, static +attributes with no function call overhead on access or any other constraints.

    +

    A special ResetMixin class is provided to add a .reset() method to users who +may want to have their objects capable of resetting these computed properties +to their ‘untriggered’ state.

    +
    +

    References

    +

    [1] How-To Guide for Descriptors, Raymond +Hettinger. https://docs.python.org/howto/descriptor.html

    +

    [2] Python data model, https://docs.python.org/reference/datamodel.html

    +
    + + + + + + +

    ResetMixin()

    A Mixin class to add a .reset() method to users of cached_property.

    +
    +

    ResetMixin

    +
    +
    +class nibabel.onetime.ResetMixin
    +

    Bases: object

    +

    A Mixin class to add a .reset() method to users of cached_property.

    +

    By default, cached properties, once computed, become static. If they happen +to depend on other parts of an object and those parts change, their values +may now be invalid.

    +

    This class offers a .reset() method that users can call explicitly when +they know the state of their objects may have changed and they want to +ensure that all their special attributes should be invalidated. Once +reset() is called, all their cached properties are reset to their +cached_property() descriptors, +and their accessor functions will be triggered again.

    +
    +

    Warning

    +

    If a class has a set of attributes that are cached_property, but that +can be initialized from any one of them, do NOT use this mixin! For +instance, UniformTimeSeries can be initialized with only sampling_rate +and t0, sampling_interval and time are auto-computed. But if you were +to reset() a UniformTimeSeries, it would lose all 4, and there would be +then no way to break the circular dependency chains.

    +

    If this becomes a problem in practice (for our analyzer objects it +isn’t, as they don’t have the above pattern), we can extend reset() to +check for a _no_reset set of names in the instance which are meant to be +kept protected. But for now this is NOT done, so caveat emptor.

    +
    +

    Examples

    +
    >>> class A(ResetMixin):
    +...     def __init__(self,x=1.0):
    +...         self.x = x
    +...
    +...     @cached_property
    +...     def y(self):
    +...         print('*** y computation executed ***')
    +...         return self.x / 2.0
    +
    +
    +
    >>> a = A(10)
    +
    +
    +

    About to access y twice, the second time no computation is done:

    +
    >>> a.y
    +*** y computation executed ***
    +5.0
    +>>> a.y
    +5.0
    +
    +
    +

    Changing x

    +
    >>> a.x = 20
    +
    +
    +

    a.y doesn’t change to 10, since it is a static attribute:

    +
    >>> a.y
    +5.0
    +
    +
    +

    We now reset a, and this will then force all auto attributes to recompute +the next time we access them:

    +
    >>> a.reset()
    +
    +
    +

    About to access y twice again after reset():

    +
    >>> a.y
    +*** y computation executed ***
    +10.0
    +>>> a.y
    +10.0
    +
    +
    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    +
    +reset() None
    +

    Reset all cached_property attributes that may have fired already.

    +
    + +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.openers.html b/reference/nibabel.openers.html new file mode 100644 index 0000000000..1feb57eeb7 --- /dev/null +++ b/reference/nibabel.openers.html @@ -0,0 +1,449 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    openers

    +

    Context manager openers for various fileobject types

    + + + + + + + + + + + + + + + +

    DeterministicGzipFile([filename, mode, ...])

    Deterministic variant of GzipFile

    Fileish(*args, **kwargs)

    ImageOpener(fileish, *args, **kwargs)

    Opener-type class to collect extra compressed extensions

    Opener(fileish, *args, **kwargs)

    Class to accept, maybe open, and context-manage file-likes / filenames

    +
    +

    DeterministicGzipFile

    +
    +
    +class nibabel.openers.DeterministicGzipFile(filename: str | None = None, mode: Mode | None = None, compresslevel: int = 9, fileobj: io.FileIO | None = None, mtime: int = 0)
    +

    Bases: GzipFile

    +

    Deterministic variant of GzipFile

    +

    This writer does not add filename information to the header, and defaults +to a modification time (mtime) of 0 seconds.

    +

    Constructor for the GzipFile class.

    +

    At least one of fileobj and filename must be given a +non-trivial value.

    +

    The new class instance is based on fileobj, which can be a regular +file, an io.BytesIO object, or any other object which simulates a file. +It defaults to None, in which case filename is opened to provide +a file object.

    +

    When fileobj is not None, the filename argument is only used to be +included in the gzip file header, which may include the original +filename of the uncompressed file. It defaults to the filename of +fileobj, if discernible; otherwise, it defaults to the empty string, +and in this case the original filename is not included in the header.

    +

    The mode argument can be any of ‘r’, ‘rb’, ‘a’, ‘ab’, ‘w’, ‘wb’, ‘x’, or +‘xb’ depending on whether the file will be read or written. The default +is the mode of fileobj if discernible; otherwise, the default is ‘rb’. +A mode of ‘r’ is equivalent to one of ‘rb’, and similarly for ‘w’ and +‘wb’, ‘a’ and ‘ab’, and ‘x’ and ‘xb’.

    +

    The compresslevel argument is an integer from 0 to 9 controlling the +level of compression; 1 is fastest and produces the least compression, +and 9 is slowest and produces the most compression. 0 is no compression +at all. The default is 9.

    +

    The mtime argument is an optional numeric timestamp to be written +to the last modification time field in the stream when compressing. +If omitted or None, the current time is used.

    +
    +
    +__init__(filename: str | None = None, mode: Mode | None = None, compresslevel: int = 9, fileobj: io.FileIO | None = None, mtime: int = 0)
    +

    Constructor for the GzipFile class.

    +

    At least one of fileobj and filename must be given a +non-trivial value.

    +

    The new class instance is based on fileobj, which can be a regular +file, an io.BytesIO object, or any other object which simulates a file. +It defaults to None, in which case filename is opened to provide +a file object.

    +

    When fileobj is not None, the filename argument is only used to be +included in the gzip file header, which may include the original +filename of the uncompressed file. It defaults to the filename of +fileobj, if discernible; otherwise, it defaults to the empty string, +and in this case the original filename is not included in the header.

    +

    The mode argument can be any of ‘r’, ‘rb’, ‘a’, ‘ab’, ‘w’, ‘wb’, ‘x’, or +‘xb’ depending on whether the file will be read or written. The default +is the mode of fileobj if discernible; otherwise, the default is ‘rb’. +A mode of ‘r’ is equivalent to one of ‘rb’, and similarly for ‘w’ and +‘wb’, ‘a’ and ‘ab’, and ‘x’ and ‘xb’.

    +

    The compresslevel argument is an integer from 0 to 9 controlling the +level of compression; 1 is fastest and produces the least compression, +and 9 is slowest and produces the most compression. 0 is no compression +at all. The default is 9.

    +

    The mtime argument is an optional numeric timestamp to be written +to the last modification time field in the stream when compressing. +If omitted or None, the current time is used.

    +
    + +
    + +
    +
    +

    Fileish

    +
    +
    +class nibabel.openers.Fileish(*args, **kwargs)
    +

    Bases: Protocol

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    +
    +read(size: int = -1, /) bytes
    +
    + +
    +
    +write(b: bytes, /) int | None
    +
    + +
    + +
    +
    +

    ImageOpener

    +
    +
    +class nibabel.openers.ImageOpener(fileish: str | IOBase, *args, **kwargs)
    +

    Bases: Opener

    +

    Opener-type class to collect extra compressed extensions

    +

    A trivial sub-class of opener to which image classes can add extra +extensions with custom openers, such as compressed openers.

    +

    To add an extension, add a line to the class definition (not __init__):

    +
    +

    ImageOpener.compress_ext_map[ext] = func_def

    +
    +

    ext is a file extension beginning with ‘.’ and should be included in +the image class’s valid_exts tuple.

    +

    func_def is a (function, (args,)) tuple, where function accepts a +filename as the first parameter, and `args defines the other arguments +that function accepts. These arguments must be any (unordered) subset of +mode, compresslevel, and buffering.

    +
    +
    +__init__(fileish: str | IOBase, *args, **kwargs)
    +
    + +
    +
    +compress_ext_map: dict[str | None, OpenerDef] = {'.bz2': (<class 'bz2.BZ2File'>, ('mode', 'buffering', 'compresslevel')), '.gz': (<function _gzip_open>, ('mode', 'compresslevel', 'mtime', 'keep_open')), '.mgz': (<function _gzip_open>, ('mode', 'compresslevel', 'mtime', 'keep_open')), '.zst': (<function _zstd_open>, ('mode', 'level_or_option', 'zstd_dict')), None: (<built-in function open>, ('mode', 'buffering'))}
    +
    + +
    + +
    +
    +

    Opener

    +
    +
    +class nibabel.openers.Opener(fileish: str | IOBase, *args, **kwargs)
    +

    Bases: object

    +

    Class to accept, maybe open, and context-manage file-likes / filenames

    +

    Provides context manager to close files that the constructor opened for +you.

    +
    +
    Parameters:
    +
    +
    fileishstr or file-like

    if str, then open with suitable opening method. If file-like, accept as +is

    +
    +
    *argspositional arguments

    passed to opening method when fileish is str. mode, if not +specified, is rb. compresslevel, if relevant, and not specified, +is set from class variable default_compresslevel. keep_open, if +relevant, and not specified, is False.

    +
    +
    **kwargskeyword arguments

    passed to opening method when fileish is str. Change of defaults as +for *args

    +
    +
    +
    +
    +
    +
    +__init__(fileish: str | IOBase, *args, **kwargs)
    +
    + +
    +
    +bz2_def = (<class 'bz2.BZ2File'>, ('mode', 'buffering', 'compresslevel'))
    +
    + +
    +
    +close() None
    +
    + +
    +
    +close_if_mine() None
    +

    Close self.fobj iff we opened it in the constructor

    +
    + +
    +
    +property closed: bool
    +
    + +
    +
    +compress_ext_icase: bool = True
    +

    whether to ignore case looking for compression extensions

    +
    + +
    +
    +compress_ext_map: dict[str | None, OpenerDef] = {'.bz2': (<class 'bz2.BZ2File'>, ('mode', 'buffering', 'compresslevel')), '.gz': (<function _gzip_open>, ('mode', 'compresslevel', 'mtime', 'keep_open')), '.zst': (<function _zstd_open>, ('mode', 'level_or_option', 'zstd_dict')), None: (<built-in function open>, ('mode', 'buffering'))}
    +
    + +
    +
    +default_compresslevel = 1
    +

    default compression level when writing gz and bz2 files

    +
    + +
    +
    +default_level_or_option = {'r': None, 'rb': None, 'w': 3, 'wb': 3}
    +
    + +
    +
    +default_zst_compresslevel = 3
    +

    default option for zst files

    +
    + +
    +
    +fileno() int
    +
    + +
    +
    +fobj: io.IOBase
    +
    + +
    +
    +gz_def = (<function _gzip_open>, ('mode', 'compresslevel', 'mtime', 'keep_open'))
    +
    + +
    +
    +property mode: str
    +
    + +
    +
    +property name: str | None
    +

    Return self.fobj.name or self._name if not present

    +

    self._name will be None if object was created with a fileobj, otherwise +it will be the filename.

    +
    + +
    +
    +read(size: int = -1, /) bytes
    +
    + +
    +
    +readinto(buffer: WriteableBuffer, /) int | None
    +
    + +
    +
    +seek(pos: int, whence: int = 0, /) int
    +
    + +
    +
    +tell() int
    +
    + +
    +
    +write(b: bytes, /) int | None
    +
    + +
    +
    +zstd_def = (<function _zstd_open>, ('mode', 'level_or_option', 'zstd_dict'))
    +
    + +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.optpkg.html b/reference/nibabel.optpkg.html new file mode 100644 index 0000000000..19fb493096 --- /dev/null +++ b/reference/nibabel.optpkg.html @@ -0,0 +1,202 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    optpkg

    +

    Routines to support optional packages

    + + + + + + +

    optional_package(name[, trip_msg, min_version])

    Return package-like thing and module setup for package name

    +
    +

    optional_package

    +
    +
    +nibabel.optpkg.optional_package(name: str, trip_msg: str | None = None, min_version: str | Version | ty.Callable[[ModuleType], bool] | None = None) tuple[ModuleType | TripWire, bool, ty.Callable[[], None]]
    +

    Return package-like thing and module setup for package name

    +
    +
    Parameters:
    +
    +
    namestr

    package name

    +
    +
    trip_msgNone or str

    message to give when someone tries to use the return package, but we +could not import it at an acceptable version, and have returned a +TripWire object instead. Default message if None.

    +
    +
    min_versionNone or str or Version or callable

    If None, do not specify a minimum version. If str, convert to a +packaging.version.Version. If str or Version compare to +version of package name with min_version <= pkg.__version__. If +callable, accepts imported pkg as argument, and returns value of +callable is True for acceptable package versions, False otherwise.

    +
    +
    +
    +
    Returns:
    +
    +
    pkg_likemodule or TripWire instance

    If we can import the package, return it. Otherwise return an object +raising an error when accessed

    +
    +
    have_pkgbool

    True if import for package was successful, false otherwise

    +
    +
    module_setupfunction

    callable usually set as setup_module in calling namespace, to allow +skipping tests.

    +
    +
    +
    +
    +

    Examples

    +

    Typical use would be something like this at the top of a module using an +optional package:

    +
    >>> from nibabel.optpkg import optional_package
    +>>> pkg, have_pkg, setup_module = optional_package('not_a_package')
    +
    +
    +

    Of course in this case the package doesn’t exist, and so, in the module:

    +
    >>> have_pkg
    +False
    +
    +
    +

    and

    +
    >>> pkg.some_function() 
    +Traceback (most recent call last):
    +    ...
    +TripWireError: We need package not_a_package for these functions,
    +    but ``import not_a_package`` raised an ImportError
    +
    +
    +

    If the module does exist - we get the module

    +
    >>> pkg, _, _ = optional_package('os')
    +>>> hasattr(pkg, 'path')
    +True
    +
    +
    +

    Or a submodule if that’s what we asked for

    +
    >>> subpkg, _, _ = optional_package('os.path')
    +>>> hasattr(subpkg, 'dirname')
    +True
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.orientations.html b/reference/nibabel.orientations.html new file mode 100644 index 0000000000..1e47c1f270 --- /dev/null +++ b/reference/nibabel.orientations.html @@ -0,0 +1,466 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    orientations

    +

    Utilities for calculating and applying affine orientations

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    OrientationError

    aff2axcodes(aff[, labels, tol])

    axis direction codes for affine aff

    apply_orientation(arr, ornt)

    Apply transformations implied by ornt to the first n axes of the array arr

    axcodes2ornt(axcodes[, labels])

    Convert axis codes axcodes to an orientation

    flip_axis(arr[, axis])

    Flip contents of axis in array arr

    inv_ornt_aff(ornt, shape)

    Affine transform reversing transforms implied in ornt

    io_orientation(affine[, tol])

    Orientation of input axes in terms of output axes for affine

    ornt2axcodes(ornt[, labels])

    Convert orientation ornt to labels for axis directions

    ornt_transform(start_ornt, end_ornt)

    Return the orientation that transforms from start_ornt to end_ornt.

    +
    +

    OrientationError

    +
    +
    +class nibabel.orientations.OrientationError
    +

    Bases: Exception

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    + +
    +
    +

    aff2axcodes

    +
    +
    +nibabel.orientations.aff2axcodes(aff, labels=None, tol=None)
    +

    axis direction codes for affine aff

    +
    +
    Parameters:
    +
    +
    aff(N,M) array-like

    affine transformation matrix

    +
    +
    labelsoptional, None or sequence of (2,) sequences

    Labels for negative and positive ends of output axes of aff. See +docstring for ornt2axcodes for more detail

    +
    +
    tolNone or float

    Tolerance for SVD of affine - see io_orientation for more detail.

    +
    +
    +
    +
    Returns:
    +
    +
    axcodes(N,) tuple

    labels for positive end of voxel axes. Dropped axes get a label of +None.

    +
    +
    +
    +
    +

    Examples

    +
    >>> aff = [[0,1,0,10],[-1,0,0,20],[0,0,1,30],[0,0,0,1]]
    +>>> aff2axcodes(aff, (('L','R'),('B','F'),('D','U')))
    +('B', 'R', 'U')
    +
    +
    +
    + +
    +
    +

    apply_orientation

    +
    +
    +nibabel.orientations.apply_orientation(arr, ornt)
    +

    Apply transformations implied by ornt to the first +n axes of the array arr

    +
    +
    Parameters:
    +
    +
    arrarray-like of data with ndim >= n
    +
    ornt(n,2) orientation array

    orientation transform. ornt[N,1]` is flip of axis N of the +array implied by `shape`, where 1 means no flip and -1 means +flip.  For example, if ``N==0 and ornt[0,1] == -1, and +there’s an array arr of shape shape, the flip would +correspond to the effect of np.flipud(arr). ornt[:,0] is +the transpose that needs to be done to the implied array, as in +arr.transpose(ornt[:,0])

    +
    +
    +
    +
    Returns:
    +
    +
    t_arrndarray

    data array arr transformed according to ornt

    +
    +
    +
    +
    +
    + +
    +
    +

    axcodes2ornt

    +
    +
    +nibabel.orientations.axcodes2ornt(axcodes, labels=None)
    +

    Convert axis codes axcodes to an orientation

    +
    +
    Parameters:
    +
    +
    axcodes(N,) tuple

    axis codes - see ornt2axcodes docstring

    +
    +
    labelsoptional, None or sequence of (2,) sequences

    (2,) sequences are labels for (beginning, end) of output axis. That +is, if the first element in axcodes is front, and the second +(2,) sequence in labels is (‘back’, ‘front’) then the first +row of ornt will be [1, 1]. If None, equivalent to +(('L','R'),('P','A'),('I','S')) - that is - RAS axes.

    +
    +
    +
    +
    Returns:
    +
    +
    ornt(N,2) array-like

    orientation array - see io_orientation docstring

    +
    +
    +
    +
    +

    Examples

    +
    >>> axcodes2ornt(('F', 'L', 'U'), (('L','R'),('B','F'),('D','U')))
    +array([[ 1.,  1.],
    +       [ 0., -1.],
    +       [ 2.,  1.]])
    +
    +
    +
    + +
    +
    +

    flip_axis

    +
    +
    +nibabel.orientations.flip_axis(arr, axis=0)
    +

    Flip contents of axis in array arr

    +

    flip_axis is deprecated. Please use numpy.flip instead.

    +
      +
    • deprecated from version: 3.2

    • +
    • Raises <class ‘nibabel.deprecator.ExpiredDeprecationError’> as of version: 5.0

    • +
    +
    + +
    +
    +

    inv_ornt_aff

    +
    +
    +nibabel.orientations.inv_ornt_aff(ornt, shape)
    +

    Affine transform reversing transforms implied in ornt

    +

    Imagine you have an array arr of shape shape, and you apply the +transforms implied by ornt (more below), to get tarr. +tarr may have a different shape shape_prime. This routine +returns the affine that will take a array coordinate for tarr +and give you the corresponding array coordinate in arr.

    +
    +
    Parameters:
    +
    +
    ornt(p, 2) ndarray

    orientation transform. ornt[P, 1]` is flip of axis N of the array +implied by `shape`, where 1 means no flip and -1 means flip.  For +example, if ``P==0 and ornt[0, 1] == -1, and there’s an array +arr of shape shape, the flip would correspond to the effect of +np.flipud(arr). ornt[:,0] gives us the (reverse of the) +transpose that has been done to arr. If there are any NaNs in +ornt, we raise an OrientationError (see notes)

    +
    +
    shapelength p sequence

    shape of array you may transform with ornt

    +
    +
    +
    +
    Returns:
    +
    +
    transform_affine(p + 1, p + 1) ndarray

    An array arr (shape shape) might be transformed according to +ornt, resulting in a transformed array tarr. transformed_affine +is the transform that takes you from array coordinates in tarr to +array coordinates in arr.

    +
    +
    +
    +
    +

    Notes

    +

    If a row in ornt contains NaN, this means that the input row does not +influence the output space, and is thus effectively dropped from the output +space. In that case one tarr coordinate maps to many arr +coordinates, we can’t invert the transform, and we raise an error

    +
    + +
    +
    +

    io_orientation

    +
    +
    +nibabel.orientations.io_orientation(affine, tol=None)
    +

    Orientation of input axes in terms of output axes for affine

    +

    Valid for an affine transformation from p dimensions to q +dimensions (affine.shape == (q + 1, p + 1)).

    +

    The calculated orientations can be used to transform associated +arrays to best match the output orientations. If p > q, then +some of the output axes should be considered dropped in this +orientation.

    +
    +
    Parameters:
    +
    +
    affine(q+1, p+1) ndarray-like

    Transformation affine from p inputs to q outputs. Usually this +will be a shape (4,4) matrix, transforming 3 inputs to 3 outputs, but +the code also handles the more general case

    +
    +
    tol{None, float}, optional

    threshold below which SVD values of the affine are considered zero. If +tol is None, and S is an array with singular values for affine, +and eps is the epsilon value for datatype of S, then tol set +to S.max() * max((q, p)) * eps

    +
    +
    +
    +
    Returns:
    +
    +
    orientations(p, 2) ndarray

    one row per input axis, where the first value in each row is the closest +corresponding output axis. The second value in each row is 1 if the +input axis is in the same direction as the corresponding output axis and +-1 if it is in the opposite direction. If a row is [np.nan, np.nan], +which can happen when p > q, then this row should be considered dropped.

    +
    +
    +
    +
    +
    + +
    +
    +

    ornt2axcodes

    +
    +
    +nibabel.orientations.ornt2axcodes(ornt, labels=None)
    +

    Convert orientation ornt to labels for axis directions

    +
    +
    Parameters:
    +
    +
    ornt(N,2) array-like

    orientation array - see io_orientation docstring

    +
    +
    labelsoptional, None or sequence of (2,) sequences

    (2,) sequences are labels for (beginning, end) of output axis. That +is, if the first row in ornt is [1, 1], and the second (2,) +sequence in labels is (‘back’, ‘front’) then the first returned axis +code will be 'front'. If the first row in ornt had been +[1, -1] then the first returned value would have been 'back'. +If None, equivalent to (('L','R'),('P','A'),('I','S')) - that is - +RAS axes.

    +
    +
    +
    +
    Returns:
    +
    +
    axcodes(N,) tuple

    labels for positive end of voxel axes. Dropped axes get a label of +None.

    +
    +
    +
    +
    +

    Examples

    +
    >>> ornt2axcodes([[1, 1],[0,-1],[2,1]], (('L','R'),('B','F'),('D','U')))
    +('F', 'L', 'U')
    +
    +
    +
    + +
    +
    +

    ornt_transform

    +
    +
    +nibabel.orientations.ornt_transform(start_ornt, end_ornt)
    +

    Return the orientation that transforms from start_ornt to end_ornt.

    +
    +
    Parameters:
    +
    +
    start_ornt(n,2) orientation array

    Initial orientation.

    +
    +
    end_ornt(n,2) orientation array

    Final orientation.

    +
    +
    +
    +
    Returns:
    +
    +
    orientations(p, 2) ndarray

    The orientation that will transform the start_ornt to the end_ornt.

    +
    +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.parrec.html b/reference/nibabel.parrec.html new file mode 100644 index 0000000000..0c32b90f6f --- /dev/null +++ b/reference/nibabel.parrec.html @@ -0,0 +1,1032 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    parrec

    +

    Read images in PAR/REC format

    +

    This is yet another MRI image format generated by Philips scanners. It is an +ASCII header (PAR) plus a binary blob (REC).

    +

    This implementation aims to read version 4.0 through 4.2 of this format. Other +versions could probably be supported, but we need example images to test +against. If you want us to support another version, and have an image we can +add to the test suite, let us know. You would make us very happy by submitting +a pull request.

    +
    +

    PAR file format

    +

    The PAR format appears to have two sections:

    +
    +

    General information

    +

    This is a set of lines each giving one key : value pair, examples:

    +
    .    EPI factor        <0,1=no EPI>     :   39
    +.    Dynamic scan      <0=no 1=yes> ?   :   1
    +.    Diffusion         <0=no 1=yes> ?   :   0
    +
    +
    +

    (from nibabel/tests/data/phantom_EPI_asc_CLEAR_2_1.PAR)

    +
    +
    +

    Image information

    +

    There is a # prefixed list of fields under the heading “IMAGE INFORMATION +DEFINITION”. From the same file, here is the start of this list:

    +
    # === IMAGE INFORMATION DEFINITION =============================================
    +#  The rest of this file contains ONE line per image, this line contains the following information:
    +#
    +#  slice number                             (integer)
    +#  echo number                              (integer)
    +#  dynamic scan number                      (integer)
    +
    +
    +

    There follows a space separated table with values for these fields, each row +containing all the named values. Here are the first few lines from the example +file above:

    +
    # === IMAGE INFORMATION ==========================================================
    +#  sl ec  dyn ph ty    idx pix scan% rec size                (re)scale              window        angulation              offcentre        thick   gap   info      spacing     echo     dtime   ttime    diff  avg  flip    freq   RR-int  turbo delay b grad cont anis         diffusion       L.ty
    +
    +1   1    1  1 0 2     0  16    62   64   64     0.00000   1.29035 4.28404e-003  1070  1860 -13.26  -0.00  -0.00    2.51   -0.81   -8.69  6.000  2.000 0 1 0 2  3.750  3.750  30.00    0.00     0.00    0.00   0   90.00     0    0    0    39   0.0  1   1    8    0   0.000    0.000    0.000  1
    +2   1    1  1 0 2     1  16    62   64   64     0.00000   1.29035 4.28404e-003  1122  1951 -13.26  -0.00  -0.00    2.51    6.98  -10.53  6.000  2.000 0 1 0 2  3.750  3.750  30.00    0.00     0.00    0.00   0   90.00     0    0    0    39   0.0  1   1    8    0   0.000    0.000    0.000  1
    +3   1    1  1 0 2     2  16    62   64   64     0.00000   1.29035 4.28404e-003  1137  1977 -13.26  -0.00  -0.00    2.51   14.77  -12.36  6.000  2.000 0 1 0 2  3.750  3.750  30.00    0.00     0.00    0.00   0   90.00     0    0    0    39   0.0  1   1    8    0   0.000    0.000    0.000  1
    +
    +
    +
    +
    +

    Orientation

    +

    PAR files refer to orientations “ap”, “fh” and “rl”.

    +

    Nibabel’s required affine output axes are RAS (left to Right, posterior to +Anterior, inferior to Superior). The correspondence of the PAR file’s axes to +RAS axes is:

    +
      +
    • ap = anterior -> posterior = negative A in RAS = P

    • +
    • fh = foot -> head = S in RAS = S

    • +
    • rl = right -> left = negative R in RAS = L

    • +
    +

    We therefore call the PAR file’s axis system “PSL” (Posterior, Superior, Left).

    +

    The orientation of the PAR file axes corresponds to DICOM’s LPS coordinate +system (right to Left, anterior to Posterior, inferior to Superior), but in a +different order.

    +
    +
    +

    Data type

    +

    It seems that everyone agrees that Philips stores REC data in little-endian +format - see https://github.com/nipy/nibabel/issues/274

    +

    Philips XML header files, and some previous experience, suggest that the REC +data is always stored as 8 or 16 bit unsigned integers - see +https://github.com/nipy/nibabel/issues/275

    +
    +
    +

    Data Sorting

    +

    PAR/REC files have a large number of potential image dimensions. To handle +sorting of volumes in PAR/REC files based on these fields and not the order +slices first appear in the PAR file, the strict_sort flag of +nibabel.load (or parrec.load) should be set to True. The fields +that are taken into account during sorting are:

    +
    +
      +
    • slice number

    • +
    • echo number

    • +
    • cardiac phase number

    • +
    • gradient orientation number

    • +
    • diffusion b value number

    • +
    • label type (ASL tag vs. control)

    • +
    • dynamic scan number

    • +
    • image_type_mr (Re, Im, Mag, Phase)

    • +
    +
    +

    Slices are sorted into the third dimension and the +order of preference for sorting along the 4th dimension corresponds to the +order in the list above. If the image data has more than 4 dimensions these +will all be concatenated along the 4th dimension. For example, for a scan with +two echos and two dynamics, the 4th dimension will have both echos of dynamic 1 +prior to the two echos for dynamic 2.

    +

    The``get_volume_labels`` method of the header returns a dictionary containing +the PAR field labels for this 4th dimension.

    +

    The volume sorting described above can be enabled in the parrec2nii command +utility via the option “–strict-sort”. The dimension info can be exported +to a CSV file by adding the option “–volume-info”.

    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    PARRECArrayProxy(file_like, header, *[, ...])

    Initialize PARREC array proxy

    PARRECError

    Exception for PAR/REC format related problems.

    PARRECHeader(info, image_defs[, ...])

    PAR/REC header

    PARRECImage(dataobj, affine[, header, ...])

    PAR/REC image

    exts2pars(exts_source)

    Parse, return any PAR headers from NIfTI extensions in exts_source

    one_line(long_str)

    Make maybe mutli-line long_str into one long line

    parse_PAR_header(fobj)

    Parse a PAR header and aggregate all information into useful containers.

    vol_is_full(slice_nos, slice_max[, slice_min])

    Vector with True for slices in complete volume, False otherwise

    vol_numbers(slice_nos)

    Calculate volume numbers inferred from slice numbers slice_nos

    +
    +

    PARRECArrayProxy

    +
    +
    +class nibabel.parrec.PARRECArrayProxy(file_like, header, *, mmap=True, scaling='dv')
    +

    Bases: object

    +

    Initialize PARREC array proxy

    +
    +
    Parameters:
    +
    +
    file_likefile-like object

    Filename or object implementing read, seek, tell

    +
    +
    headerPARRECHeader instance

    Implementing get_data_shape, get_data_dtype, +get_sorted_slice_indices, get_data_scaling, +get_rec_shape.

    +
    +
    mmap{True, False, ‘c’, ‘r’}, optional, keyword only

    mmap controls the use of numpy memory mapping for reading data. +If False, do not try numpy memmap for data array. If one of +{‘c’, ‘r’}, try numpy memmap with mode=mmap. A mmap value of +True gives the same behavior as mmap='c'. If file_like +cannot be memory-mapped, ignore mmap value and read array from +file.

    +
    +
    scaling{‘fp’, ‘dv’}, optional, keyword only

    Type of scaling to use - see header get_data_scaling method.

    +
    +
    +
    +
    +
    +
    +__init__(file_like, header, *, mmap=True, scaling='dv')
    +

    Initialize PARREC array proxy

    +
    +
    Parameters:
    +
    +
    file_likefile-like object

    Filename or object implementing read, seek, tell

    +
    +
    headerPARRECHeader instance

    Implementing get_data_shape, get_data_dtype, +get_sorted_slice_indices, get_data_scaling, +get_rec_shape.

    +
    +
    mmap{True, False, ‘c’, ‘r’}, optional, keyword only

    mmap controls the use of numpy memory mapping for reading data. +If False, do not try numpy memmap for data array. If one of +{‘c’, ‘r’}, try numpy memmap with mode=mmap. A mmap value of +True gives the same behavior as mmap='c'. If file_like +cannot be memory-mapped, ignore mmap value and read array from +file.

    +
    +
    scaling{‘fp’, ‘dv’}, optional, keyword only

    Type of scaling to use - see header get_data_scaling method.

    +
    +
    +
    +
    +
    + +
    +
    +property dtype
    +
    + +
    +
    +get_unscaled()
    +

    Read data from file

    +

    This is an optional part of the proxy API

    +
    + +
    +
    +property is_proxy
    +
    + +
    +
    +property ndim
    +
    + +
    +
    +property shape
    +
    + +
    + +
    +
    +

    PARRECError

    +
    +
    +class nibabel.parrec.PARRECError
    +

    Bases: Exception

    +

    Exception for PAR/REC format related problems.

    +

    To be raised whenever PAR/REC is not happy, or we are not happy with +PAR/REC.

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    + +
    +
    +

    PARRECHeader

    +
    +
    +class nibabel.parrec.PARRECHeader(info, image_defs, permit_truncated=False, strict_sort=False)
    +

    Bases: SpatialHeader

    +

    PAR/REC header

    +
    +
    Parameters:
    +
    +
    infodict

    “General information” from the PAR file (as returned by +parse_PAR_header()).

    +
    +
    image_defsarray

    Structured array with image definitions from the PAR file (as +returned by parse_PAR_header()).

    +
    +
    permit_truncatedbool, optional

    If True, a warning is emitted instead of an error when a truncated +recording is detected.

    +
    +
    strict_sortbool, optional, keyword-only

    If True, a larger number of header fields are used while sorting +the REC data array. This may produce a different sort order than +strict_sort=False, where volumes are sorted by the order in which +the slices appear in the .PAR file.

    +
    +
    +
    +
    +
    +
    +__init__(info, image_defs, permit_truncated=False, strict_sort=False)
    +
    +
    Parameters:
    +
    +
    infodict

    “General information” from the PAR file (as returned by +parse_PAR_header()).

    +
    +
    image_defsarray

    Structured array with image definitions from the PAR file (as +returned by parse_PAR_header()).

    +
    +
    permit_truncatedbool, optional

    If True, a warning is emitted instead of an error when a truncated +recording is detected.

    +
    +
    strict_sortbool, optional, keyword-only

    If True, a larger number of header fields are used while sorting +the REC data array. This may produce a different sort order than +strict_sort=False, where volumes are sorted by the order in which +the slices appear in the .PAR file.

    +
    +
    +
    +
    +
    + +
    +
    +as_analyze_map()
    +

    Convert PAR parameters to NIFTI1 format

    +
    + +
    +
    +copy()
    +

    Copy object to independent representation

    +

    The copy should not be affected by any changes to the original +object.

    +
    + +
    +
    +classmethod from_fileobj(fileobj, permit_truncated=False, strict_sort=False)
    +
    + +
    +
    +classmethod from_header(header=None)
    +
    + +
    +
    +get_affine(origin='scanner')
    +

    Compute affine transformation into scanner space.

    +

    The method only considers global rotation and offset settings in the +header and ignores potentially deviating information in the image +definitions.

    +
    +
    Parameters:
    +
    +
    origin{‘scanner’, ‘fov’}

    Transformation origin. By default the transformation is computed +relative to the scanner’s iso center. If ‘fov’ is requested the +transformation origin will be the center of the field of view +instead.

    +
    +
    +
    +
    Returns:
    +
    +
    aff(4, 4) array

    4x4 array, with output axis order corresponding to RAS or (x,y,z) +or (lr, pa, fh).

    +
    +
    +
    +
    +

    Notes

    +

    Transformations appear to be specified in (ap, fh, rl) axes. The +orientation of data is recorded in the “slice orientation” field of the +PAR header “General Information”.

    +

    We need to:

    +
      +
    • translate to coordinates in terms of the center of the FOV

    • +
    • apply voxel size scaling

    • +
    • reorder / flip the data to Philips’ PSL axes

    • +
    • apply the rotations

    • +
    • apply any isocenter scaling offset if origin == “scanner”

    • +
    • reorder and flip to RAS axes

    • +
    +
    + +
    +
    +get_bvals_bvecs()
    +

    Get bvals and bvecs from data

    +
    +
    Returns:
    +
    +
    b_valsNone or array

    Array of b values, shape (n_directions,), or None if not a +diffusion acquisition.

    +
    +
    b_vectorsNone or array

    Array of b vectors, shape (n_directions, 3), or None if not a +diffusion acquisition.

    +
    +
    +
    +
    +
    + +
    +
    +get_data_offset()
    +

    PAR header always has 0 data offset (into REC file)

    +
    + +
    +
    +get_data_scaling(method='dv')
    +

    Returns scaling slope and intercept.

    +
    +
    Parameters:
    +
    +
    method{‘fp’, ‘dv’}

    Scaling settings to be reported – see notes below.

    +
    +
    +
    +
    Returns:
    +
    +
    slopearray

    scaling slope

    +
    +
    interceptarray

    scaling intercept

    +
    +
    +
    +
    +

    Notes

    +

    The PAR header contains two different scaling settings: ‘dv’ (value on +console) and ‘fp’ (floating point value). Here is how they are defined:

    +

    DV = PV * RS + RI +FP = DV / (RS * SS)

    +

    where:

    +

    PV: value in REC +RS: rescale slope +RI: rescale intercept +SS: scale slope

    +
    + +
    +
    +get_def(name)
    +

    Return a single image definition field (or None if missing)

    +
    + +
    +
    +get_echo_train_length()
    +

    Echo train length of the recording

    +
    + +
    +
    +get_q_vectors()
    +

    Get Q vectors from the data

    +
    +
    Returns:
    +
    +
    q_vectorsNone or array

    Array of q vectors (bvals * bvecs), or None if not a diffusion +acquisition.

    +
    +
    +
    +
    +
    + +
    +
    +get_rec_shape()
    +
    + +
    +
    +get_slice_orientation()
    +

    Returns the slice orientation label.

    +
    +
    Returns:
    +
    +
    orientation{‘transverse’, ‘sagittal’, ‘coronal’}
    +
    +
    +
    +
    + +
    +
    +get_sorted_slice_indices()
    +

    Return indices to sort (and maybe discard) slices in REC file.

    +

    If the recording is truncated, the returned indices take care of +discarding any slice indices from incomplete volumes.

    +

    If self.strict_sort is True, a more complicated sorting based on +multiple fields from the .PAR file is used. This may produce a +different sort order than strict_sort=False, where volumes are sorted +by the order in which the slices appear in the .PAR file.

    +
    +
    Returns:
    +
    +
    slice_indiceslist

    List for indexing into the last (third) dimension of the REC data +array, and (equivalently) the only dimension of +self.image_defs.

    +
    +
    +
    +
    +
    + +
    +
    +get_volume_labels()
    +

    Dynamic labels corresponding to the final data dimension(s).

    +

    This is useful for custom data sorting. A subset of the info in +self.image_defs is returned in an order that matches the final +data dimension(s). Only labels that have more than one unique value +across the dataset will be returned.

    +
    +
    Returns:
    +
    +
    sort_infodict

    Each key corresponds to volume labels for a dynamically varying +sequence dimension. The ordering of the labels matches the volume +ordering determined via self.get_sorted_slice_indices.

    +
    +
    +
    +
    +
    + +
    +
    +get_water_fat_shift()
    +

    Water fat shift, in pixels

    +
    + +
    +
    +set_data_offset(offset)
    +

    PAR header always has 0 data offset (into REC file)

    +
    + +
    + +
    +
    +

    PARRECImage

    +
    +
    +class nibabel.parrec.PARRECImage(dataobj: ArrayLike, affine: np.ndarray | None, header: FileBasedHeader | ty.Mapping | None = None, extra: ty.Mapping | None = None, file_map: FileMap | None = None)
    +

    Bases: SpatialImage

    +

    PAR/REC image

    +

    Initialize image

    +

    The image is a combination of (array-like, affine matrix, header), with +optional metadata in extra, and filename / file-like objects +contained in the file_map mapping.

    +
    +
    Parameters:
    +
    +
    dataobjobject

    Object containing image data. It should be some object that returns an +array from np.asanyarray. It should have a shape attribute +or property

    +
    +
    affineNone or (4,4) array-like

    homogeneous affine giving relationship between voxel coordinates and +world coordinates. Affine can also be None. In this case, +obj.affine also returns None, and the affine as written to disk +will depend on the file format.

    +
    +
    headerNone or mapping or header instance, optional

    metadata for this image format

    +
    +
    extraNone or mapping, optional

    metadata to associate with image that cannot be stored in the +metadata of this image type

    +
    +
    file_mapmapping, optional

    mapping giving file information for this image format

    +
    +
    +
    +
    +
    +
    +__init__(dataobj: ArrayLike, affine: np.ndarray | None, header: FileBasedHeader | ty.Mapping | None = None, extra: ty.Mapping | None = None, file_map: FileMap | None = None)
    +

    Initialize image

    +

    The image is a combination of (array-like, affine matrix, header), with +optional metadata in extra, and filename / file-like objects +contained in the file_map mapping.

    +
    +
    Parameters:
    +
    +
    dataobjobject

    Object containing image data. It should be some object that returns an +array from np.asanyarray. It should have a shape attribute +or property

    +
    +
    affineNone or (4,4) array-like

    homogeneous affine giving relationship between voxel coordinates and +world coordinates. Affine can also be None. In this case, +obj.affine also returns None, and the affine as written to disk +will depend on the file format.

    +
    +
    headerNone or mapping or header instance, optional

    metadata for this image format

    +
    +
    extraNone or mapping, optional

    metadata to associate with image that cannot be stored in the +metadata of this image type

    +
    +
    file_mapmapping, optional

    mapping giving file information for this image format

    +
    +
    +
    +
    +
    + +
    +
    +ImageArrayProxy
    +

    alias of PARRECArrayProxy

    +
    + +
    +
    +files_types: tuple[ExtensionSpec, ...] = (('image', '.rec'), ('header', '.par'))
    +
    + +
    +
    +classmethod from_file_map(file_map, *, mmap=True, permit_truncated=False, scaling='dv', strict_sort=False)
    +

    Create PARREC image from file map file_map

    +
    +
    Parameters:
    +
    +
    file_mapdict

    dict with keys image, header and values being fileholder +objects for the respective REC and PAR files.

    +
    +
    mmap{True, False, ‘c’, ‘r’}, optional, keyword only

    mmap controls the use of numpy memory mapping for reading image +array data. If False, do not try numpy memmap for data array. +If one of {‘c’, ‘r’}, try numpy memmap with mode=mmap. A +mmap value of True gives the same behavior as mmap='c'. If +image data file cannot be memory-mapped, ignore mmap value and +read array from file.

    +
    +
    permit_truncated{False, True}, optional, keyword-only

    If False, raise an error for an image where the header shows signs +that fewer slices / volumes were recorded than were expected.

    +
    +
    scaling{‘dv’, ‘fp’}, optional, keyword-only

    Scaling method to apply to data (see +PARRECHeader.get_data_scaling()).

    +
    +
    strict_sortbool, optional, keyword-only

    If True, a larger number of header fields are used while sorting +the REC data array. This may produce a different sort order than +strict_sort=False, where volumes are sorted by the order in which +the slices appear in the .PAR file.

    +
    +
    +
    +
    +
    + +
    +
    +classmethod from_filename(filename, *, mmap=True, permit_truncated=False, scaling='dv', strict_sort=False)
    +

    Create PARREC image from filename filename

    +
    +
    Parameters:
    +
    +
    filenamestr

    Filename of “PAR” or “REC” file

    +
    +
    mmap{True, False, ‘c’, ‘r’}, optional, keyword only

    mmap controls the use of numpy memory mapping for reading image +array data. If False, do not try numpy memmap for data array. +If one of {‘c’, ‘r’}, try numpy memmap with mode=mmap. A +mmap value of True gives the same behavior as mmap='c'. If +image data file cannot be memory-mapped, ignore mmap value and +read array from file.

    +
    +
    permit_truncated{False, True}, optional, keyword-only

    If False, raise an error for an image where the header shows signs +that fewer slices / volumes were recorded than were expected.

    +
    +
    scaling{‘dv’, ‘fp’}, optional, keyword-only

    Scaling method to apply to data (see +PARRECHeader.get_data_scaling()).

    +
    +
    strict_sortbool, optional, keyword-only

    If True, a larger number of header fields are used while sorting +the REC data array. This may produce a different sort order than +strict_sort=False, where volumes are sorted by the order in which +the slices appear in the .PAR file.

    +
    +
    +
    +
    +
    + +
    +
    +header_class
    +

    alias of PARRECHeader

    +
    + +
    +
    +classmethod load(filename, *, mmap=True, permit_truncated=False, scaling='dv', strict_sort=False)
    +

    Create PARREC image from filename filename

    +
    +
    Parameters:
    +
    +
    filenamestr

    Filename of “PAR” or “REC” file

    +
    +
    mmap{True, False, ‘c’, ‘r’}, optional, keyword only

    mmap controls the use of numpy memory mapping for reading image +array data. If False, do not try numpy memmap for data array. +If one of {‘c’, ‘r’}, try numpy memmap with mode=mmap. A +mmap value of True gives the same behavior as mmap='c'. If +image data file cannot be memory-mapped, ignore mmap value and +read array from file.

    +
    +
    permit_truncated{False, True}, optional, keyword-only

    If False, raise an error for an image where the header shows signs +that fewer slices / volumes were recorded than were expected.

    +
    +
    scaling{‘dv’, ‘fp’}, optional, keyword-only

    Scaling method to apply to data (see +PARRECHeader.get_data_scaling()).

    +
    +
    strict_sortbool, optional, keyword-only

    If True, a larger number of header fields are used while sorting +the REC data array. This may produce a different sort order than +strict_sort=False, where volumes are sorted by the order in which +the slices appear in the .PAR file.

    +
    +
    +
    +
    +
    + +
    +
    +makeable: bool = False
    +
    + +
    +
    +rw: bool = False
    +
    + +
    +
    +valid_exts: tuple[str, ...] = ('.rec', '.par')
    +
    + +
    + +
    +
    +

    exts2pars

    +
    +
    +nibabel.parrec.exts2pars(exts_source)
    +

    Parse, return any PAR headers from NIfTI extensions in exts_source

    +
    +
    Parameters:
    +
    +
    exts_sourcesequence or Nifti1Image, Nifti1Header instance

    A sequence of extensions, or header containing NIfTI extensions, or an +image containing a header with NIfTI extensions.

    +
    +
    +
    +
    Returns:
    +
    +
    par_headerslist

    A list of PARRECHeader objects, usually empty or with one element, each +element contains a PARRECHeader read from the contained extensions.

    +
    +
    +
    +
    +
    + +
    +
    +

    one_line

    +
    +
    +nibabel.parrec.one_line(long_str)
    +

    Make maybe mutli-line long_str into one long line

    +
    + +
    +
    +

    parse_PAR_header

    +
    +
    +nibabel.parrec.parse_PAR_header(fobj)
    +

    Parse a PAR header and aggregate all information into useful containers.

    +
    +
    Parameters:
    +
    +
    fobjfile-object

    The PAR header file object.

    +
    +
    +
    +
    Returns:
    +
    +
    general_infodict

    Contains all “General Information” from the header file

    +
    +
    image_infondarray

    Structured array with fields giving all “Image information” in the +header

    +
    +
    +
    +
    +
    + +
    +
    +

    vol_is_full

    +
    +
    +nibabel.parrec.vol_is_full(slice_nos, slice_max, slice_min=1)
    +

    Vector with True for slices in complete volume, False otherwise

    +
    +
    Parameters:
    +
    +
    slice_nossequence

    Sequence of slice numbers, e.g. [1, 2, 3, 4, 1, 2, 3, 4].

    +
    +
    slice_maxint

    Highest slice number for a full slice set. Slice set will be +range(slice_min, slice_max+1).

    +
    +
    slice_minint, optional

    Lowest slice number for full slice set. Default is 1.

    +
    +
    +
    +
    Returns:
    +
    +
    is_fullarray

    Bool vector with True for slices in full volumes, False for slices in +partial volumes. A full volume is a volume with all slices in the +slice set as defined above.

    +
    +
    +
    +
    Raises:
    +
    +
    ValueError

    if any value in slice_nos is outside slice set indices.

    +
    +
    +
    +
    +
    + +
    +
    +

    vol_numbers

    +
    +
    +nibabel.parrec.vol_numbers(slice_nos)
    +

    Calculate volume numbers inferred from slice numbers slice_nos

    +

    The volume number for each slice is the number of times this slice number +has occurred previously in the slice_nos sequence

    +
    +
    Parameters:
    +
    +
    slice_nossequence

    Sequence of slice numbers, e.g. [1, 2, 3, 4, 1, 2, 3, 4].

    +
    +
    +
    +
    Returns:
    +
    +
    vol_noslist

    A list, the same length of slice_nos giving the volume number for +each corresponding slice number.

    +
    +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.pointset.html b/reference/nibabel.pointset.html new file mode 100644 index 0000000000..8ffbf7f7c3 --- /dev/null +++ b/reference/nibabel.pointset.html @@ -0,0 +1,360 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    pointset

    +

    Point-set structures

    +

    Imaging data are sampled at points in space, and these points +can be described by coordinates. +These structures are designed to enable operations on sets of +points, as opposed to the data sampled at those points.

    +

    Abstractly, a point set is any collection of points, but there are +two types that warrant special consideration in the neuroimaging +context: grids and meshes.

    +

    A grid is a collection of regularly-spaced points. The canonical +examples of grids are the indices of voxels and their affine +projection into a reference space.

    +

    A mesh is a collection of points and some structure that enables +adjacent points to be identified. A triangular mesh in particular +uses triplets of adjacent vertices to describe faces.

    + + + + + + + + + + + + + + + +

    CoordinateArray(*args, **kwargs)

    Grid(coordinates[, affine, homogeneous])

    A regularly-spaced collection of coordinates

    GridIndices(shape[, dtype])

    Class for generating indices just-in-time

    Pointset(coordinates[, affine, homogeneous])

    A collection of points described by coordinates.

    +
    +

    CoordinateArray

    +
    +
    +class nibabel.pointset.CoordinateArray(*args, **kwargs)
    +

    Bases: Protocol

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    +
    +ndim: int
    +
    + +
    +
    +shape: tuple[int, int]
    +
    + +
    + +
    +
    +

    Grid

    +
    +
    +class nibabel.pointset.Grid(coordinates: CoordinateArray, affine: ndarray | None = None, homogeneous: bool = False)
    +

    Bases: Pointset

    +

    A regularly-spaced collection of coordinates

    +

    This class provides factory methods for generating Pointsets from +SpatialImages and generating masks +from coordinate sets.

    +
    +
    +__init__(coordinates: CoordinateArray, affine: ndarray | None = None, homogeneous: bool = False)
    +
    + +
    +
    +classmethod from_image(spatialimage: SpatialImage) Self
    +
    + +
    +
    +classmethod from_mask(mask: SpatialImage) Self
    +
    + +
    +
    +to_mask(shape=None) SpatialImage
    +
    + +
    + +
    +
    +

    GridIndices

    +
    +
    +class nibabel.pointset.GridIndices(shape, dtype=None)
    +

    Bases: object

    +

    Class for generating indices just-in-time

    +
    +
    +__init__(shape, dtype=None)
    +
    + +
    +
    +dtype
    +
    + +
    +
    +gridshape
    +
    + +
    +
    +ndim = 2
    +
    + +
    +
    +shape
    +
    + +
    + +
    +
    +

    Pointset

    +
    +
    +class nibabel.pointset.Pointset(coordinates: CoordinateArray, affine: ndarray | None = None, homogeneous: bool = False)
    +

    Bases: object

    +

    A collection of points described by coordinates.

    +
    +
    Parameters:
    +
    +
    coordsarray-like

    (N, n) array with N being points and columns their n-dimensional coordinates

    +
    +
    affinenumpy.ndarray

    Affine transform to be applied to coordinates array

    +
    +
    homogeneousbool

    Indicate whether the provided coordinates are homogeneous, +i.e., homogeneous 3D coordinates have the form (x, y, z, 1)

    +
    +
    +
    +
    +
    +
    +__init__(coordinates: CoordinateArray, affine: ndarray | None = None, homogeneous: bool = False)
    +
    + +
    +
    +affine: ndarray
    +
    + +
    +
    +coordinates: CoordinateArray
    +
    + +
    +
    +property dim: int
    +

    The dimensionality of the space the coordinates are in

    +
    + +
    +
    +get_coords(*, as_homogeneous: bool = False)
    +

    Retrieve the coordinates

    +
    +
    Parameters:
    +
    +
    as_homogeneousbool

    Return homogeneous coordinates if True, or Cartesian +coordinates if False.

    +
    +
    namestr

    Select a particular coordinate system if more than one may exist. +By default, None is equivalent to “world” and corresponds to +an RAS+ coordinate system.

    +
    +
    +
    +
    +
    + +
    +
    +homogeneous: bool = False
    +
    + +
    +
    +property n_coords: int
    +

    Number of coordinates

    +

    Subclasses should override with more efficient implementations.

    +
    + +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.processing.html b/reference/nibabel.processing.html new file mode 100644 index 0000000000..fc3c76f42f --- /dev/null +++ b/reference/nibabel.processing.html @@ -0,0 +1,477 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    processing

    +

    Image processing functions

    +

    Image processing functions for:

    +
    +
      +
    • smoothing

    • +
    • resampling

    • +
    • converting SD to and from FWHM

    • +
    +
    +

    Smoothing and resampling routines need scipy.

    + + + + + + + + + + + + + + + + + + + + + + + + +

    adapt_affine(affine, n_dim)

    Adapt input / output dimensions of spatial affine for n_dims

    conform(from_img[, out_shape, voxel_size, ...])

    Resample image to out_shape with voxels of size voxel_size.

    fwhm2sigma(fwhm)

    Convert a FWHM value to sigma in a Gaussian kernel.

    resample_from_to(from_img, to_vox_map[, ...])

    Resample image from_img to mapped voxel space to_vox_map

    resample_to_output(in_img[, voxel_sizes, ...])

    Resample image in_img to output voxel axes (world space)

    sigma2fwhm(sigma)

    Convert a sigma in a Gaussian kernel to a FWHM value

    smooth_image(img, fwhm[, mode, cval, out_class])

    Smooth image img along voxel axes by FWHM fwhm millimeters

    +
    +

    adapt_affine

    +
    +
    +nibabel.processing.adapt_affine(affine, n_dim)
    +

    Adapt input / output dimensions of spatial affine for n_dims

    +

    Adapts a spatial (4, 4) affine that is being applied to an image with fewer +than 3 spatial dimensions, or more than 3 dimensions. If there are more +than three dimensions, assume an identity transformation for these +dimensions.

    +
    +
    Parameters:
    +
    +
    affinearray-like

    affine transform. Usually shape (4, 4). For what follows N, M = +affine.shape

    +
    +
    n_dimsint

    Number of dimensions of underlying array, and therefore number of input +dimensions for affine.

    +
    +
    +
    +
    Returns:
    +
    +
    adaptedshape (M, n_dims+1) array

    Affine array adapted to number of input dimensions. Columns of the +affine corresponding to missing input dimensions have been dropped, +columns corresponding to extra input dimensions have an extra identity +column added

    +
    +
    +
    +
    +
    + +
    +
    +

    conform

    +
    +
    +nibabel.processing.conform(from_img, out_shape=(256, 256, 256), voxel_size=(1.0, 1.0, 1.0), order=3, cval=0.0, orientation='RAS', out_class=None)
    +

    Resample image to out_shape with voxels of size voxel_size.

    +

    Using the default arguments, this function is meant to replicate most parts +of FreeSurfer’s mri_convert --conform command. Specifically, this +function:

    +
    +
      +
    • Resamples data to output_shape

    • +
    • Resamples voxel sizes to voxel_size

    • +
    • Reorients to RAS (mri_convert --conform reorients to LIA)

    • +
    +
    +

    Unlike mri_convert --conform, this command does not:

    +
    +
      +
    • Transform data to range [0, 255]

    • +
    • Cast to unsigned eight-bit integer

    • +
    +
    +
    +
    Parameters:
    +
    +
    from_imgobject

    Object having attributes dataobj, affine, header and +shape. If out_class is not None, img.__class__ should be able +to construct an image from data, affine and header.

    +
    +
    out_shapesequence, optional

    The shape of the output volume. Default is (256, 256, 256).

    +
    +
    voxel_sizesequence, optional

    The size in millimeters of the voxels in the resampled output. Default +is 1mm isotropic.

    +
    +
    orderint, optional

    The order of the spline interpolation, default is 3. The order has to +be in the range 0-5 (see scipy.ndimage.affine_transform)

    +
    +
    cvalscalar, optional

    Value used for points outside the boundaries of the input if +mode='constant'. Default is 0.0 (see +scipy.ndimage.affine_transform)

    +
    +
    orientationstr, optional

    Orientation of output image. Default is “RAS”.

    +
    +
    out_classNone or SpatialImage class, optional

    Class of output image. If None, use from_img.__class__.

    +
    +
    +
    +
    Returns:
    +
    +
    out_imgobject

    Image of instance specified by out_class, containing data output from +resampling from_img into axes aligned to the output space of +from_img.affine

    +
    +
    +
    +
    +
    + +
    +
    +

    fwhm2sigma

    +
    +
    +nibabel.processing.fwhm2sigma(fwhm)
    +

    Convert a FWHM value to sigma in a Gaussian kernel.

    +
    +
    Parameters:
    +
    +
    fwhmarray-like

    FWHM value or values

    +
    +
    +
    +
    Returns:
    +
    +
    sigmaarray or float

    sigma values corresponding to fwhm values

    +
    +
    +
    +
    +

    Examples

    +
    >>> sigma = fwhm2sigma(6)
    +>>> sigmae = fwhm2sigma([6, 7, 8])
    +>>> sigma == sigmae[0]
    +True
    +
    +
    +
    + +
    +
    +

    resample_from_to

    +
    +
    +nibabel.processing.resample_from_to(from_img, to_vox_map, order=3, mode='constant', cval=0.0, out_class=<class 'nibabel.nifti1.Nifti1Image'>)
    +

    Resample image from_img to mapped voxel space to_vox_map

    +

    Resample using N-d spline interpolation.

    +
    +
    Parameters:
    +
    +
    from_imgobject

    Object having attributes dataobj, affine, header and +shape. If out_class is not None, img.__class__ should be able +to construct an image from data, affine and header.

    +
    +
    to_vox_mapimage object or length 2 sequence

    If object, has attributes shape giving input voxel shape, and +affine giving mapping of input voxels to output space. If length 2 +sequence, elements are (shape, affine) with same meaning as above. The +affine is a (4, 4) array-like.

    +
    +
    orderint, optional

    The order of the spline interpolation, default is 3. The order has to +be in the range 0-5 (see scipy.ndimage.affine_transform)

    +
    +
    modestr, optional

    Points outside the boundaries of the input are filled according +to the given mode (‘constant’, ‘nearest’, ‘reflect’ or ‘wrap’). +Default is ‘constant’ (see scipy.ndimage.affine_transform)

    +
    +
    cvalscalar, optional

    Value used for points outside the boundaries of the input if +mode='constant'. Default is 0.0 (see +scipy.ndimage.affine_transform)

    +
    +
    out_classNone or SpatialImage class, optional

    Class of output image. If None, use from_img.__class__.

    +
    +
    +
    +
    Returns:
    +
    +
    out_imgobject

    Image of instance specified by out_class, containing data output from +resampling from_img into axes aligned to the output space of +from_img.affine

    +
    +
    +
    +
    +
    + +
    +
    +

    resample_to_output

    +
    +
    +nibabel.processing.resample_to_output(in_img, voxel_sizes=None, order=3, mode='constant', cval=0.0, out_class=<class 'nibabel.nifti1.Nifti1Image'>)
    +

    Resample image in_img to output voxel axes (world space)

    +
    +
    Parameters:
    +
    +
    in_imgobject

    Object having attributes dataobj, affine, header. If +out_class is not None, img.__class__ should be able to construct +an image from data, affine and header.

    +
    +
    voxel_sizesNone or sequence

    Gives the diagonal entries of out_img.affine` (except the trailing 1 +for the homogeneous coordinates) (``out_img.affine == +np.diag(voxel_sizes + [1])). If None, return identity +out_img.affine. If scalar, interpret as vector [voxel_sizes] * +len(in_img.shape).

    +
    +
    orderint, optional

    The order of the spline interpolation, default is 3. The order has to +be in the range 0-5 (see scipy.ndimage.affine_transform).

    +
    +
    modestr, optional

    Points outside the boundaries of the input are filled according to the +given mode (‘constant’, ‘nearest’, ‘reflect’ or ‘wrap’). Default is +‘constant’ (see scipy.ndimage.affine_transform).

    +
    +
    cvalscalar, optional

    Value used for points outside the boundaries of the input if +mode='constant'. Default is 0.0 (see +scipy.ndimage.affine_transform).

    +
    +
    out_classNone or SpatialImage class, optional

    Class of output image. If None, use in_img.__class__.

    +
    +
    +
    +
    Returns:
    +
    +
    out_imgobject

    Image of instance specified by out_class, containing data output from +resampling in_img into axes aligned to the output space of +in_img.affine

    +
    +
    +
    +
    +
    + +
    +
    +

    sigma2fwhm

    +
    +
    +nibabel.processing.sigma2fwhm(sigma)
    +

    Convert a sigma in a Gaussian kernel to a FWHM value

    +
    +
    Parameters:
    +
    +
    sigmaarray-like

    sigma value or values

    +
    +
    +
    +
    Returns:
    +
    +
    fwhmarray or float

    fwhm values corresponding to sigma values

    +
    +
    +
    +
    +

    Examples

    +
    >>> fwhm = sigma2fwhm(3)
    +>>> fwhms = sigma2fwhm([3, 4, 5])
    +>>> fwhm == fwhms[0]
    +True
    +
    +
    +
    + +
    +
    +

    smooth_image

    +
    +
    +nibabel.processing.smooth_image(img, fwhm, mode='nearest', cval=0.0, out_class=<class 'nibabel.nifti1.Nifti1Image'>)
    +

    Smooth image img along voxel axes by FWHM fwhm millimeters

    +
    +
    Parameters:
    +
    +
    imgobject

    Object having attributes dataobj, affine, header and +shape. If out_class is not None, img.__class__ should be able +to construct an image from data, affine and header.

    +
    +
    fwhmscalar or length 3 sequence

    FWHM in mm over which to smooth. The smoothing applies to the voxel +axes, not to the output axes, but is in millimeters. The function +adjusts the FWHM to voxels using the voxel sizes calculated from the +affine. A scalar implies the same smoothing across the spatial +dimensions of the image, but 0 smoothing over any further dimensions +such as time. A vector should be the same length as the number of +image dimensions.

    +
    +
    modestr, optional

    Points outside the boundaries of the input are filled according +to the given mode (‘constant’, ‘nearest’, ‘reflect’ or ‘wrap’). +Default is ‘nearest’. This is different from the default for +scipy.ndimage.affine_transform, which is ‘constant’. ‘nearest’ +might be a better choice when smoothing to the edge of an image where +there is still strong brain signal, otherwise this signal will get +blurred towards zero.

    +
    +
    cvalscalar, optional

    Value used for points outside the boundaries of the input if +mode='constant'. Default is 0.0 (see +scipy.ndimage.affine_transform).

    +
    +
    out_classNone or SpatialImage class, optional

    Class of output image. If None, use img.__class__.

    +
    +
    +
    +
    Returns:
    +
    +
    smoothed_imgobject

    Image of instance specified by out_class, containing data output from +smoothing img data by given FWHM kernel.

    +
    +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.pydicom_compat.html b/reference/nibabel.pydicom_compat.html new file mode 100644 index 0000000000..2457736708 --- /dev/null +++ b/reference/nibabel.pydicom_compat.html @@ -0,0 +1,162 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    pydicom_compat

    +

    Adapter module for working with pydicom < 1.0 and >= 1.0

    +

    In what follows, “dicom is available” means we can import either a) dicom +(pydicom < 1.0) or or b) pydicom (pydicom >= 1.0).

    +

    Regardless of whether dicom is available this module should be importable +without error, and always defines:

    +
      +
    • have_dicom : True if we can import pydicom or dicom;

    • +
    • 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 +is importable else None;

    • +
    +

    A test decorator is available in nibabel.nicom.tests:

    +
      +
    • dicom_test : test decorator that skips test if dicom not available.

    • +
    +

    A deprecated copy is available here for backward compatibility.

    + + + + + + +

    dicom_test(func)

    dicom_test has been moved to nibabel.nicom.tests

    +
    +

    dicom_test

    +
    +
    +nibabel.pydicom_compat.dicom_test(func)
    +

    dicom_test has been moved to nibabel.nicom.tests

    +
      +
    • deprecated from version: 3.1

    • +
    • Raises <class ‘nibabel.deprecator.ExpiredDeprecationError’> as of version: 5.0

    • +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.quaternions.html b/reference/nibabel.quaternions.html new file mode 100644 index 0000000000..82cb77f19b --- /dev/null +++ b/reference/nibabel.quaternions.html @@ -0,0 +1,665 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    quaternions

    +

    Functions to operate on, or return, quaternions

    +

    The module also includes functions for the closely related angle, axis +pair as a specification for rotation.

    +

    Quaternions here consist of 4 values w, x, y, z, where w is the +real (scalar) part, and x, y, z are the complex (vector) part.

    +

    Note - rotation matrices here apply to column vectors, that is, +they are applied on the left of the vector. For example:

    +
    >>> import numpy as np
    +>>> from nibabel.quaternions import quat2mat
    +>>> q = [0, 1, 0, 0] # 180 degree rotation around axis 0
    +>>> M = quat2mat(q) # from this module
    +>>> vec = np.array([1, 2, 3]).reshape((3,1)) # column vector
    +>>> tvec = np.dot(M, vec)
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    angle_axis2mat(theta, vector[, is_normalized])

    Rotation matrix of angle theta around vector

    angle_axis2quat(theta, vector[, is_normalized])

    Quaternion for rotation of angle theta around vector

    conjugate(q)

    Conjugate of quaternion

    eye()

    Return identity quaternion

    fillpositive(xyz[, w2_thresh])

    Compute unit quaternion from last 3 values

    inverse(q)

    Return multiplicative inverse of quaternion q

    isunit(q)

    Return True is this is very nearly a unit quaternion

    mat2quat(M)

    Calculate quaternion corresponding to given rotation matrix

    mult(q1, q2)

    Multiply two quaternions

    nearly_equivalent(q1, q2[, rtol, atol])

    Returns True if q1 and q2 give near equivalent transforms

    norm(q)

    Return norm of quaternion

    quat2angle_axis(quat[, identity_thresh])

    Convert quaternion to rotation of angle around axis

    quat2mat(q)

    Calculate rotation matrix corresponding to quaternion

    rotate_vector(v, q)

    Apply transformation in quaternion q to vector v

    +
    +

    angle_axis2mat

    +
    +
    +nibabel.quaternions.angle_axis2mat(theta, vector, is_normalized=False)
    +

    Rotation matrix of angle theta around vector

    +
    +
    Parameters:
    +
    +
    thetascalar

    angle of rotation

    +
    +
    vector3 element sequence

    vector specifying axis for rotation.

    +
    +
    is_normalizedbool, optional

    True if vector is already normalized (has norm of 1). Default +False

    +
    +
    +
    +
    Returns:
    +
    +
    matarray shape (3,3)

    rotation matrix for specified rotation

    +
    +
    +
    +
    +

    Notes

    +

    From: https://en.wikipedia.org/wiki/Rotation_matrix#Axis_and_angle

    +
    + +
    +
    +

    angle_axis2quat

    +
    +
    +nibabel.quaternions.angle_axis2quat(theta, vector, is_normalized=False)
    +

    Quaternion for rotation of angle theta around vector

    +
    +
    Parameters:
    +
    +
    thetascalar

    angle of rotation

    +
    +
    vector3 element sequence

    vector specifying axis for rotation.

    +
    +
    is_normalizedbool, optional

    True if vector is already normalized (has norm of 1). Default +False

    +
    +
    +
    +
    Returns:
    +
    +
    quat4 element sequence of symbols

    quaternion giving specified rotation

    +
    +
    +
    +
    +

    Notes

    +

    Formula from http://mathworld.wolfram.com/EulerParameters.html

    +

    Examples

    +
    >>> q = angle_axis2quat(np.pi, [1, 0, 0])
    +>>> np.allclose(q, [0, 1, 0,  0])
    +True
    +
    +
    +
    + +
    +
    +

    conjugate

    +
    +
    +nibabel.quaternions.conjugate(q)
    +

    Conjugate of quaternion

    +
    +
    Parameters:
    +
    +
    q4 element sequence

    w, i, j, k of quaternion

    +
    +
    +
    +
    Returns:
    +
    +
    conjqarray shape (4,)

    w, i, j, k of conjugate of q

    +
    +
    +
    +
    +
    + +
    +
    +

    eye

    +
    +
    +nibabel.quaternions.eye()
    +

    Return identity quaternion

    +
    + +
    +
    +

    fillpositive

    +
    +
    +nibabel.quaternions.fillpositive(xyz, w2_thresh=None)
    +

    Compute unit quaternion from last 3 values

    +
    +
    Parameters:
    +
    +
    xyziterable

    iterable containing 3 values, corresponding to quaternion x, y, z

    +
    +
    w2_threshNone or float, optional

    threshold to determine if w squared is non-zero. +If None (default) then w2_thresh set equal to +3 * np.finfo(xyz.dtype).eps, if possible, otherwise +3 * np.finfo(np.float64).eps

    +
    +
    +
    +
    Returns:
    +
    +
    wxyzarray shape (4,)

    Full 4 values of quaternion

    +
    +
    +
    +
    +

    Notes

    +

    If w, x, y, z are the values in the full quaternion, assumes w is +positive.

    +

    Gives error if w*w is estimated to be negative

    +

    w = 0 corresponds to a 180 degree rotation

    +

    The unit quaternion specifies that np.dot(wxyz, wxyz) == 1.

    +

    If w is positive (assumed here), w is given by:

    +

    w = np.sqrt(1.0-(x*x+y*y+z*z))

    +

    w2 = 1.0-(x*x+y*y+z*z) can be near zero, which will lead to +numerical instability in sqrt. Here we use the system maximum +float type to reduce numerical instability

    +

    Examples

    +
    >>> import numpy as np
    +>>> wxyz = fillpositive([0,0,0])
    +>>> np.all(wxyz == [1, 0, 0, 0])
    +True
    +>>> wxyz = fillpositive([1,0,0]) # Corner case; w is 0
    +>>> np.all(wxyz == [0, 1, 0, 0])
    +True
    +>>> np.dot(wxyz, wxyz)
    +1.0
    +
    +
    +
    + +
    +
    +

    inverse

    +
    +
    +nibabel.quaternions.inverse(q)
    +

    Return multiplicative inverse of quaternion q

    +
    +
    Parameters:
    +
    +
    q4 element sequence

    w, i, j, k of quaternion

    +
    +
    +
    +
    Returns:
    +
    +
    invqarray shape (4,)

    w, i, j, k of quaternion inverse

    +
    +
    +
    +
    +
    + +
    +
    +

    isunit

    +
    +
    +nibabel.quaternions.isunit(q)
    +

    Return True is this is very nearly a unit quaternion

    +
    + +
    +
    +

    mat2quat

    +
    +
    +nibabel.quaternions.mat2quat(M)
    +

    Calculate quaternion corresponding to given rotation matrix

    +
    +
    Parameters:
    +
    +
    Marray-like

    3x3 rotation matrix

    +
    +
    +
    +
    Returns:
    +
    +
    q(4,) array

    closest quaternion to input matrix, having positive q[0]

    +
    +
    +
    +
    +

    Notes

    +

    Method claimed to be robust to numerical errors in M

    +

    Constructs quaternion by calculating maximum eigenvector for matrix +K (constructed from input M). Although this is not tested, a +maximum eigenvalue of 1 corresponds to a valid rotation.

    +

    A quaternion q*-1 corresponds to the same rotation as q; thus the +sign of the reconstructed quaternion is arbitrary, and we return +quaternions with positive w (q[0]).

    +

    References

    + +

    Examples

    +
    >>> import numpy as np
    +>>> q = mat2quat(np.eye(3)) # Identity rotation
    +>>> np.allclose(q, [1, 0, 0, 0])
    +True
    +>>> q = mat2quat(np.diag([1, -1, -1]))
    +>>> np.allclose(q, [0, 1, 0, 0]) # 180 degree rotn around axis 0
    +True
    +
    +
    +
    + +
    +
    +

    mult

    +
    +
    +nibabel.quaternions.mult(q1, q2)
    +

    Multiply two quaternions

    +
    +
    Parameters:
    +
    +
    q14 element sequence
    +
    q24 element sequence
    +
    +
    +
    Returns:
    +
    +
    q12shape (4,) array
    +
    +
    +
    +

    Notes

    +

    See : https://en.wikipedia.org/wiki/Quaternions#Hamilton_product

    +
    + +
    +
    +

    nearly_equivalent

    +
    +
    +nibabel.quaternions.nearly_equivalent(q1, q2, rtol=1e-05, atol=1e-08)
    +

    Returns True if q1 and q2 give near equivalent transforms

    +

    q1 may be nearly numerically equal to q2, or nearly equal to q2 * -1 +(because a quaternion multiplied by -1 gives the same transform).

    +
    +
    Parameters:
    +
    +
    q14 element sequence

    w, x, y, z of first quaternion

    +
    +
    q24 element sequence

    w, x, y, z of second quaternion

    +
    +
    +
    +
    Returns:
    +
    +
    equivbool

    True if q1 and q2 are nearly equivalent, False otherwise

    +
    +
    +
    +
    +

    Examples

    +
    >>> q1 = [1, 0, 0, 0]
    +>>> nearly_equivalent(q1, [0, 1, 0, 0])
    +False
    +>>> nearly_equivalent(q1, [1, 0, 0, 0])
    +True
    +>>> nearly_equivalent(q1, [-1, 0, 0, 0])
    +True
    +
    +
    +
    + +
    +
    +

    norm

    +
    +
    +nibabel.quaternions.norm(q)
    +

    Return norm of quaternion

    +
    +
    Parameters:
    +
    +
    q4 element sequence

    w, i, j, k of quaternion

    +
    +
    +
    +
    Returns:
    +
    +
    nscalar

    quaternion norm

    +
    +
    +
    +
    +
    + +
    +
    +

    quat2angle_axis

    +
    +
    +nibabel.quaternions.quat2angle_axis(quat, identity_thresh=None)
    +

    Convert quaternion to rotation of angle around axis

    +
    +
    Parameters:
    +
    +
    quat4 element sequence

    w, x, y, z forming quaternion

    +
    +
    identity_threshNone or scalar, optional

    threshold below which the norm of the vector part of the +quaternion (x, y, z) is deemed to be 0, leading to the identity +rotation. None (the default) leads to a threshold estimated +based on the precision of the input.

    +
    +
    +
    +
    Returns:
    +
    +
    thetascalar

    angle of rotation

    +
    +
    vectorarray shape (3,)

    axis around which rotation occurs

    +
    +
    +
    +
    +

    Notes

    +

    A quaternion for which x, y, z are all equal to 0, is an identity +rotation. In this case we return a 0 angle and an arbitrary +vector, here [1, 0, 0]

    +

    Examples

    +
    >>> theta, vec = quat2angle_axis([0, 1, 0, 0])
    +>>> np.allclose(theta, np.pi)
    +True
    +>>> vec
    +array([1., 0., 0.])
    +
    +
    +

    If this is an identity rotation, we return a zero angle and an +arbitrary vector

    +
    >>> quat2angle_axis([1, 0, 0, 0])
    +(0.0, array([1., 0., 0.]))
    +
    +
    +
    + +
    +
    +

    quat2mat

    +
    +
    +nibabel.quaternions.quat2mat(q)
    +

    Calculate rotation matrix corresponding to quaternion

    +
    +
    Parameters:
    +
    +
    q4 element array-like
    +
    +
    +
    Returns:
    +
    +
    M(3,3) array

    Rotation matrix corresponding to input quaternion q

    +
    +
    +
    +
    +

    Notes

    +

    Rotation matrix applies to column vectors, and is applied to the +left of coordinate vectors. The algorithm here allows non-unit +quaternions.

    +

    References

    +

    Algorithm from +https://en.wikipedia.org/wiki/Rotation_matrix#Quaternion

    +

    Examples

    +
    >>> import numpy as np
    +>>> M = quat2mat([1, 0, 0, 0]) # Identity quaternion
    +>>> np.allclose(M, np.eye(3))
    +True
    +>>> M = quat2mat([0, 1, 0, 0]) # 180 degree rotn around axis 0
    +>>> np.allclose(M, np.diag([1, -1, -1]))
    +True
    +
    +
    +
    + +
    +
    +

    rotate_vector

    +
    +
    +nibabel.quaternions.rotate_vector(v, q)
    +

    Apply transformation in quaternion q to vector v

    +
    +
    Parameters:
    +
    +
    v3 element sequence

    3 dimensional vector

    +
    +
    q4 element sequence

    w, i, j, k of quaternion

    +
    +
    +
    +
    Returns:
    +
    +
    vdasharray shape (3,)

    v rotated by quaternion q

    +
    +
    +
    +
    +

    Notes

    +

    See: +https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation#Describing_rotations_with_quaternions

    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.rstutils.html b/reference/nibabel.rstutils.html new file mode 100644 index 0000000000..31f22945ed --- /dev/null +++ b/reference/nibabel.rstutils.html @@ -0,0 +1,174 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    rstutils

    +

    ReStructured Text utilities

    +
      +
    • Make ReST table given array of values

    • +
    + + + + + + +

    rst_table(cell_values[, row_names, ...])

    Return string for ReST table with entries cell_values

    +
    +

    rst_table

    +
    +
    +nibabel.rstutils.rst_table(cell_values, row_names=None, col_names=None, title='', val_fmt='{0:5.2f}', format_chars=None)
    +

    Return string for ReST table with entries cell_values

    +
    +
    Parameters:
    +
    +
    cell_values(R, C) array-like

    At least 2D. Can be greater than 2D, in which case you should adapt +the val_fmt to deal with the multiple entries that will go in each +cell

    +
    +
    row_namesNone or (R,) length sequence, optional

    Row names. If None, use row[0] etc.

    +
    +
    col_namesNone or (C,) length sequence, optional

    Column names. If None, use col[0] etc.

    +
    +
    titlestr, optional

    Title for table. Add as heading above table

    +
    +
    val_fmtstr, optional

    Format string using string format method mini-language. Converts +the result of cell_values[r, c] to a string to make the cell +contents. Default assumes a floating point value in a 2D cell_values.

    +
    +
    format_charsNone or dict, optional

    With keys ‘down’, ‘along’, ‘thick_long’, ‘cross’ and ‘title_heading’. +Values are characters for: lines going down; lines going along; thick +lines along; two lines crossing; and the title overline / underline. +All missing values filled with rst defaults.

    +
    +
    +
    +
    Returns:
    +
    +
    table_strstr

    Multiline string with ascii table, suitable for printing

    +
    +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.spaces.html b/reference/nibabel.spaces.html new file mode 100644 index 0000000000..851b463a80 --- /dev/null +++ b/reference/nibabel.spaces.html @@ -0,0 +1,238 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    spaces

    +

    Routines to work with spaces

    +

    A space is defined by coordinate axes.

    +

    A voxel space can be expressed by a shape implying an array, where the axes are +the axes of the array.

    +

    A mapped voxel space (mapped voxels) is either:

    +
      +
    • an image, with attributes shape (the voxel space) and affine (the +mapping), or

    • +
    • a length 2 sequence with the same information (shape, affine).

    • +
    + + + + + + + + + +

    slice2volume(index, axis[, shape])

    Affine expressing selection of a single slice from 3D volume

    vox2out_vox(mapped_voxels[, voxel_sizes])

    output-aligned shape, affine for input implied by mapped_voxels

    +
    +

    slice2volume

    +
    +
    +nibabel.spaces.slice2volume(index, axis, shape=None)
    +

    Affine expressing selection of a single slice from 3D volume

    +

    Imagine we have taken a slice from an image data array, s = data[:, :, +index]. This function returns the affine to map the array coordinates of +s to the array coordinates of data.

    +

    This can be useful for resampling a single slice from a volume. For +example, to resample slice k in the space of img1 from the matching +spatial voxel values in img2, you might do something like:

    +
    slice_shape = img1.shape[:2]
    +slice_aff = slice2volume(k, 2)
    +whole_aff = np.linalg.inv(img2.affine).dot(img1.affine.dot(slice_aff))
    +
    +
    +

    and then use whole_aff in scipy.ndimage.affine_transform:

    +
    +

    rzs, trans = to_matvec(whole_aff) +data = img2.get_fdata() +new_slice = scipy.ndimage.affine_transform(data, rzs, trans, slice_shape)

    +
    +
    +
    Parameters:
    +
    +
    indexint

    index of selected slice

    +
    +
    axis{0, 1, 2}

    axis to which index applies

    +
    +
    +
    +
    Returns:
    +
    +
    slice_affshape (4, 3) affine

    Affine relating input coordinates in a slice to output coordinates in +the embedded volume

    +
    +
    +
    +
    +
    + +
    +
    +

    vox2out_vox

    +
    +
    +nibabel.spaces.vox2out_vox(mapped_voxels, voxel_sizes=None)
    +

    output-aligned shape, affine for input implied by mapped_voxels

    +

    The input (voxel) space, and the affine mapping to output space, are given +in mapped_voxels.

    +

    The output space is implied by the affine, we don’t need to know what that +is, we just return something with the same (implied) output space.

    +

    Our job is to work out another voxel space where the voxel array axes and +the output axes are aligned (top left 3 x 3 of affine is diagonal with all +positive entries) and which contains all the voxels of the implied input +image at their correct output space positions, once resampled into the +output voxel space.

    +
    +
    Parameters:
    +
    +
    mapped_voxelsobject or length 2 sequence

    If object, has attributes shape giving input voxel shape, and +affine giving mapping of input voxels to output space. If length 2 +sequence, elements are (shape, affine) with same meaning as above. The +affine is a (4, 4) array-like.

    +
    +
    voxel_sizesNone or sequence

    Gives the diagonal entries of output_affine (except the trailing 1 +for the homogeneous coordinates) (output_affine == np.diag(voxel_sizes ++ [1])). If None, return identity output_affine.

    +
    +
    +
    +
    Returns:
    +
    +
    output_shapesequence

    Shape of output image that has voxel axes aligned to original image +output space axes, and encloses all the voxel data from the original +image implied by input shape.

    +
    +
    output_affine(4, 4) array

    Affine of output image that has voxel axes aligned to the output axes +implied by input affine. Top-left 3 x 3 part of affine is diagonal with +all positive entries. The entries come from voxel_sizes if +specified, or are all 1. If the image is < 3D, then the missing +dimensions will have a 1 in the matching diagonal.

    +
    +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.spatialimages.html b/reference/nibabel.spatialimages.html new file mode 100644 index 0000000000..b9b6b18cfb --- /dev/null +++ b/reference/nibabel.spatialimages.html @@ -0,0 +1,852 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    spatialimages

    +

    A simple spatial image class

    +

    The image class maintains the association between a 3D (or greater) +array, and an affine transform that maps voxel coordinates to some world space. +It also has a header - some standard set of meta-data that is specific to +the image format, and extra - a dictionary container for any other +metadata.

    +

    It has attributes:

    +
    +
      +
    • extra

    • +
    +
    +

    methods:

    +
    +
      +
    • .get_fdata()

    • +
    • .to_filename(fname) - writes data to filename(s) derived from +fname, where the derivation may differ between formats.

    • +
    • to_file_map() - save image to files with which the image is already +associated.

    • +
    +
    +

    properties:

    +
    +
      +
    • shape

    • +
    • affine

    • +
    • header

    • +
    • dataobj

    • +
    +
    +

    classmethods:

    +
    +
      +
    • from_filename(fname) - make instance by loading from filename

    • +
    • from_file_map(fmap) - make instance from file map

    • +
    • instance_to_filename(img, fname) - save img instance to +filename fname.

    • +
    +
    +

    You cannot slice an image, and trying to slice an image generates an +informative TypeError.

    +
    +

    There are several ways of writing data.

    +

    There is the usual way, which is the default:

    +
    img.to_filename(fname)
    +
    +
    +

    and that is, to take the data encapsulated by the image and cast it to +the datatype the header expects, setting any available header scaling +into the header to help the data match.

    +

    You can load the data into an image from file with:

    +
    img.from_filename(fname)
    +
    +
    +

    The image stores its associated files in its file_map attribute. In order +to just save an image, for which you know there is an associated filename, or +other storage, you can do:

    +
    img.to_file_map()
    +
    +
    +

    You can get the data out again with:

    +
    img.get_fdata()
    +
    +
    +

    Less commonly, for some image types that support it, you might want to +fetch out the unscaled array via the object containing the data:

    +
    unscaled_data = img.dataoobj.get_unscaled()
    +
    +
    +

    Analyze-type images (including nifti) support this, but others may not +(MINC, for example).

    +

    Sometimes you might to avoid any loss of precision by making the +data type the same as the input:

    +
    hdr = img.header
    +hdr.set_data_dtype(data.dtype)
    +img.to_filename(fname)
    +
    +
    +
    +
    +

    Files interface

    +

    The image has an attribute file_map. This is a mapping, that has keys +corresponding to the file types that an image needs for storage. For +example, the Analyze data format needs an image and a header +file type for storage:

    +
    >>> import numpy as np
    +>>> import nibabel as nib
    +>>> data = np.arange(24, dtype='f4').reshape((2,3,4))
    +>>> img = nib.AnalyzeImage(data, np.eye(4))
    +>>> sorted(img.file_map)
    +['header', 'image']
    +
    +
    +

    The values of file_map are not in fact files but objects with +attributes filename, fileobj and pos.

    +

    The reason for this interface, is that the contents of files has to +contain enough information so that an existing image instance can save +itself back to the files pointed to in file_map. When a file holder +holds active file-like objects, then these may be affected by the +initial file read; in this case, the contains file-like objects need to +carry the position at which a write (with to_file_map) should place the +data. The file_map contents should therefore be such, that this will +work:

    +
    >>> # write an image to files
    +>>> from io import BytesIO
    +>>> import nibabel as nib
    +>>> file_map = nib.AnalyzeImage.make_file_map()
    +>>> file_map['image'].fileobj = BytesIO()
    +>>> file_map['header'].fileobj = BytesIO()
    +>>> img = nib.AnalyzeImage(data, np.eye(4))
    +>>> img.file_map = file_map
    +>>> img.to_file_map()
    +>>> # read it back again from the written files
    +>>> img2 = nib.AnalyzeImage.from_file_map(file_map)
    +>>> np.all(img2.get_fdata(dtype=np.float32) == data)
    +True
    +>>> # write, read it again
    +>>> img2.to_file_map()
    +>>> img3 = nib.AnalyzeImage.from_file_map(file_map)
    +>>> np.all(img3.get_fdata(dtype=np.float32) == data)
    +True
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    HasDtype(*args, **kwargs)

    HeaderDataError

    Class to indicate error in getting or setting header data

    HeaderTypeError

    Class to indicate error in parameters into header functions

    ImageDataError

    SpatialFirstSlicer(img)

    Slicing interface that returns a new image with an updated affine

    SpatialHeader(data_dtype, shape, zooms)

    Template class to implement header protocol

    SpatialImage(dataobj, affine[, header, ...])

    Template class for volumetric (3D/4D) images

    SpatialProtocol(*args, **kwargs)

    supported_np_types(obj)

    Numpy data types that instance obj supports

    +
    +

    HasDtype

    +
    +
    +class nibabel.spatialimages.HasDtype(*args, **kwargs)
    +

    Bases: Protocol

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    +
    +get_data_dtype() dtype
    +
    + +
    +
    +set_data_dtype(dtype: npt.DTypeLike) None
    +
    + +
    + +
    +
    +

    HeaderDataError

    +
    +
    +class nibabel.spatialimages.HeaderDataError
    +

    Bases: Exception

    +

    Class to indicate error in getting or setting header data

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    + +
    +
    +

    HeaderTypeError

    +
    +
    +class nibabel.spatialimages.HeaderTypeError
    +

    Bases: Exception

    +

    Class to indicate error in parameters into header functions

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    + +
    +
    +

    ImageDataError

    +
    +
    +class nibabel.spatialimages.ImageDataError
    +

    Bases: Exception

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    + +
    +
    +

    SpatialFirstSlicer

    +
    +
    +class nibabel.spatialimages.SpatialFirstSlicer(img: SpatialImgT)
    +

    Bases: Generic[SpatialImgT]

    +

    Slicing interface that returns a new image with an updated affine

    +

    Checks that an image’s first three axes are spatial

    +
    +
    +__init__(img: SpatialImgT)
    +
    + +
    +
    +check_slicing(slicer: object, return_spatial: bool = False) tuple[slice | int | None, ...]
    +

    Canonicalize slicers and check for scalar indices in spatial dims

    +
    +
    Parameters:
    +
    +
    slicerobject

    something that can be used to slice an array as in +arr[sliceobj]

    +
    +
    return_spatialbool

    return only slices along spatial dimensions (x, y, z)

    +
    +
    +
    +
    Returns:
    +
    +
    slicerobject

    Validated slicer object that will slice image’s dataobj +without collapsing spatial dimensions

    +
    +
    +
    +
    +
    + +
    +
    +img: SpatialImgT
    +
    + +
    +
    +slice_affine(slicer: object) ndarray
    +

    Retrieve affine for current image, if sliced by a given index

    +

    Applies scaling if down-sampling is applied, and adjusts the intercept +to account for any cropping.

    +
    +
    Parameters:
    +
    +
    slicerobject

    something that can be used to slice an array as in +arr[sliceobj]

    +
    +
    +
    +
    Returns:
    +
    +
    affine(4,4) ndarray

    Affine with updated scale and intercept

    +
    +
    +
    +
    +
    + +
    + +
    +
    +

    SpatialHeader

    +
    +
    +class nibabel.spatialimages.SpatialHeader(data_dtype: npt.DTypeLike = <class 'numpy.float32'>, shape: Sequence[int] = (0, ), zooms: Sequence[float] | None = None)
    +

    Bases: FileBasedHeader, SpatialProtocol

    +

    Template class to implement header protocol

    +
    +
    +__init__(data_dtype: npt.DTypeLike = <class 'numpy.float32'>, shape: Sequence[int] = (0, ), zooms: Sequence[float] | None = None)
    +
    + +
    +
    +copy() SpatialHdrT
    +

    Copy object to independent representation

    +

    The copy should not be affected by any changes to the original +object.

    +
    + +
    +
    +data_from_fileobj(fileobj: io.IOBase) np.ndarray
    +

    Read binary image data from fileobj

    +
    + +
    +
    +data_layout: Literal['F', 'C'] = 'F'
    +
    + +
    +
    +data_to_fileobj(data: npt.ArrayLike, fileobj: io.IOBase, rescale: bool = True)
    +

    Write array data data as binary to fileobj

    +
    +
    Parameters:
    +
    +
    dataarray-like

    data to write

    +
    +
    fileobjfile-like object

    file-like object implementing ‘write’

    +
    +
    rescale{True, False}, optional

    Whether to try and rescale data to match output dtype specified by +header. For this minimal header, rescale has no effect

    +
    +
    +
    +
    +
    + +
    +
    +default_x_flip: bool = True
    +
    + +
    +
    +classmethod from_header(header: SpatialProtocol | FileBasedHeader | Mapping | None = None) SpatialHdrT
    +
    + +
    +
    +get_base_affine() ndarray
    +
    + +
    +
    +get_best_affine() ndarray
    +
    + +
    +
    +get_data_dtype() dtype
    +
    + +
    +
    +get_data_shape() tuple[int, ...]
    +
    + +
    +
    +get_zooms() tuple[float, ...]
    +
    + +
    +
    +set_data_dtype(dtype: npt.DTypeLike) None
    +
    + +
    +
    +set_data_shape(shape: Sequence[int]) None
    +
    + +
    +
    +set_zooms(zooms: Sequence[float]) None
    +
    + +
    + +
    +
    +

    SpatialImage

    +
    +
    +class nibabel.spatialimages.SpatialImage(dataobj: ArrayLike, affine: np.ndarray | None, header: FileBasedHeader | ty.Mapping | None = None, extra: ty.Mapping | None = None, file_map: FileMap | None = None)
    +

    Bases: DataobjImage

    +

    Template class for volumetric (3D/4D) images

    +

    Initialize image

    +

    The image is a combination of (array-like, affine matrix, header), with +optional metadata in extra, and filename / file-like objects +contained in the file_map mapping.

    +
    +
    Parameters:
    +
    +
    dataobjobject

    Object containing image data. It should be some object that returns an +array from np.asanyarray. It should have a shape attribute +or property

    +
    +
    affineNone or (4,4) array-like

    homogeneous affine giving relationship between voxel coordinates and +world coordinates. Affine can also be None. In this case, +obj.affine also returns None, and the affine as written to disk +will depend on the file format.

    +
    +
    headerNone or mapping or header instance, optional

    metadata for this image format

    +
    +
    extraNone or mapping, optional

    metadata to associate with image that cannot be stored in the +metadata of this image type

    +
    +
    file_mapmapping, optional

    mapping giving file information for this image format

    +
    +
    +
    +
    +
    +
    +__init__(dataobj: ArrayLike, affine: np.ndarray | None, header: FileBasedHeader | ty.Mapping | None = None, extra: ty.Mapping | None = None, file_map: FileMap | None = None)
    +

    Initialize image

    +

    The image is a combination of (array-like, affine matrix, header), with +optional metadata in extra, and filename / file-like objects +contained in the file_map mapping.

    +
    +
    Parameters:
    +
    +
    dataobjobject

    Object containing image data. It should be some object that returns an +array from np.asanyarray. It should have a shape attribute +or property

    +
    +
    affineNone or (4,4) array-like

    homogeneous affine giving relationship between voxel coordinates and +world coordinates. Affine can also be None. In this case, +obj.affine also returns None, and the affine as written to disk +will depend on the file format.

    +
    +
    headerNone or mapping or header instance, optional

    metadata for this image format

    +
    +
    extraNone or mapping, optional

    metadata to associate with image that cannot be stored in the +metadata of this image type

    +
    +
    file_mapmapping, optional

    mapping giving file information for this image format

    +
    +
    +
    +
    +
    + +
    +
    +ImageSlicer
    +

    alias of SpatialFirstSlicer

    +
    + +
    +
    +property affine
    +
    + +
    +
    +as_reoriented(ornt: Sequence[Sequence[int]]) SpatialImgT
    +

    Apply an orientation change and return a new image

    +

    If ornt is identity transform, return the original image, unchanged

    +
    +
    Parameters:
    +
    +
    ornt(n,2) orientation array

    orientation transform. ornt[N,1]` is flip of axis N of the +array implied by `shape`, where 1 means no flip and -1 means +flip.  For example, if ``N==0 and ornt[0,1] == -1, and +there’s an array arr of shape shape, the flip would +correspond to the effect of np.flipud(arr). ornt[:,0] is +the transpose that needs to be done to the implied array, as in +arr.transpose(ornt[:,0])

    +
    +
    +
    +
    +

    Notes

    +

    Subclasses should override this if they have additional requirements +when re-orienting an image.

    +
    + +
    +
    +classmethod from_image(img: SpatialImage | FileBasedImage) SpatialImgT
    +

    Class method to create new instance of own class from img

    +
    +
    Parameters:
    +
    +
    imgspatialimage instance

    In fact, an object with the API of spatialimage - +specifically dataobj, affine, header and extra.

    +
    +
    +
    +
    Returns:
    +
    +
    cimgspatialimage instance

    Image, of our own class

    +
    +
    +
    +
    +
    + +
    +
    +get_data_dtype() dtype
    +
    + +
    +
    +header_class
    +

    alias of SpatialHeader

    +
    + +
    +
    +orthoview() OrthoSlicer3D
    +

    Plot the image using OrthoSlicer3D

    +
    +
    Returns:
    +
    +
    viewerinstance of OrthoSlicer3D

    The viewer.

    +
    +
    +
    +
    +

    Notes

    +

    This requires matplotlib. If a non-interactive backend is used, +consider using viewer.show() (equivalently plt.show()) to show +the figure.

    +
    + +
    +
    +set_data_dtype(dtype: npt.DTypeLike) None
    +
    + +
    +
    +property slicer: SpatialFirstSlicer[SpatialImgT]
    +

    Slicer object that returns cropped and subsampled images

    +

    The image is resliced in the current orientation; no rotation or +resampling is performed, and no attempt is made to filter the image +to avoid aliasing.

    +

    The affine matrix is updated with the new intercept (and scales, if +down-sampling is used), so that all values are found at the same RAS +locations.

    +

    Slicing may include non-spatial dimensions. +However, this method does not currently adjust the repetition time in +the image header.

    +
    + +
    +
    +update_header() None
    +

    Harmonize header with image data and affine

    +
    >>> data = np.zeros((2,3,4))
    +>>> affine = np.diag([1.0,2.0,3.0,1.0])
    +>>> img = SpatialImage(data, affine)
    +>>> img.shape == (2, 3, 4)
    +True
    +>>> img.update_header()
    +>>> img.header.get_data_shape() == (2, 3, 4)
    +True
    +>>> img.header.get_zooms()
    +(1.0, 2.0, 3.0)
    +
    +
    +
    + +
    + +
    +
    +

    SpatialProtocol

    +
    +
    +class nibabel.spatialimages.SpatialProtocol(*args, **kwargs)
    +

    Bases: Protocol

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    +
    +get_data_dtype() dtype
    +
    + +
    +
    +get_data_shape() tuple[int, ...]
    +
    + +
    +
    +get_zooms() tuple[float, ...]
    +
    + +
    + +
    +
    +

    supported_np_types

    +
    +
    +nibabel.spatialimages.supported_np_types(obj: HasDtype) set[type[generic]]
    +

    Numpy data types that instance obj supports

    +
    +
    Parameters:
    +
    +
    objobject

    Object implementing get_data_dtype and set_data_dtype. The object +should raise HeaderDataError for setting unsupported dtypes. The +object will likely be a header or a SpatialImage

    +
    +
    +
    +
    Returns:
    +
    +
    np_typesset

    set of numpy types that obj supports

    +
    +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.spm2analyze.html b/reference/nibabel.spm2analyze.html new file mode 100644 index 0000000000..4d4d325d32 --- /dev/null +++ b/reference/nibabel.spm2analyze.html @@ -0,0 +1,431 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    spm2analyze

    +

    Read / write access to SPM2 version of analyze image format

    + + + + + + + + + +

    Spm2AnalyzeHeader([binaryblock, endianness, ...])

    Class for SPM2 variant of basic Analyze header

    Spm2AnalyzeImage(dataobj, affine[, header, ...])

    Class for SPM2 variant of basic Analyze image

    +
    +

    Spm2AnalyzeHeader

    +
    +
    +class nibabel.spm2analyze.Spm2AnalyzeHeader(binaryblock=None, endianness=None, check=True)
    +

    Bases: Spm99AnalyzeHeader

    +

    Class for SPM2 variant of basic Analyze header

    +

    SPM2 variant adds the following to basic Analyze format:

    +
      +
    • voxel origin;

    • +
    • slope scaling of data;

    • +
    • reading - but not writing - intercept of data.

    • +
    +

    Initialize header from binary data block

    +
    +
    Parameters:
    +
    +
    binaryblock{None, string} optional

    binary block to set into header. By default, None, in +which case we insert the default empty header block

    +
    +
    endianness{None, ‘<’,’>’, other endian code} string, optional

    endianness of the binaryblock. If None, guess endianness +from the data.

    +
    +
    checkbool, optional

    Whether to check content of header in initialization. +Default is True.

    +
    +
    +
    +
    +

    Examples

    +
    >>> hdr1 = AnalyzeHeader() # an empty header
    +>>> hdr1.endianness == native_code
    +True
    +>>> hdr1.get_data_shape()
    +(0,)
    +>>> hdr1.set_data_shape((1,2,3)) # now with some content
    +>>> hdr1.get_data_shape()
    +(1, 2, 3)
    +
    +
    +

    We can set the binary block directly via this initialization. +Here we get it from the header we have just made

    +
    >>> binblock2 = hdr1.binaryblock
    +>>> hdr2 = AnalyzeHeader(binblock2)
    +>>> hdr2.get_data_shape()
    +(1, 2, 3)
    +
    +
    +

    Empty headers are native endian by default

    +
    >>> hdr2.endianness == native_code
    +True
    +
    +
    +

    You can pass valid opposite endian headers with the +endianness parameter. Even empty headers can have +endianness

    +
    >>> hdr3 = AnalyzeHeader(endianness=swapped_code)
    +>>> hdr3.endianness == swapped_code
    +True
    +
    +
    +

    If you do not pass an endianness, and you pass some data, we +will try to guess from the passed data.

    +
    >>> binblock3 = hdr3.binaryblock
    +>>> hdr4 = AnalyzeHeader(binblock3)
    +>>> hdr4.endianness == swapped_code
    +True
    +
    +
    +
    +
    +__init__(binaryblock=None, endianness=None, check=True)
    +

    Initialize header from binary data block

    +
    +
    Parameters:
    +
    +
    binaryblock{None, string} optional

    binary block to set into header. By default, None, in +which case we insert the default empty header block

    +
    +
    endianness{None, ‘<’,’>’, other endian code} string, optional

    endianness of the binaryblock. If None, guess endianness +from the data.

    +
    +
    checkbool, optional

    Whether to check content of header in initialization. +Default is True.

    +
    +
    +
    +
    +

    Examples

    +
    >>> hdr1 = AnalyzeHeader() # an empty header
    +>>> hdr1.endianness == native_code
    +True
    +>>> hdr1.get_data_shape()
    +(0,)
    +>>> hdr1.set_data_shape((1,2,3)) # now with some content
    +>>> hdr1.get_data_shape()
    +(1, 2, 3)
    +
    +
    +

    We can set the binary block directly via this initialization. +Here we get it from the header we have just made

    +
    >>> binblock2 = hdr1.binaryblock
    +>>> hdr2 = AnalyzeHeader(binblock2)
    +>>> hdr2.get_data_shape()
    +(1, 2, 3)
    +
    +
    +

    Empty headers are native endian by default

    +
    >>> hdr2.endianness == native_code
    +True
    +
    +
    +

    You can pass valid opposite endian headers with the +endianness parameter. Even empty headers can have +endianness

    +
    >>> hdr3 = AnalyzeHeader(endianness=swapped_code)
    +>>> hdr3.endianness == swapped_code
    +True
    +
    +
    +

    If you do not pass an endianness, and you pass some data, we +will try to guess from the passed data.

    +
    >>> binblock3 = hdr3.binaryblock
    +>>> hdr4 = AnalyzeHeader(binblock3)
    +>>> hdr4.endianness == swapped_code
    +True
    +
    +
    +
    + +
    +
    +get_slope_inter()
    +

    Get data scaling (slope) and intercept from header data

    +

    Uses the algorithm from SPM2 spm_vol_ana.m by John Ashburner

    +
    +
    Parameters:
    +
    +
    selfheader

    Mapping with fields: +* scl_slope - slope +* scl_inter - possible intercept (SPM2 use - shared by nifti) +* glmax - the (recorded) maximum value in the data (unscaled) +* glmin - recorded minimum unscaled value +* cal_max - the calibrated (scaled) maximum value in the dataset +* cal_min - ditto minimum value

    +
    +
    +
    +
    Returns:
    +
    +
    scl_slopeNone or float

    slope. None if there is no valid scaling from these fields

    +
    +
    scl_interNone or float

    intercept. Also None if there is no valid slope, intercept

    +
    +
    +
    +
    +

    Examples

    +
    >>> fields = {'scl_slope': 1, 'scl_inter': 0, 'glmax': 0, 'glmin': 0,
    +...           'cal_max': 0, 'cal_min': 0}
    +>>> hdr = Spm2AnalyzeHeader()
    +>>> for key, value in fields.items():
    +...     hdr[key] = value
    +>>> hdr.get_slope_inter()
    +(1.0, 0.0)
    +>>> hdr['scl_inter'] = 0.5
    +>>> hdr.get_slope_inter()
    +(1.0, 0.5)
    +>>> hdr['scl_inter'] = np.nan
    +>>> hdr.get_slope_inter()
    +(1.0, 0.0)
    +
    +
    +

    If ‘scl_slope’ is 0, nan or inf, cannot use ‘scl_slope’. +Without valid information in the gl / cal fields, we cannot get +scaling, and return None

    +
    >>> hdr['scl_slope'] = 0
    +>>> hdr.get_slope_inter()
    +(None, None)
    +>>> hdr['scl_slope'] = np.nan
    +>>> hdr.get_slope_inter()
    +(None, None)
    +
    +
    +

    Valid information in the gl AND cal fields are needed

    +
    >>> hdr['cal_max'] = 0.8
    +>>> hdr['cal_min'] = 0.2
    +>>> hdr.get_slope_inter()
    +(None, None)
    +>>> hdr['glmax'] = 110
    +>>> hdr['glmin'] = 10
    +>>> np.allclose(hdr.get_slope_inter(), [0.6/100, 0.2-0.6/100*10])
    +True
    +
    +
    +
    + +
    +
    +classmethod may_contain_header(binaryblock)
    +
    + +
    +
    +template_dtype = dtype([('sizeof_hdr', '<i4'), ('data_type', 'S10'), ('db_name', 'S18'), ('extents', '<i4'), ('session_error', '<i2'), ('regular', 'S1'), ('hkey_un0', 'S1'), ('dim', '<i2', (8,)), ('vox_units', 'S4'), ('cal_units', 'S8'), ('unused1', '<i2'), ('datatype', '<i2'), ('bitpix', '<i2'), ('dim_un0', '<i2'), ('pixdim', '<f4', (8,)), ('vox_offset', '<f4'), ('scl_slope', '<f4'), ('scl_inter', '<f4'), ('funused3', '<f4'), ('cal_max', '<f4'), ('cal_min', '<f4'), ('compressed', '<i4'), ('verified', '<i4'), ('glmax', '<i4'), ('glmin', '<i4'), ('descrip', 'S80'), ('aux_file', 'S24'), ('orient', 'S1'), ('origin', '<i2', (5,)), ('generated', 'S10'), ('scannum', 'S10'), ('patient_id', 'S10'), ('exp_date', 'S10'), ('exp_time', 'S10'), ('hist_un0', 'S3'), ('views', '<i4'), ('vols_added', '<i4'), ('start_field', '<i4'), ('field_skip', '<i4'), ('omax', '<i4'), ('omin', '<i4'), ('smax', '<i4'), ('smin', '<i4')])
    +
    + +
    + +
    +
    +

    Spm2AnalyzeImage

    +
    +
    +class nibabel.spm2analyze.Spm2AnalyzeImage(dataobj, affine, header=None, extra=None, file_map=None, dtype=None)
    +

    Bases: Spm99AnalyzeImage

    +

    Class for SPM2 variant of basic Analyze image

    +

    Initialize image

    +

    The image is a combination of (array-like, affine matrix, header), with +optional metadata in extra, and filename / file-like objects +contained in the file_map mapping.

    +
    +
    Parameters:
    +
    +
    dataobjobject

    Object containing image data. It should be some object that returns an +array from np.asanyarray. It should have a shape attribute +or property

    +
    +
    affineNone or (4,4) array-like

    homogeneous affine giving relationship between voxel coordinates and +world coordinates. Affine can also be None. In this case, +obj.affine also returns None, and the affine as written to disk +will depend on the file format.

    +
    +
    headerNone or mapping or header instance, optional

    metadata for this image format

    +
    +
    extraNone or mapping, optional

    metadata to associate with image that cannot be stored in the +metadata of this image type

    +
    +
    file_mapmapping, optional

    mapping giving file information for this image format

    +
    +
    +
    +
    +
    +
    +__init__(dataobj, affine, header=None, extra=None, file_map=None, dtype=None)
    +

    Initialize image

    +

    The image is a combination of (array-like, affine matrix, header), with +optional metadata in extra, and filename / file-like objects +contained in the file_map mapping.

    +
    +
    Parameters:
    +
    +
    dataobjobject

    Object containing image data. It should be some object that returns an +array from np.asanyarray. It should have a shape attribute +or property

    +
    +
    affineNone or (4,4) array-like

    homogeneous affine giving relationship between voxel coordinates and +world coordinates. Affine can also be None. In this case, +obj.affine also returns None, and the affine as written to disk +will depend on the file format.

    +
    +
    headerNone or mapping or header instance, optional

    metadata for this image format

    +
    +
    extraNone or mapping, optional

    metadata to associate with image that cannot be stored in the +metadata of this image type

    +
    +
    file_mapmapping, optional

    mapping giving file information for this image format

    +
    +
    +
    +
    +
    + +
    +
    +header_class
    +

    alias of Spm2AnalyzeHeader

    +
    + +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.spm99analyze.html b/reference/nibabel.spm99analyze.html new file mode 100644 index 0000000000..8256bcd04f --- /dev/null +++ b/reference/nibabel.spm99analyze.html @@ -0,0 +1,745 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    spm99analyze

    +

    Read / write access to SPM99 version of analyze image format

    + + + + + + + + + + + + +

    Spm99AnalyzeHeader([binaryblock, ...])

    Class for SPM99 variant of basic Analyze header

    Spm99AnalyzeImage(dataobj, affine[, header, ...])

    Class for SPM99 variant of basic Analyze image

    SpmAnalyzeHeader([binaryblock, endianness, ...])

    Basic scaling Spm Analyze header

    +
    +

    Spm99AnalyzeHeader

    +
    +
    +class nibabel.spm99analyze.Spm99AnalyzeHeader(binaryblock=None, endianness=None, check=True)
    +

    Bases: SpmAnalyzeHeader

    +

    Class for SPM99 variant of basic Analyze header

    +

    SPM99 variant adds the following to basic Analyze format:

    +
      +
    • voxel origin;

    • +
    • slope scaling of data.

    • +
    +

    Initialize header from binary data block

    +
    +
    Parameters:
    +
    +
    binaryblock{None, string} optional

    binary block to set into header. By default, None, in +which case we insert the default empty header block

    +
    +
    endianness{None, ‘<’,’>’, other endian code} string, optional

    endianness of the binaryblock. If None, guess endianness +from the data.

    +
    +
    checkbool, optional

    Whether to check content of header in initialization. +Default is True.

    +
    +
    +
    +
    +

    Examples

    +
    >>> hdr1 = AnalyzeHeader() # an empty header
    +>>> hdr1.endianness == native_code
    +True
    +>>> hdr1.get_data_shape()
    +(0,)
    +>>> hdr1.set_data_shape((1,2,3)) # now with some content
    +>>> hdr1.get_data_shape()
    +(1, 2, 3)
    +
    +
    +

    We can set the binary block directly via this initialization. +Here we get it from the header we have just made

    +
    >>> binblock2 = hdr1.binaryblock
    +>>> hdr2 = AnalyzeHeader(binblock2)
    +>>> hdr2.get_data_shape()
    +(1, 2, 3)
    +
    +
    +

    Empty headers are native endian by default

    +
    >>> hdr2.endianness == native_code
    +True
    +
    +
    +

    You can pass valid opposite endian headers with the +endianness parameter. Even empty headers can have +endianness

    +
    >>> hdr3 = AnalyzeHeader(endianness=swapped_code)
    +>>> hdr3.endianness == swapped_code
    +True
    +
    +
    +

    If you do not pass an endianness, and you pass some data, we +will try to guess from the passed data.

    +
    >>> binblock3 = hdr3.binaryblock
    +>>> hdr4 = AnalyzeHeader(binblock3)
    +>>> hdr4.endianness == swapped_code
    +True
    +
    +
    +
    +
    +__init__(binaryblock=None, endianness=None, check=True)
    +

    Initialize header from binary data block

    +
    +
    Parameters:
    +
    +
    binaryblock{None, string} optional

    binary block to set into header. By default, None, in +which case we insert the default empty header block

    +
    +
    endianness{None, ‘<’,’>’, other endian code} string, optional

    endianness of the binaryblock. If None, guess endianness +from the data.

    +
    +
    checkbool, optional

    Whether to check content of header in initialization. +Default is True.

    +
    +
    +
    +
    +

    Examples

    +
    >>> hdr1 = AnalyzeHeader() # an empty header
    +>>> hdr1.endianness == native_code
    +True
    +>>> hdr1.get_data_shape()
    +(0,)
    +>>> hdr1.set_data_shape((1,2,3)) # now with some content
    +>>> hdr1.get_data_shape()
    +(1, 2, 3)
    +
    +
    +

    We can set the binary block directly via this initialization. +Here we get it from the header we have just made

    +
    >>> binblock2 = hdr1.binaryblock
    +>>> hdr2 = AnalyzeHeader(binblock2)
    +>>> hdr2.get_data_shape()
    +(1, 2, 3)
    +
    +
    +

    Empty headers are native endian by default

    +
    >>> hdr2.endianness == native_code
    +True
    +
    +
    +

    You can pass valid opposite endian headers with the +endianness parameter. Even empty headers can have +endianness

    +
    >>> hdr3 = AnalyzeHeader(endianness=swapped_code)
    +>>> hdr3.endianness == swapped_code
    +True
    +
    +
    +

    If you do not pass an endianness, and you pass some data, we +will try to guess from the passed data.

    +
    >>> binblock3 = hdr3.binaryblock
    +>>> hdr4 = AnalyzeHeader(binblock3)
    +>>> hdr4.endianness == swapped_code
    +True
    +
    +
    +
    + +
    +
    +get_best_affine()
    +

    Get affine from header, using SPM origin field if sensible

    +

    The default translations are got from the origin +field, if set, or from the center of the image otherwise.

    +

    Examples

    +
    >>> hdr = Spm99AnalyzeHeader()
    +>>> hdr.set_data_shape((3, 5, 7))
    +>>> hdr.set_zooms((3, 2, 1))
    +>>> hdr.default_x_flip
    +True
    +>>> hdr.get_origin_affine() # from center of image
    +array([[-3.,  0.,  0.,  3.],
    +       [ 0.,  2.,  0., -4.],
    +       [ 0.,  0.,  1., -3.],
    +       [ 0.,  0.,  0.,  1.]])
    +>>> hdr['origin'][:3] = [3,4,5]
    +>>> hdr.get_origin_affine() # using origin
    +array([[-3.,  0.,  0.,  6.],
    +       [ 0.,  2.,  0., -6.],
    +       [ 0.,  0.,  1., -4.],
    +       [ 0.,  0.,  0.,  1.]])
    +>>> hdr['origin'] = 0 # unset origin
    +>>> hdr.set_data_shape((3, 5, 7))
    +>>> hdr.get_origin_affine() # from center of image
    +array([[-3.,  0.,  0.,  3.],
    +       [ 0.,  2.,  0., -4.],
    +       [ 0.,  0.,  1., -3.],
    +       [ 0.,  0.,  0.,  1.]])
    +
    +
    +
    + +
    +
    +get_origin_affine()
    +

    Get affine from header, using SPM origin field if sensible

    +

    The default translations are got from the origin +field, if set, or from the center of the image otherwise.

    +

    Examples

    +
    >>> hdr = Spm99AnalyzeHeader()
    +>>> hdr.set_data_shape((3, 5, 7))
    +>>> hdr.set_zooms((3, 2, 1))
    +>>> hdr.default_x_flip
    +True
    +>>> hdr.get_origin_affine() # from center of image
    +array([[-3.,  0.,  0.,  3.],
    +       [ 0.,  2.,  0., -4.],
    +       [ 0.,  0.,  1., -3.],
    +       [ 0.,  0.,  0.,  1.]])
    +>>> hdr['origin'][:3] = [3,4,5]
    +>>> hdr.get_origin_affine() # using origin
    +array([[-3.,  0.,  0.,  6.],
    +       [ 0.,  2.,  0., -6.],
    +       [ 0.,  0.,  1., -4.],
    +       [ 0.,  0.,  0.,  1.]])
    +>>> hdr['origin'] = 0 # unset origin
    +>>> hdr.set_data_shape((3, 5, 7))
    +>>> hdr.get_origin_affine() # from center of image
    +array([[-3.,  0.,  0.,  3.],
    +       [ 0.,  2.,  0., -4.],
    +       [ 0.,  0.,  1., -3.],
    +       [ 0.,  0.,  0.,  1.]])
    +
    +
    +
    + +
    +
    +set_origin_from_affine(affine)
    +

    Set SPM origin to header from affine matrix.

    +

    The origin field was read but not written by SPM99 and 2. It was +used for storing a central voxel coordinate, that could be used in +aligning the image to some standard position - a proxy for a full +translation vector that was usually stored in a separate matlab .mat +file.

    +

    Nifti uses the space occupied by the SPM origin field for important +other information (the transform codes), so writing the origin will +make the header a confusing Nifti file. If you work with both Analyze +and Nifti, you should probably avoid doing this.

    +
    +
    Parameters:
    +
    +
    affinearray-like, shape (4,4)

    Affine matrix to set

    +
    +
    +
    +
    Returns:
    +
    +
    None
    +
    +
    +
    +

    Examples

    +
    >>> hdr = Spm99AnalyzeHeader()
    +>>> hdr.set_data_shape((3, 5, 7))
    +>>> hdr.set_zooms((3,2,1))
    +>>> hdr.get_origin_affine()
    +array([[-3.,  0.,  0.,  3.],
    +       [ 0.,  2.,  0., -4.],
    +       [ 0.,  0.,  1., -3.],
    +       [ 0.,  0.,  0.,  1.]])
    +>>> affine = np.diag([3,2,1,1])
    +>>> affine[:3,3] = [-6, -6, -4]
    +>>> hdr.set_origin_from_affine(affine)
    +>>> np.all(hdr['origin'][:3] == [3,4,5])
    +True
    +>>> hdr.get_origin_affine()
    +array([[-3.,  0.,  0.,  6.],
    +       [ 0.,  2.,  0., -6.],
    +       [ 0.,  0.,  1., -4.],
    +       [ 0.,  0.,  0.,  1.]])
    +
    +
    +
    + +
    + +
    +
    +

    Spm99AnalyzeImage

    +
    +
    +class nibabel.spm99analyze.Spm99AnalyzeImage(dataobj, affine, header=None, extra=None, file_map=None, dtype=None)
    +

    Bases: AnalyzeImage

    +

    Class for SPM99 variant of basic Analyze image

    +

    Initialize image

    +

    The image is a combination of (array-like, affine matrix, header), with +optional metadata in extra, and filename / file-like objects +contained in the file_map mapping.

    +
    +
    Parameters:
    +
    +
    dataobjobject

    Object containing image data. It should be some object that returns an +array from np.asanyarray. It should have a shape attribute +or property

    +
    +
    affineNone or (4,4) array-like

    homogeneous affine giving relationship between voxel coordinates and +world coordinates. Affine can also be None. In this case, +obj.affine also returns None, and the affine as written to disk +will depend on the file format.

    +
    +
    headerNone or mapping or header instance, optional

    metadata for this image format

    +
    +
    extraNone or mapping, optional

    metadata to associate with image that cannot be stored in the +metadata of this image type

    +
    +
    file_mapmapping, optional

    mapping giving file information for this image format

    +
    +
    +
    +
    +
    +
    +__init__(dataobj, affine, header=None, extra=None, file_map=None, dtype=None)
    +

    Initialize image

    +

    The image is a combination of (array-like, affine matrix, header), with +optional metadata in extra, and filename / file-like objects +contained in the file_map mapping.

    +
    +
    Parameters:
    +
    +
    dataobjobject

    Object containing image data. It should be some object that returns an +array from np.asanyarray. It should have a shape attribute +or property

    +
    +
    affineNone or (4,4) array-like

    homogeneous affine giving relationship between voxel coordinates and +world coordinates. Affine can also be None. In this case, +obj.affine also returns None, and the affine as written to disk +will depend on the file format.

    +
    +
    headerNone or mapping or header instance, optional

    metadata for this image format

    +
    +
    extraNone or mapping, optional

    metadata to associate with image that cannot be stored in the +metadata of this image type

    +
    +
    file_mapmapping, optional

    mapping giving file information for this image format

    +
    +
    +
    +
    +
    + +
    +
    +files_types: tuple[tuple[str, str], ...] = (('image', '.img'), ('header', '.hdr'), ('mat', '.mat'))
    +
    + +
    +
    +classmethod from_file_map(file_map, *, mmap=True, keep_file_open=None)
    +

    Class method to create image from mapping in file_map

    +
    +
    Parameters:
    +
    +
    file_mapdict

    Mapping with (key, value) pairs of (file_type, FileHolder +instance giving file-likes for each file needed for this image +type.

    +
    +
    mmap{True, False, ‘c’, ‘r’}, optional, keyword only

    mmap controls the use of numpy memory mapping for reading image +array data. If False, do not try numpy memmap for data array. +If one of {‘c’, ‘r’}, try numpy memmap with mode=mmap. A +mmap value of True gives the same behavior as mmap='c'. If +image data file cannot be memory-mapped, ignore mmap value and +read array from file.

    +
    +
    keep_file_open{ None, True, False }, optional, keyword only

    keep_file_open controls whether a new file handle is created +every time the image is accessed, or a single file handle is +created and used for the lifetime of this ArrayProxy. If +True, a single file handle is created and used. If False, +a new file handle is created every time the image is accessed. +If file_map refers to an open file handle, this setting has no +effect. The default value (None) will result in the value of +nibabel.arrayproxy.KEEP_FILE_OPEN_DEFAULT being used.

    +
    +
    +
    +
    Returns:
    +
    +
    imgSpm99AnalyzeImage instance
    +
    +
    +
    +
    + +
    +
    +has_affine = True
    +
    + +
    +
    +header_class
    +

    alias of Spm99AnalyzeHeader

    +
    + +
    +
    +makeable: bool = True
    +
    + +
    +
    +rw: bool = True
    +
    + +
    +
    +to_file_map(file_map=None, dtype=None)
    +

    Write image to file_map or contained self.file_map

    +

    Extends Analyze to_file_map method by writing mat file

    +
    +
    Parameters:
    +
    +
    file_mapNone or mapping, optional

    files mapping. If None (default) use object’s file_map +attribute instead

    +
    +
    +
    +
    +
    + +
    + +
    +
    +

    SpmAnalyzeHeader

    +
    +
    +class nibabel.spm99analyze.SpmAnalyzeHeader(binaryblock=None, endianness=None, check=True)
    +

    Bases: AnalyzeHeader

    +

    Basic scaling Spm Analyze header

    +

    Initialize header from binary data block

    +
    +
    Parameters:
    +
    +
    binaryblock{None, string} optional

    binary block to set into header. By default, None, in +which case we insert the default empty header block

    +
    +
    endianness{None, ‘<’,’>’, other endian code} string, optional

    endianness of the binaryblock. If None, guess endianness +from the data.

    +
    +
    checkbool, optional

    Whether to check content of header in initialization. +Default is True.

    +
    +
    +
    +
    +

    Examples

    +
    >>> hdr1 = AnalyzeHeader() # an empty header
    +>>> hdr1.endianness == native_code
    +True
    +>>> hdr1.get_data_shape()
    +(0,)
    +>>> hdr1.set_data_shape((1,2,3)) # now with some content
    +>>> hdr1.get_data_shape()
    +(1, 2, 3)
    +
    +
    +

    We can set the binary block directly via this initialization. +Here we get it from the header we have just made

    +
    >>> binblock2 = hdr1.binaryblock
    +>>> hdr2 = AnalyzeHeader(binblock2)
    +>>> hdr2.get_data_shape()
    +(1, 2, 3)
    +
    +
    +

    Empty headers are native endian by default

    +
    >>> hdr2.endianness == native_code
    +True
    +
    +
    +

    You can pass valid opposite endian headers with the +endianness parameter. Even empty headers can have +endianness

    +
    >>> hdr3 = AnalyzeHeader(endianness=swapped_code)
    +>>> hdr3.endianness == swapped_code
    +True
    +
    +
    +

    If you do not pass an endianness, and you pass some data, we +will try to guess from the passed data.

    +
    >>> binblock3 = hdr3.binaryblock
    +>>> hdr4 = AnalyzeHeader(binblock3)
    +>>> hdr4.endianness == swapped_code
    +True
    +
    +
    +
    +
    +__init__(binaryblock=None, endianness=None, check=True)
    +

    Initialize header from binary data block

    +
    +
    Parameters:
    +
    +
    binaryblock{None, string} optional

    binary block to set into header. By default, None, in +which case we insert the default empty header block

    +
    +
    endianness{None, ‘<’,’>’, other endian code} string, optional

    endianness of the binaryblock. If None, guess endianness +from the data.

    +
    +
    checkbool, optional

    Whether to check content of header in initialization. +Default is True.

    +
    +
    +
    +
    +

    Examples

    +
    >>> hdr1 = AnalyzeHeader() # an empty header
    +>>> hdr1.endianness == native_code
    +True
    +>>> hdr1.get_data_shape()
    +(0,)
    +>>> hdr1.set_data_shape((1,2,3)) # now with some content
    +>>> hdr1.get_data_shape()
    +(1, 2, 3)
    +
    +
    +

    We can set the binary block directly via this initialization. +Here we get it from the header we have just made

    +
    >>> binblock2 = hdr1.binaryblock
    +>>> hdr2 = AnalyzeHeader(binblock2)
    +>>> hdr2.get_data_shape()
    +(1, 2, 3)
    +
    +
    +

    Empty headers are native endian by default

    +
    >>> hdr2.endianness == native_code
    +True
    +
    +
    +

    You can pass valid opposite endian headers with the +endianness parameter. Even empty headers can have +endianness

    +
    >>> hdr3 = AnalyzeHeader(endianness=swapped_code)
    +>>> hdr3.endianness == swapped_code
    +True
    +
    +
    +

    If you do not pass an endianness, and you pass some data, we +will try to guess from the passed data.

    +
    >>> binblock3 = hdr3.binaryblock
    +>>> hdr4 = AnalyzeHeader(binblock3)
    +>>> hdr4.endianness == swapped_code
    +True
    +
    +
    +
    + +
    +
    +classmethod default_structarr(endianness=None)
    +

    Create empty header binary block with given endianness

    +
    + +
    +
    +get_slope_inter()
    +

    Get scalefactor and intercept

    +

    If scalefactor is 0.0 return None to indicate no scalefactor. +Intercept is always None because SPM99 analyze cannot store intercepts.

    +
    + +
    +
    +has_data_intercept = False
    +
    + +
    +
    +has_data_slope = True
    +
    + +
    +
    +set_slope_inter(slope, inter=None)
    +

    Set slope and / or intercept into header

    +

    Set slope and intercept for image data, such that, if the image +data is arr, then the scaled image data will be (arr * +slope) + inter

    +

    The SPM Analyze header can’t save an intercept value, and we raise an +error unless inter is None, NaN or 0

    +
    +
    Parameters:
    +
    +
    slopeNone or float

    If None, implies slope of NaN. NaN is a signal to the image +writing routines to rescale on save. 0, Inf, -Inf are invalid and +cause a HeaderDataError

    +
    +
    interNone or float, optional

    intercept. Must be None, NaN or 0, because SPM99 cannot store +intercepts.

    +
    +
    +
    +
    +
    + +
    +
    +template_dtype = dtype([('sizeof_hdr', '<i4'), ('data_type', 'S10'), ('db_name', 'S18'), ('extents', '<i4'), ('session_error', '<i2'), ('regular', 'S1'), ('hkey_un0', 'S1'), ('dim', '<i2', (8,)), ('vox_units', 'S4'), ('cal_units', 'S8'), ('unused1', '<i2'), ('datatype', '<i2'), ('bitpix', '<i2'), ('dim_un0', '<i2'), ('pixdim', '<f4', (8,)), ('vox_offset', '<f4'), ('scl_slope', '<f4'), ('funused2', '<f4'), ('funused3', '<f4'), ('cal_max', '<f4'), ('cal_min', '<f4'), ('compressed', '<i4'), ('verified', '<i4'), ('glmax', '<i4'), ('glmin', '<i4'), ('descrip', 'S80'), ('aux_file', 'S24'), ('orient', 'S1'), ('origin', '<i2', (5,)), ('generated', 'S10'), ('scannum', 'S10'), ('patient_id', 'S10'), ('exp_date', 'S10'), ('exp_time', 'S10'), ('hist_un0', 'S3'), ('views', '<i4'), ('vols_added', '<i4'), ('start_field', '<i4'), ('field_skip', '<i4'), ('omax', '<i4'), ('omin', '<i4'), ('smax', '<i4'), ('smin', '<i4')])
    +
    + +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.streamlines.html b/reference/nibabel.streamlines.html new file mode 100644 index 0000000000..9a4a27d7b2 --- /dev/null +++ b/reference/nibabel.streamlines.html @@ -0,0 +1,2329 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    streamlines

    +

    Multiformat-capable streamline format read / write interface

    + + + + + + + + + + + + + + + +

    detect_format(fileobj)

    Returns the StreamlinesFile object guessed from the file-like object.

    is_supported(fileobj)

    Checks if the file-like object if supported by NiBabel.

    load(fileobj[, lazy_load])

    Loads streamlines in RAS+ and mm space from a file-like object.

    save(tractogram, filename, **kwargs)

    Saves a tractogram to a file.

    +
    +

    Module: streamlines.array_sequence

    + + + + + + + + + + + + + + + + + + +

    ArraySequence([iterable, buffer_size])

    Sequence of ndarrays having variable first dimension sizes.

    concatenate(seqs, axis)

    Concatenates multiple ArraySequence objects along an axis.

    create_arraysequences_from_generator(gen, n)

    Creates ArraySequence objects from a generator yielding tuples

    is_array_sequence(obj)

    Return True if obj is an array sequence.

    is_ndarray_of_int_or_bool(obj)

    +
    +
    +

    Module: streamlines.header

    +

    Field class defining common header fields in tractogram files

    + + + + + + +

    Field()

    Header fields common to multiple streamline file formats.

    +
    +
    +

    Module: streamlines.tck

    +

    Read / write access to TCK streamlines format.

    +

    TCK format is defined at +http://mrtrix.readthedocs.io/en/latest/getting_started/image_data.html?highlight=format#tracks-file-format-tck

    + + + + + + +

    TckFile(tractogram[, header])

    Convenience class to encapsulate TCK file format.

    +
    +
    +

    Module: streamlines.tractogram

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    LazyDict(*args, **kwargs)

    Dictionary of generator functions.

    LazyTractogram([streamlines, ...])

    Lazy container for streamlines and their data information.

    PerArrayDict([n_rows])

    Dictionary for which key access can do slicing on the values.

    PerArraySequenceDict([n_rows])

    Dictionary for which key access can do slicing on the values.

    SliceableDataDict(*args, **kwargs)

    Dictionary for which key access can do slicing on the values.

    Tractogram([streamlines, ...])

    Container for streamlines and their data information.

    TractogramItem(streamline, ...)

    Class containing information about one streamline.

    is_data_dict(obj)

    True if obj seems to implement the DataDict API

    is_lazy_dict(obj)

    True if obj seems to implement the LazyDict API

    +
    +
    +

    Module: streamlines.tractogram_file

    +

    Define abstract interface for Tractogram file classes

    + + + + + + + + + + + + + + + + + + + + + + + + +

    DataError

    Raised when data is missing or inconsistent in a tractogram file.

    DataWarning

    Base class for warnings about tractogram file data.

    ExtensionWarning

    Base class for warnings about tractogram file extension.

    HeaderError

    Raised when a tractogram file header contains invalid information.

    HeaderWarning

    Base class for warnings about tractogram file header.

    TractogramFile(tractogram[, header])

    Convenience class to encapsulate tractogram file format.

    abstractclassmethod(callable)

    +
    +
    +

    Module: streamlines.trk

    + + + + + + + + + + + + + + + + + + +

    TrkFile(tractogram[, header])

    Convenience class to encapsulate TRK file format.

    decode_value_from_name(encoded_name)

    Decodes a value that has been encoded in the last bytes of a string.

    encode_value_in_name(value, name[, max_name_len])

    Return name as fixed-length string, appending value as string.

    get_affine_rasmm_to_trackvis(header)

    get_affine_trackvis_to_rasmm(header)

    Get affine mapping trackvis voxelmm space to RAS+ mm space

    +
    +
    +

    Module: streamlines.utils

    + + + + + + + + + +

    get_affine_from_reference(ref)

    Returns the affine defining the reference space.

    peek_next(iterable)

    Peek next element of iterable.

    +
    +

    detect_format

    +
    +
    +nibabel.streamlines.detect_format(fileobj)
    +

    Returns the StreamlinesFile object guessed from the file-like object.

    +
    +
    Parameters:
    +
    +
    fileobjstring or file-like object

    If string, a filename; otherwise an open file-like object pointing +to a tractogram file (and ready to read from the beginning of the +header)

    +
    +
    +
    +
    Returns:
    +
    +
    tractogram_fileTractogramFile class

    The class type guessed from the content of fileobj.

    +
    +
    +
    +
    +
    + +
    +
    +

    is_supported

    +
    +
    +nibabel.streamlines.is_supported(fileobj)
    +

    Checks if the file-like object if supported by NiBabel.

    +
    +
    Parameters:
    +
    +
    fileobjstring or file-like object

    If string, a filename; otherwise an open file-like object pointing +to a streamlines file (and ready to read from the beginning of the +header)

    +
    +
    +
    +
    Returns:
    +
    +
    is_supportedboolean
    +
    +
    +
    +
    + +
    +
    +

    load

    +
    +
    +nibabel.streamlines.load(fileobj, lazy_load=False)
    +

    Loads streamlines in RAS+ and mm space from a file-like object.

    +
    +
    Parameters:
    +
    +
    fileobjstring or file-like object

    If string, a filename; otherwise an open file-like object +pointing to a streamlines file (and ready to read from the beginning +of the streamlines file’s header).

    +
    +
    lazy_load{False, True}, optional

    If True, load streamlines in a lazy manner i.e. they will not be kept +in memory and only be loaded when needed. +Otherwise, load all streamlines in memory.

    +
    +
    +
    +
    Returns:
    +
    +
    tractogram_fileTractogramFile object

    Returns an instance of a TractogramFile containing data and +metadata of the tractogram loaded from fileobj.

    +
    +
    +
    +
    +

    Notes

    +

    The streamline coordinate (0,0,0) refers to the center of the voxel.

    +
    + +
    +
    +

    save

    +
    +
    +nibabel.streamlines.save(tractogram, filename, **kwargs)
    +

    Saves a tractogram to a file.

    +
    +
    Parameters:
    +
    +
    tractogramTractogram object or TractogramFile object

    If Tractogram object, the file format will be guessed from +filename and a TractogramFile object will be created using +provided keyword arguments. +If TractogramFile object, the file format is known and will +be used to save its content to filename.

    +
    +
    filenamestr

    Name of the file where the tractogram will be saved.

    +
    +
    **kwargskeyword arguments

    Keyword arguments passed to TractogramFile constructor. +Should not be specified if tractogram is already an instance of +TractogramFile.

    +
    +
    +
    +
    +
    + +
    +
    +

    ArraySequence

    +
    +
    +class nibabel.streamlines.array_sequence.ArraySequence(iterable=None, buffer_size=4)
    +

    Bases: object

    +

    Sequence of ndarrays having variable first dimension sizes.

    +

    This is a container that can store multiple ndarrays where each ndarray +might have a different first dimension size but a common size for the +remaining dimensions.

    +

    More generally, an instance of ArraySequence of length \(N\) is +composed of \(N\) ndarrays of shape \((d_1, d_2, ... d_D)\) where \(d_1\) +can vary in length between arrays but \((d_2, ..., d_D)\) have to be the +same for every ndarray.

    +

    Initialize array sequence instance

    +
    +
    Parameters:
    +
    +
    iterableNone or iterable or ArraySequence, optional

    If None, create an empty ArraySequence object. +If iterable, create a ArraySequence object initialized +from array-like objects yielded by the iterable. +If ArraySequence, create a view (no memory is allocated). +For an actual copy use copy() instead.

    +
    +
    buffer_sizefloat, optional

    Size (in Mb) for memory allocation when iterable is a generator.

    +
    +
    +
    +
    +
    +
    +__init__(iterable=None, buffer_size=4)
    +

    Initialize array sequence instance

    +
    +
    Parameters:
    +
    +
    iterableNone or iterable or ArraySequence, optional

    If None, create an empty ArraySequence object. +If iterable, create a ArraySequence object initialized +from array-like objects yielded by the iterable. +If ArraySequence, create a view (no memory is allocated). +For an actual copy use copy() instead.

    +
    +
    buffer_sizefloat, optional

    Size (in Mb) for memory allocation when iterable is a generator.

    +
    +
    +
    +
    +
    + +
    +
    +append(element, cache_build=False)
    +

    Appends element to this array sequence.

    +

    Append can be a lot faster if it knows that it is appending several +elements instead of a single element. In that case it can cache the +parameters it uses between append operations, in a “build cache”. To +tell append to do this, use cache_build=True. If you use +cache_build=True, you need to finalize the append operations with +finalize_append().

    +
    +
    Parameters:
    +
    +
    elementndarray

    Element to append. The shape must match already inserted elements +shape except for the first dimension.

    +
    +
    cache_build{False, True}

    Whether to save the build cache from this append routine. If True, +append can assume it is the only player updating self, and the +caller must finalize self after all append operations, with +self.finalize_append().

    +
    +
    +
    +
    Returns:
    +
    +
    None
    +
    +
    +
    +

    Notes

    +

    If you need to add multiple elements you should consider +ArraySequence.extend.

    +
    + +
    +
    +property common_shape
    +

    Matching shape of the elements in this array sequence.

    +
    + +
    +
    +copy()
    +

    Creates a copy of this ArraySequence object.

    +
    +
    Returns:
    +
    +
    seq_copyArraySequence instance

    Copy of self.

    +
    +
    +
    +
    +

    Notes

    +

    We do not simply deepcopy this object because we have a chance to use +less memory. For example, if the array sequence being copied is the +result of a slicing operation on an array sequence.

    +
    + +
    +
    +extend(elements)
    +

    Appends all elements to this array sequence.

    +
    +
    Parameters:
    +
    +
    elementsiterable of ndarrays or ArraySequence object

    If iterable of ndarrays, each ndarray will be concatenated along +the first dimension then appended to the data of this +ArraySequence. +If ArraySequence object, its data are simply appended to +the data of this ArraySequence.

    +
    +
    +
    +
    Returns:
    +
    +
    None
    +
    +
    +
    +

    Notes

    +

    The shape of the elements to be added must match the one of the data of +this ArraySequence except for the first dimension.

    +
    + +
    +
    +finalize_append()
    +

    Finalize process of appending several elements to self

    +

    append() can be a lot faster if it knows that it is appending +several elements instead of a single element. To tell the append +method this is the case, use cache_build=True. This method +finalizes the series of append operations after a call to +append() with cache_build=True.

    +
    + +
    +
    +get_data()
    +

    Returns a copy of the elements in this array sequence.

    +

    Notes

    +

    To modify the data on this array sequence, one can use +in-place mathematical operators (e.g., seq += …) or the use +assignment operator (i.e, seq[…] = value).

    +
    + +
    +
    +property is_array_sequence
    +
    + +
    +
    +property is_sliced_view
    +
    + +
    +
    +classmethod load(filename)
    +

    Loads a ArraySequence object from a .npz file.

    +
    + +
    +
    +save(filename)
    +

    Saves this ArraySequence object to a .npz file.

    +
    + +
    +
    +shrink_data()
    +
    + +
    +
    +property total_nb_rows
    +

    Total number of rows in this array sequence.

    +
    + +
    + +
    +
    +

    concatenate

    +
    +
    +nibabel.streamlines.array_sequence.concatenate(seqs, axis)
    +

    Concatenates multiple ArraySequence objects along an axis.

    +
    +
    Parameters:
    +
    +
    seqs: iterable of :class:`ArraySequence` objects

    Sequences to concatenate.

    +
    +
    axisint

    Axis along which the sequences will be concatenated.

    +
    +
    +
    +
    Returns:
    +
    +
    new_seq: ArraySequence object

    New ArraySequence object which is the result of +concatenating multiple sequences along the given axis.

    +
    +
    +
    +
    +
    + +
    +
    +

    create_arraysequences_from_generator

    +
    +
    +nibabel.streamlines.array_sequence.create_arraysequences_from_generator(gen, n, buffer_sizes=None)
    +

    Creates ArraySequence objects from a generator yielding tuples

    +
    +
    Parameters:
    +
    +
    gengenerator

    Generator yielding a size n tuple containing the values to put in the +array sequences.

    +
    +
    nint

    Number of ArraySequences object to create.

    +
    +
    buffer_sizeslist of float, optional

    Sizes (in Mb) for each ArraySequence’s buffer.

    +
    +
    +
    +
    +
    + +
    +
    +

    is_array_sequence

    +
    +
    +nibabel.streamlines.array_sequence.is_array_sequence(obj)
    +

    Return True if obj is an array sequence.

    +
    + +
    +
    +

    is_ndarray_of_int_or_bool

    +
    +
    +nibabel.streamlines.array_sequence.is_ndarray_of_int_or_bool(obj)
    +
    + +
    +
    +

    Field

    +
    +
    +class nibabel.streamlines.header.Field
    +

    Bases: object

    +

    Header fields common to multiple streamline file formats.

    +

    In IPython, use nibabel.streamlines.Field?? to list them.

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    +
    +DIMENSIONS = 'dimensions'
    +
    + +
    +
    +ENDIANNESS = 'endianness'
    +
    + +
    +
    +MAGIC_NUMBER = 'magic_number'
    +
    + +
    +
    +METHOD = 'method'
    +
    + +
    +
    +NB_POINTS = 'nb_points'
    +
    + +
    +
    +NB_PROPERTIES_PER_STREAMLINE = 'nb_properties_per_streamline'
    +
    + +
    +
    +NB_SCALARS_PER_POINT = 'nb_scalars_per_point'
    +
    + +
    +
    +NB_STREAMLINES = 'nb_streamlines'
    +
    + +
    +
    +ORIGIN = 'origin'
    +
    + +
    +
    +STEP_SIZE = 'step_size'
    +
    + +
    +
    +VOXEL_ORDER = 'voxel_order'
    +
    + +
    +
    +VOXEL_SIZES = 'voxel_sizes'
    +
    + +
    +
    +VOXEL_TO_RASMM = 'voxel_to_rasmm'
    +
    + +
    + +
    +
    +

    TckFile

    +
    +
    +class nibabel.streamlines.tck.TckFile(tractogram, header=None)
    +

    Bases: TractogramFile

    +

    Convenience class to encapsulate TCK file format.

    +

    Notes

    +

    MRtrix (so its file format: TCK) considers streamlines coordinates +to be in world space (RAS+ and mm space). MRtrix refers to that space +as the “real” or “scanner” space [1].

    +

    Moreover, when streamlines are mapped back to voxel space [2], a +streamline point located at an integer coordinate (i,j,k) is considered +to be at the center of the corresponding voxel. This is in contrast with +TRK’s internal convention where it would have referred to a corner.

    +

    NiBabel’s streamlines internal representation follows the same +convention as MRtrix.

    + +
    +
    Parameters:
    +
    +
    tractogramTractogram object

    Tractogram that will be contained in this TckFile.

    +
    +
    headerNone or dict, optional

    Metadata associated to this tractogram file. If None, make +default empty header.

    +
    +
    +
    +
    +

    Notes

    +

    Streamlines of the tractogram are assumed to be in RAS+ and mm +space. It is also assumed that when streamlines are mapped back to +voxel space, a streamline point located at an integer coordinate +(i,j,k) is considered to be at the center of the corresponding voxel. +This is in contrast with TRK’s internal convention where it would +have referred to a corner.

    +
    +
    +__init__(tractogram, header=None)
    +
    +
    Parameters:
    +
    +
    tractogramTractogram object

    Tractogram that will be contained in this TckFile.

    +
    +
    headerNone or dict, optional

    Metadata associated to this tractogram file. If None, make +default empty header.

    +
    +
    +
    +
    +

    Notes

    +

    Streamlines of the tractogram are assumed to be in RAS+ and mm +space. It is also assumed that when streamlines are mapped back to +voxel space, a streamline point located at an integer coordinate +(i,j,k) is considered to be at the center of the corresponding voxel. +This is in contrast with TRK’s internal convention where it would +have referred to a corner.

    +
    + +
    +
    +EOF_DELIMITER = array([[inf, inf, inf]], dtype=float32)
    +
    + +
    +
    +FIBER_DELIMITER = array([[nan, nan, nan]], dtype=float32)
    +
    + +
    +
    +MAGIC_NUMBER = b'mrtrix tracks'
    +
    + +
    +
    +SUPPORTS_DATA_PER_POINT = False
    +
    + +
    +
    +SUPPORTS_DATA_PER_STREAMLINE = False
    +
    + +
    +
    +classmethod create_empty_header()
    +

    Return an empty compliant TCK header as dict

    +
    + +
    +
    +classmethod is_correct_format(fileobj)
    +

    Check if the file is in TCK format.

    +
    +
    Parameters:
    +
    +
    fileobjstring or file-like object

    If string, a filename; otherwise an open file-like object in +binary mode pointing to TCK file (and ready to read from the +beginning of the TCK header). Note that calling this function +does not change the file position.

    +
    +
    +
    +
    Returns:
    +
    +
    is_correct_format{True, False}

    Returns True if fileobj is compatible with TCK format, +otherwise returns False.

    +
    +
    +
    +
    +
    + +
    +
    +classmethod load(fileobj, lazy_load=False)
    +

    Loads streamlines from a filename or file-like object.

    +
    +
    Parameters:
    +
    +
    fileobjstring or file-like object

    If string, a filename; otherwise an open file-like object in +binary mode pointing to TCK file (and ready to read from the +beginning of the TCK header). Note that calling this function +does not change the file position.

    +
    +
    lazy_load{False, True}, optional

    If True, load streamlines in a lazy manner i.e. they will not be +kept in memory. Otherwise, load all streamlines in memory.

    +
    +
    +
    +
    Returns:
    +
    +
    tck_fileTckFile object

    Returns an object containing tractogram data and header +information.

    +
    +
    +
    +
    +

    Notes

    +

    Streamlines of the tractogram are assumed to be in RAS+ and mm +space. It is also assumed that when streamlines are mapped back to +voxel space, a streamline point located at an integer coordinate +(i,j,k) is considered to be at the center of the corresponding voxel. +This is in contrast with TRK’s internal convention where it would +have referred to a corner.

    +
    + +
    +
    +save(fileobj)
    +

    Save tractogram to a filename or file-like object using TCK format.

    +
    +
    Parameters:
    +
    +
    fileobjstring or file-like object

    If string, a filename; otherwise an open file-like object in +binary mode pointing to TCK file (and ready to write from the +beginning of the TCK header data).

    +
    +
    +
    +
    +
    + +
    + +
    +
    +

    LazyDict

    +
    +
    +class nibabel.streamlines.tractogram.LazyDict(*args, **kwargs)
    +

    Bases: MutableMapping

    +

    Dictionary of generator functions.

    +

    This container behaves like a dictionary but it makes sure its elements are +callable objects that it assumes are generator functions yielding values. +When getting the element associated with a given key, the element (i.e. a +generator function) is first called before being returned.

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    + +
    +
    +

    LazyTractogram

    +
    +
    +class nibabel.streamlines.tractogram.LazyTractogram(streamlines=None, data_per_streamline=None, data_per_point=None, affine_to_rasmm=None)
    +

    Bases: Tractogram

    +

    Lazy container for streamlines and their data information.

    +

    This container behaves lazily as it uses generator functions to manage +streamlines and their data information. This container is thus memory +friendly since it doesn’t require having all this data loaded in memory.

    +

    Streamlines of a tractogram can be in any coordinate system of your +choice as long as you provide the correct affine_to_rasmm matrix, at +construction time. When applied to streamlines coordinates, that +transformation matrix should bring the streamlines back to world space +(RAS+ and mm space) [3].

    +

    Moreover, when streamlines are mapped back to voxel space [4], a +streamline point located at an integer coordinate (i,j,k) is considered +to be at the center of the corresponding voxel. This is in contrast with +other conventions where it might have referred to a corner.

    +
    +
    Attributes:
    +
    +
    streamlinesgenerator function

    Generator function yielding streamlines. Each streamline is an +ndarray of shape (\(N_t\), 3) where \(N_t\) is the number of points of +streamline \(t\).

    +
    +
    data_per_streamlineinstance of LazyDict

    Dictionary where the items are (str, instantiated generator). +Each key represents a piece of information \(i\) to be kept alongside +every streamline, and its associated value is a generator function +yielding that information via ndarrays of shape (\(P_i\),) where \(P_i\) is +the number of values to store for that particular piece of information +\(i\).

    +
    +
    data_per_pointLazyDict object

    Dictionary where the items are (str, instantiated generator). Each key +represents a piece of information \(i\) to be kept alongside every point +of every streamline, and its associated value is a generator function +yielding that information via ndarrays of shape (\(N_t\), \(M_i\)) where +\(N_t\) is the number of points for a particular streamline \(t\) and \(M_i\) +is the number of values to store for that particular piece of +information \(i\).

    +
    +
    +
    +
    +

    Notes

    +

    LazyTractogram objects do not support indexing currently. +LazyTractogram objects are suited for operations that can be linearized +such as applying an affine transformation or converting streamlines from +one file format to another.

    +

    References

    + +
    +
    Parameters:
    +
    +
    streamlinesgenerator function, optional

    Generator function yielding streamlines. Each streamline is an +ndarray of shape (\(N_t\), 3) where \(N_t\) is the number of points of +streamline \(t\).

    +
    +
    data_per_streamlinedict of generator functions, optional

    Dictionary where the items are (str, generator function). +Each key represents an information \(i\) to be kept alongside every +streamline, and its associated value is a generator function +yielding that information via ndarrays of shape (\(P_i\),) where +\(P_i\) is the number of values to store for that particular +information \(i\).

    +
    +
    data_per_pointdict of generator functions, optional

    Dictionary where the items are (str, generator function). +Each key represents an information \(i\) to be kept alongside every +point of every streamline, and its associated value is a generator +function yielding that information via ndarrays of shape +(\(N_t\), \(M_i\)) where \(N_t\) is the number of points for a particular +streamline \(t\) and \(M_i\) is the number of values to store for +that particular information \(i\).

    +
    +
    affine_to_rasmmndarray of shape (4, 4) or None, optional

    Transformation matrix that brings the streamlines contained in +this tractogram to RAS+ and mm space where coordinate (0,0,0) +refers to the center of the voxel. By default, the streamlines +are in an unknown space, i.e. affine_to_rasmm is None.

    +
    +
    +
    +
    +
    +
    +__init__(streamlines=None, data_per_streamline=None, data_per_point=None, affine_to_rasmm=None)
    +
    +
    Parameters:
    +
    +
    streamlinesgenerator function, optional

    Generator function yielding streamlines. Each streamline is an +ndarray of shape (\(N_t\), 3) where \(N_t\) is the number of points of +streamline \(t\).

    +
    +
    data_per_streamlinedict of generator functions, optional

    Dictionary where the items are (str, generator function). +Each key represents an information \(i\) to be kept alongside every +streamline, and its associated value is a generator function +yielding that information via ndarrays of shape (\(P_i\),) where +\(P_i\) is the number of values to store for that particular +information \(i\).

    +
    +
    data_per_pointdict of generator functions, optional

    Dictionary where the items are (str, generator function). +Each key represents an information \(i\) to be kept alongside every +point of every streamline, and its associated value is a generator +function yielding that information via ndarrays of shape +(\(N_t\), \(M_i\)) where \(N_t\) is the number of points for a particular +streamline \(t\) and \(M_i\) is the number of values to store for +that particular information \(i\).

    +
    +
    affine_to_rasmmndarray of shape (4, 4) or None, optional

    Transformation matrix that brings the streamlines contained in +this tractogram to RAS+ and mm space where coordinate (0,0,0) +refers to the center of the voxel. By default, the streamlines +are in an unknown space, i.e. affine_to_rasmm is None.

    +
    +
    +
    +
    +
    + +
    +
    +apply_affine(affine, lazy=True)
    +

    Applies an affine transformation to the streamlines.

    +

    The transformation given by the affine matrix is applied after any +other pending transformations to the streamline points.

    +
    +
    Parameters:
    +
    +
    affine2D array (4,4)

    Transformation matrix that will be applied on each streamline.

    +
    +
    lazyTrue, optional

    Should always be True for LazyTractogram object. Doing +otherwise will raise a ValueError.

    +
    +
    +
    +
    Returns:
    +
    +
    lazy_tractogramLazyTractogram object

    A copy of this LazyTractogram instance but with a +transformation to be applied on the streamlines.

    +
    +
    +
    +
    +
    + +
    +
    +copy()
    +

    Returns a copy of this LazyTractogram object.

    +
    + +
    +
    +property data
    +
    + +
    +
    +property data_per_point
    +
    + +
    +
    +property data_per_streamline
    +
    + +
    +
    +extend(other)
    +

    Appends the data of another Tractogram.

    +

    Data that will be appended includes the streamlines and the content +of both dictionaries data_per_streamline and data_per_point.

    +
    +
    Parameters:
    +
    +
    otherTractogram object

    Its data will be appended to the data of this tractogram.

    +
    +
    +
    +
    Returns:
    +
    +
    None
    +
    +
    +
    +

    Notes

    +

    The entries in both dictionaries self.data_per_streamline and +self.data_per_point must match respectively those contained in +the other tractogram.

    +
    + +
    +
    +classmethod from_data_func(data_func)
    +

    Creates an instance from a generator function.

    +

    The generator function must yield TractogramItem objects.

    +
    +
    Parameters:
    +
    +
    data_funcgenerator function yielding TractogramItem objects

    Generator function that whenever is called starts yielding +TractogramItem objects that will be used to instantiate a +LazyTractogram.

    +
    +
    +
    +
    Returns:
    +
    +
    lazy_tractogramLazyTractogram object

    New lazy tractogram.

    +
    +
    +
    +
    +
    + +
    +
    +classmethod from_tractogram(tractogram)
    +

    Creates a LazyTractogram object from a Tractogram object.

    +
    +
    Parameters:
    +
    +
    tractogramTractgogram object

    Tractogram from which to create a LazyTractogram object.

    +
    +
    +
    +
    Returns:
    +
    +
    lazy_tractogramLazyTractogram object

    New lazy tractogram.

    +
    +
    +
    +
    +
    + +
    +
    +property streamlines
    +
    + +
    +
    +to_world(lazy=True)
    +

    Brings the streamlines to world space (i.e. RAS+ and mm).

    +

    The transformation is applied after any other pending transformations +to the streamline points.

    +
    +
    Parameters:
    +
    +
    lazyTrue, optional

    Should always be True for LazyTractogram object. Doing +otherwise will raise a ValueError.

    +
    +
    +
    +
    Returns:
    +
    +
    lazy_tractogramLazyTractogram object

    A copy of this LazyTractogram instance but with a +transformation to be applied on the streamlines.

    +
    +
    +
    +
    +
    + +
    + +
    +
    +

    PerArrayDict

    +
    +
    +class nibabel.streamlines.tractogram.PerArrayDict(n_rows=0, *args, **kwargs)
    +

    Bases: SliceableDataDict

    +

    Dictionary for which key access can do slicing on the values.

    +

    This container behaves like a standard dictionary but extends key access to +allow keys for key access to be indices slicing into the contained ndarray +values. The elements must also be ndarrays.

    +

    In addition, it makes sure the amount of data contained in those ndarrays +matches the number of streamlines given at the instantiation of this +instance.

    +
    +
    Parameters:
    +
    +
    n_rowsNone or int, optional

    Number of rows per value in each key, value pair or None for not +specified.

    +
    +
    *args
    +
    **kwargs

    Positional and keyword arguments, passed straight through the dict +constructor.

    +
    +
    +
    +
    +
    +
    +__init__(n_rows=0, *args, **kwargs)
    +
    + +
    +
    +extend(other)
    +

    Appends the elements of another PerArrayDict.

    +

    That is, for each entry in this dictionary, we append the elements +coming from the other dictionary at the corresponding entry.

    +
    +
    Parameters:
    +
    +
    otherPerArrayDict object

    Its data will be appended to the data of this dictionary.

    +
    +
    +
    +
    Returns:
    +
    +
    None
    +
    +
    +
    +

    Notes

    +

    The keys in both dictionaries must be the same.

    +
    + +
    + +
    +
    +

    PerArraySequenceDict

    +
    +
    +class nibabel.streamlines.tractogram.PerArraySequenceDict(n_rows=0, *args, **kwargs)
    +

    Bases: PerArrayDict

    +

    Dictionary for which key access can do slicing on the values.

    +

    This container behaves like a standard dictionary but extends key access to +allow keys for key access to be indices slicing into the contained ndarray +values. The elements must also be ArraySequence.

    +

    In addition, it makes sure the amount of data contained in those array +sequences matches the number of elements given at the instantiation +of the instance.

    +
    +
    +__init__(n_rows=0, *args, **kwargs)
    +
    + +
    + +
    +
    +

    SliceableDataDict

    +
    +
    +class nibabel.streamlines.tractogram.SliceableDataDict(*args, **kwargs)
    +

    Bases: MutableMapping

    +

    Dictionary for which key access can do slicing on the values.

    +

    This container behaves like a standard dictionary but extends key access to +allow keys for key access to be indices slicing into the contained ndarray +values.

    +
    +
    Parameters:
    +
    +
    *args
    +
    **kwargs

    Positional and keyword arguments, passed straight through the dict +constructor.

    +
    +
    +
    +
    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    + +
    +
    +

    Tractogram

    +
    +
    +class nibabel.streamlines.tractogram.Tractogram(streamlines=None, data_per_streamline=None, data_per_point=None, affine_to_rasmm=None)
    +

    Bases: object

    +

    Container for streamlines and their data information.

    +

    Streamlines of a tractogram can be in any coordinate system of your +choice as long as you provide the correct affine_to_rasmm matrix, at +construction time. When applied to streamlines coordinates, that +transformation matrix should bring the streamlines back to world space +(RAS+ and mm space) [5].

    +

    Moreover, when streamlines are mapped back to voxel space [6], a +streamline point located at an integer coordinate (i,j,k) is considered +to be at the center of the corresponding voxel. This is in contrast with +other conventions where it might have referred to a corner.

    +
    +
    Attributes:
    +
    +
    streamlinesArraySequence object

    Sequence of \(T\) streamlines. Each streamline is an ndarray of +shape (\(N_t\), 3) where \(N_t\) is the number of points of +streamline \(t\).

    +
    +
    data_per_streamlinePerArrayDict object

    Dictionary where the items are (str, 2D array). Each key represents a +piece of information \(i\) to be kept alongside every streamline, and its +associated value is a 2D array of shape (\(T\), \(P_i\)) where \(T\) is the +number of streamlines and \(P_i\) is the number of values to store for +that particular piece of information \(i\).

    +
    +
    data_per_pointPerArraySequenceDict object

    Dictionary where the items are (str, ArraySequence). Each key +represents a piece of information \(i\) to be kept alongside every point +of every streamline, and its associated value is an iterable of +ndarrays of shape (\(N_t\), \(M_i\)) where \(N_t\) is the number of points +for a particular streamline \(t\) and \(M_i\) is the number values to store +for that particular piece of information \(i\).

    +
    +
    +
    +
    +

    References

    + +
    +
    Parameters:
    +
    +
    streamlinesiterable of ndarrays or ArraySequence, optional

    Sequence of \(T\) streamlines. Each streamline is an ndarray of +shape (\(N_t\), 3) where \(N_t\) is the number of points of +streamline \(t\).

    +
    +
    data_per_streamlinedict of iterable of ndarrays, optional

    Dictionary where the items are (str, iterable). +Each key represents an information \(i\) to be kept alongside every +streamline, and its associated value is an iterable of ndarrays of +shape (\(P_i\),) where \(P_i\) is the number of scalar values to store +for that particular information \(i\).

    +
    +
    data_per_pointdict of iterable of ndarrays, optional

    Dictionary where the items are (str, iterable). +Each key represents an information \(i\) to be kept alongside every +point of every streamline, and its associated value is an iterable +of ndarrays of shape (\(N_t\), \(M_i\)) where \(N_t\) is the number of +points for a particular streamline \(t\) and \(M_i\) is the number +scalar values to store for that particular information \(i\).

    +
    +
    affine_to_rasmmndarray of shape (4, 4) or None, optional

    Transformation matrix that brings the streamlines contained in +this tractogram to RAS+ and mm space where coordinate (0,0,0) +refers to the center of the voxel. By default, the streamlines +are in an unknown space, i.e. affine_to_rasmm is None.

    +
    +
    +
    +
    +
    +
    +__init__(streamlines=None, data_per_streamline=None, data_per_point=None, affine_to_rasmm=None)
    +
    +
    Parameters:
    +
    +
    streamlinesiterable of ndarrays or ArraySequence, optional

    Sequence of \(T\) streamlines. Each streamline is an ndarray of +shape (\(N_t\), 3) where \(N_t\) is the number of points of +streamline \(t\).

    +
    +
    data_per_streamlinedict of iterable of ndarrays, optional

    Dictionary where the items are (str, iterable). +Each key represents an information \(i\) to be kept alongside every +streamline, and its associated value is an iterable of ndarrays of +shape (\(P_i\),) where \(P_i\) is the number of scalar values to store +for that particular information \(i\).

    +
    +
    data_per_pointdict of iterable of ndarrays, optional

    Dictionary where the items are (str, iterable). +Each key represents an information \(i\) to be kept alongside every +point of every streamline, and its associated value is an iterable +of ndarrays of shape (\(N_t\), \(M_i\)) where \(N_t\) is the number of +points for a particular streamline \(t\) and \(M_i\) is the number +scalar values to store for that particular information \(i\).

    +
    +
    affine_to_rasmmndarray of shape (4, 4) or None, optional

    Transformation matrix that brings the streamlines contained in +this tractogram to RAS+ and mm space where coordinate (0,0,0) +refers to the center of the voxel. By default, the streamlines +are in an unknown space, i.e. affine_to_rasmm is None.

    +
    +
    +
    +
    +
    + +
    +
    +property affine_to_rasmm
    +

    Affine bringing streamlines in this tractogram to RAS+mm.

    +
    + +
    +
    +apply_affine(affine, lazy=False)
    +

    Applies an affine transformation on the points of each streamline.

    +

    If lazy is not specified, this is performed in-place.

    +
    +
    Parameters:
    +
    +
    affinendarray of shape (4, 4)

    Transformation that will be applied to every streamline.

    +
    +
    lazy{False, True}, optional

    If True, streamlines are not transformed in-place and a +LazyTractogram object is returned. Otherwise, streamlines +are modified in-place.

    +
    +
    +
    +
    Returns:
    +
    +
    tractogramTractogram or LazyTractogram object

    Tractogram where the streamlines have been transformed according +to the given affine transformation. If the lazy option is true, +it returns a LazyTractogram object, otherwise it returns a +reference to this Tractogram object with updated +streamlines.

    +
    +
    +
    +
    +
    + +
    +
    +copy()
    +

    Returns a copy of this Tractogram object.

    +
    + +
    +
    +property data_per_point
    +
    + +
    +
    +property data_per_streamline
    +
    + +
    +
    +extend(other)
    +

    Appends the data of another Tractogram.

    +

    Data that will be appended includes the streamlines and the content +of both dictionaries data_per_streamline and data_per_point.

    +
    +
    Parameters:
    +
    +
    otherTractogram object

    Its data will be appended to the data of this tractogram.

    +
    +
    +
    +
    Returns:
    +
    +
    None
    +
    +
    +
    +

    Notes

    +

    The entries in both dictionaries self.data_per_streamline and +self.data_per_point must match respectively those contained in +the other tractogram.

    +
    + +
    +
    +property streamlines
    +
    + +
    +
    +to_world(lazy=False)
    +

    Brings the streamlines to world space (i.e. RAS+ and mm).

    +

    If lazy is not specified, this is performed in-place.

    +
    +
    Parameters:
    +
    +
    lazy{False, True}, optional

    If True, streamlines are not transformed in-place and a +LazyTractogram object is returned. Otherwise, streamlines +are modified in-place.

    +
    +
    +
    +
    Returns:
    +
    +
    tractogramTractogram or LazyTractogram object

    Tractogram where the streamlines have been sent to world space. +If the lazy option is true, it returns a LazyTractogram +object, otherwise it returns a reference to this +Tractogram object with updated streamlines.

    +
    +
    +
    +
    +
    + +
    + +
    +
    +

    TractogramItem

    +
    +
    +class nibabel.streamlines.tractogram.TractogramItem(streamline, data_for_streamline, data_for_points)
    +

    Bases: object

    +

    Class containing information about one streamline.

    +

    TractogramItem objects have three public attributes: streamline, +data_for_streamline, and data_for_points.

    +
    +
    Parameters:
    +
    +
    streamlinendarray shape (N, 3)

    Points of this streamline represented as an ndarray of shape (N, 3) +where N is the number of points.

    +
    +
    data_for_streamlinedict

    Dictionary containing some data associated with this particular +streamline. Each key k is mapped to a ndarray of shape (Pt,), where +Pt is the dimension of the data associated with key k.

    +
    +
    data_for_pointsdict

    Dictionary containing some data associated to each point of this +particular streamline. Each key k is mapped to a ndarray of shape +(Nt, Mk), where Nt is the number of points of this streamline and +Mk is the dimension of the data associated with key k.

    +
    +
    +
    +
    +
    +
    +__init__(streamline, data_for_streamline, data_for_points)
    +
    + +
    + +
    +
    +

    is_data_dict

    +
    +
    +nibabel.streamlines.tractogram.is_data_dict(obj)
    +

    True if obj seems to implement the DataDict API

    +
    + +
    +
    +

    is_lazy_dict

    +
    +
    +nibabel.streamlines.tractogram.is_lazy_dict(obj)
    +

    True if obj seems to implement the LazyDict API

    +
    + +
    +
    +

    DataError

    +
    +
    +class nibabel.streamlines.tractogram_file.DataError
    +

    Bases: Exception

    +

    Raised when data is missing or inconsistent in a tractogram file.

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    + +
    +
    +

    DataWarning

    +
    +
    +class nibabel.streamlines.tractogram_file.DataWarning
    +

    Bases: Warning

    +

    Base class for warnings about tractogram file data.

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    + +
    +
    +

    ExtensionWarning

    +
    +
    +class nibabel.streamlines.tractogram_file.ExtensionWarning
    +

    Bases: Warning

    +

    Base class for warnings about tractogram file extension.

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    + +
    +
    +

    HeaderError

    +
    +
    +class nibabel.streamlines.tractogram_file.HeaderError
    +

    Bases: Exception

    +

    Raised when a tractogram file header contains invalid information.

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    + +
    +
    +

    HeaderWarning

    +
    +
    +class nibabel.streamlines.tractogram_file.HeaderWarning
    +

    Bases: Warning

    +

    Base class for warnings about tractogram file header.

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    + +
    +
    +

    TractogramFile

    +
    +
    +class nibabel.streamlines.tractogram_file.TractogramFile(tractogram, header=None)
    +

    Bases: ABC

    +

    Convenience class to encapsulate tractogram file format.

    +
    +
    +__init__(tractogram, header=None)
    +
    + +
    +
    +property affine
    +

    voxmm -> rasmm affine.

    +
    + +
    +
    +classmethod create_empty_header()
    +

    Returns an empty header for this streamlines file format.

    +
    + +
    +
    +property header
    +
    + +
    +
    +abstract classmethod is_correct_format(fileobj)
    +

    Checks if the file has the right streamlines file format.

    +
    +
    Parameters:
    +
    +
    fileobjstring or file-like object

    If string, a filename; otherwise an open file-like object +pointing to a streamlines file (and ready to read from the +beginning of the header).

    +
    +
    +
    +
    Returns:
    +
    +
    is_correct_format{True, False}

    Returns True if fileobj is in the right streamlines file format, +otherwise returns False.

    +
    +
    +
    +
    +
    + +
    +
    +abstract classmethod load(fileobj, lazy_load=True)
    +

    Loads streamlines from a filename or file-like object.

    +
    +
    Parameters:
    +
    +
    fileobjstring or file-like object

    If string, a filename; otherwise an open file-like object +pointing to a streamlines file (and ready to read from the +beginning of the header).

    +
    +
    lazy_load{False, True}, optional

    If True, load streamlines in a lazy manner i.e. they will not be +kept in memory. Otherwise, load all streamlines in memory.

    +
    +
    +
    +
    Returns:
    +
    +
    tractogram_fileTractogramFile object

    Returns an object containing tractogram data and header +information.

    +
    +
    +
    +
    +
    + +
    +
    +abstract save(fileobj)
    +

    Saves streamlines to a filename or file-like object.

    +
    +
    Parameters:
    +
    +
    fileobjstring or file-like object

    If string, a filename; otherwise an open file-like object +opened and ready to write.

    +
    +
    +
    +
    +
    + +
    +
    +property streamlines
    +
    + +
    +
    +property tractogram
    +
    + +
    + +
    +
    +

    abstractclassmethod

    +
    +
    +class nibabel.streamlines.tractogram_file.abstractclassmethod(callable)
    +

    Bases: classmethod

    +
    +
    +__init__(callable)
    +
    + +
    + +
    +
    +

    TrkFile

    +
    +
    +class nibabel.streamlines.trk.TrkFile(tractogram, header=None)
    +

    Bases: TractogramFile

    +

    Convenience class to encapsulate TRK file format.

    +

    Notes

    +

    TrackVis (so its file format: TRK) considers the streamline coordinate +(0,0,0) to be in the corner of the voxel whereas NiBabel’s streamlines +internal representation (Voxel space) assumes (0,0,0) to be in the +center of the voxel.

    +

    Thus, streamlines are shifted by half a voxel on load and are shifted +back on save.

    +
    +
    Parameters:
    +
    +
    tractogramTractogram object

    Tractogram that will be contained in this TrkFile.

    +
    +
    headerdict, optional

    Metadata associated to this tractogram file.

    +
    +
    +
    +
    +

    Notes

    +

    Streamlines of the tractogram are assumed to be in RAS+ +and mm space where coordinate (0,0,0) refers to the center +of the voxel.

    +
    +
    +__init__(tractogram, header=None)
    +
    +
    Parameters:
    +
    +
    tractogramTractogram object

    Tractogram that will be contained in this TrkFile.

    +
    +
    headerdict, optional

    Metadata associated to this tractogram file.

    +
    +
    +
    +
    +

    Notes

    +

    Streamlines of the tractogram are assumed to be in RAS+ +and mm space where coordinate (0,0,0) refers to the center +of the voxel.

    +
    + +
    +
    +HEADER_SIZE = 1000
    +
    + +
    +
    +MAGIC_NUMBER = b'TRACK'
    +
    + +
    +
    +SUPPORTS_DATA_PER_POINT = True
    +
    + +
    +
    +SUPPORTS_DATA_PER_STREAMLINE = True
    +
    + +
    +
    +classmethod create_empty_header(endianness=None)
    +

    Return an empty compliant TRK header as dict

    +
    + +
    +
    +classmethod is_correct_format(fileobj)
    +

    Check if the file is in TRK format.

    +
    +
    Parameters:
    +
    +
    fileobjstring or file-like object

    If string, a filename; otherwise an open file-like object +pointing to TRK file (and ready to read from the beginning +of the TRK header data). Note that calling this function +does not change the file position.

    +
    +
    +
    +
    Returns:
    +
    +
    is_correct_format{True, False}

    Returns True if fileobj is compatible with TRK format, +otherwise returns False.

    +
    +
    +
    +
    +
    + +
    +
    +classmethod load(fileobj, lazy_load=False)
    +

    Loads streamlines from a filename or file-like object.

    +
    +
    Parameters:
    +
    +
    fileobjstring or file-like object

    If string, a filename; otherwise an open file-like object +pointing to TRK file (and ready to read from the beginning +of the TRK header). Note that calling this function +does not change the file position.

    +
    +
    lazy_load{False, True}, optional

    If True, load streamlines in a lazy manner i.e. they will not be +kept in memory. Otherwise, load all streamlines in memory.

    +
    +
    +
    +
    Returns:
    +
    +
    trk_fileTrkFile object

    Returns an object containing tractogram data and header +information.

    +
    +
    +
    +
    +

    Notes

    +

    Streamlines of the returned tractogram are assumed to be in RAS +and mm space where coordinate (0,0,0) refers to the center of the +voxel.

    +
    + +
    +
    +save(fileobj)
    +

    Save tractogram to a filename or file-like object using TRK format.

    +
    +
    Parameters:
    +
    +
    fileobjstring or file-like object

    If string, a filename; otherwise an open file-like object +pointing to TRK file (and ready to write from the beginning +of the TRK header data).

    +
    +
    +
    +
    +
    + +
    + +
    +
    +

    decode_value_from_name

    +
    +
    +nibabel.streamlines.trk.decode_value_from_name(encoded_name)
    +

    Decodes a value that has been encoded in the last bytes of a string.

    +

    Check encode_value_in_name() to see how the value has been encoded.

    +
    +
    Parameters:
    +
    +
    encoded_namebytes

    Name in which a value has been encoded or not.

    +
    +
    +
    +
    Returns:
    +
    +
    namebytes

    Name without the encoded value.

    +
    +
    valueint

    Value decoded from the name.

    +
    +
    +
    +
    +
    + +
    +
    +

    encode_value_in_name

    +
    +
    +nibabel.streamlines.trk.encode_value_in_name(value, name, max_name_len=20)
    +

    Return name as fixed-length string, appending value as string.

    +

    Form output from name if value <= 1 else name + \ + +str(value).

    +

    Return output as fixed length string length max_name_len, padded with +\.

    +

    This function also verifies that the modified length of name is less than +max_name_len.

    +
    +
    Parameters:
    +
    +
    valueint

    Integer value to encode.

    +
    +
    namestr

    Name to which we may append an ascii / latin-1 representation of +value.

    +
    +
    max_name_lenint, optional

    Maximum length of byte string that output can have.

    +
    +
    +
    +
    Returns:
    +
    +
    encoded_namebytes

    Name maybe followed by \ and ascii / latin-1 representation of +value, padded with \ bytes.

    +
    +
    +
    +
    +
    + +
    +
    +

    get_affine_rasmm_to_trackvis

    +
    +
    +nibabel.streamlines.trk.get_affine_rasmm_to_trackvis(header)
    +
    + +
    +
    +

    get_affine_trackvis_to_rasmm

    +
    +
    +nibabel.streamlines.trk.get_affine_trackvis_to_rasmm(header)
    +

    Get affine mapping trackvis voxelmm space to RAS+ mm space

    +

    The streamlines in a trackvis file are in ‘voxelmm’ space, where the +coordinates refer to the corner of the voxel.

    +

    Compute the affine matrix that will bring them back to RAS+ mm space, where +the coordinates refer to the center of the voxel.

    +
    +
    Parameters:
    +
    +
    headerdict or ndarray

    Dict or numpy structured array containing trackvis header.

    +
    +
    +
    +
    Returns:
    +
    +
    aff_tv2rasshape (4, 4) array

    Affine array mapping coordinates in ‘voxelmm’ space to RAS+ mm space.

    +
    +
    +
    +
    +
    + +
    +
    +

    get_affine_from_reference

    +
    +
    +nibabel.streamlines.utils.get_affine_from_reference(ref)
    +

    Returns the affine defining the reference space.

    +
    +
    Parameters:
    +
    +
    refstr or Nifti1Image object or ndarray shape (4, 4)

    If str then it’s the filename of reference file that will be loaded +using nibabel.load() in order to obtain the affine. +If Nifti1Image object then the affine is obtained from it. +If ndarray shape (4, 4) then it’s the affine.

    +
    +
    +
    +
    Returns:
    +
    +
    affinendarray (4, 4)

    Transformation matrix mapping voxel space to RAS+mm space.

    +
    +
    +
    +
    +
    + +
    +
    +

    peek_next

    +
    +
    +nibabel.streamlines.utils.peek_next(iterable)
    +

    Peek next element of iterable.

    +
    +
    Parameters:
    +
    +
    iterable

    Iterable to peek the next element from.

    +
    +
    +
    +
    Returns:
    +
    +
    next_item

    Element peeked from iterable.

    +
    +
    new_iterable

    Iterable behaving like if the original iterable was untouched.

    +
    +
    +
    +
    +
    + +
    +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.tmpdirs.html b/reference/nibabel.tmpdirs.html new file mode 100644 index 0000000000..9f0b0369d6 --- /dev/null +++ b/reference/nibabel.tmpdirs.html @@ -0,0 +1,260 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    tmpdirs

    +

    Contexts for with statement providing temporary directories

    + + + + + + + + + + + + +

    TemporaryDirectory([suffix, prefix, dir])

    Create and return a temporary directory.

    InGivenDirectory([path])

    Change directory to given directory for duration of with block

    InTemporaryDirectory()

    Create, return, and change directory to a temporary directory

    +
    +

    TemporaryDirectory

    +
    +
    +class nibabel.tmpdirs.TemporaryDirectory(suffix='', prefix='tmp', dir=None)
    +

    Bases: TemporaryDirectory

    +

    Create and return a temporary directory. This has the same +behavior as mkdtemp but can be used as a context manager.

    +

    Upon exiting the context, the directory and everything contained +in it are removed.

    +

    Please use the standard library tempfile.TemporaryDirectory

    +
      +
    • deprecated from version: 5.0

    • +
    • Will raise <class ‘nibabel.deprecator.ExpiredDeprecationError’> as of version: 7.0

    • +
    +

    Examples

    +
    >>> import os
    +>>> with TemporaryDirectory() as tmpdir:
    +...     fname = os.path.join(tmpdir, 'example_file.txt')
    +...     with open(fname, 'wt') as fobj:
    +...         _ = fobj.write('a string\n')
    +>>> os.path.exists(tmpdir)
    +False
    +
    +
    +
    +
    +__init__(suffix='', prefix='tmp', dir=None)
    +

    Please use the standard library tempfile.TemporaryDirectory

    +
      +
    • deprecated from version: 5.0

    • +
    • Will raise <class ‘nibabel.deprecator.ExpiredDeprecationError’> as of version: 7.0

    • +
    +

    Examples

    +
    >>> import os
    +>>> with TemporaryDirectory() as tmpdir:
    +...     fname = os.path.join(tmpdir, 'example_file.txt')
    +...     with open(fname, 'wt') as fobj:
    +...         _ = fobj.write('a string\n')
    +>>> os.path.exists(tmpdir)
    +False
    +
    +
    +
    + +
    + +
    +
    +

    InGivenDirectory

    +
    +
    +nibabel.tmpdirs.InGivenDirectory(path=None)
    +

    Change directory to given directory for duration of with block

    +

    Useful when you want to use InTemporaryDirectory for the final test, but +you are still debugging. For example, you may want to do this in the end:

    +
    >>> with InTemporaryDirectory() as tmpdir:
    +...     # do something complicated which might break
    +...     pass
    +
    +
    +

    But indeed the complicated thing does break, and meanwhile the +InTemporaryDirectory context manager wiped out the directory with the +temporary files that you wanted for debugging. So, while debugging, you +replace with something like:

    +
    >>> with InGivenDirectory() as tmpdir: # Use working directory by default
    +...     # do something complicated which might break
    +...     pass
    +
    +
    +

    You can then look at the temporary file outputs to debug what is happening, +fix, and finally replace InGivenDirectory with InTemporaryDirectory +again.

    +
    +
    Parameters:
    +
    +
    pathNone or str, optional

    path to change directory to, for duration of with block. +Defaults to os.getcwd() if None

    +
    +
    +
    +
    +
    + +
    +
    +

    InTemporaryDirectory

    +
    +
    +nibabel.tmpdirs.InTemporaryDirectory()
    +

    Create, return, and change directory to a temporary directory

    +

    Notes

    +

    As its name suggests, the class temporarily changes the working +directory of the Python process, and this is not thread-safe. We suggest +using it only for tests.

    +

    Examples

    +
    >>> import os
    +>>> from pathlib import Path
    +>>> my_cwd = os.getcwd()
    +>>> with InTemporaryDirectory() as tmpdir:
    +...     _ = Path('test.txt').write_text('some text')
    +...     assert os.path.isfile('test.txt')
    +...     assert os.path.isfile(os.path.join(tmpdir, 'test.txt'))
    +>>> os.path.exists(tmpdir)
    +False
    +>>> os.getcwd() == my_cwd
    +True
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.tripwire.html b/reference/nibabel.tripwire.html new file mode 100644 index 0000000000..c292e1555e --- /dev/null +++ b/reference/nibabel.tripwire.html @@ -0,0 +1,206 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    tripwire

    +

    Class to raise error for missing modules or other misfortunes

    + + + + + + + + + + + + +

    TripWire(msg)

    Class raising error if used

    TripWireError

    Exception if trying to use TripWire object

    is_tripwire(obj)

    Returns True if obj appears to be a TripWire object

    +
    +

    TripWire

    +
    +
    +class nibabel.tripwire.TripWire(msg: str)
    +

    Bases: object

    +

    Class raising error if used

    +

    Standard use is to proxy modules that we could not import

    +

    Examples

    +
    >>> a_module = TripWire('We do not have a_module')
    +>>> a_module.do_silly_thing('with silly string') 
    +Traceback (most recent call last):
    +    ...
    +TripWireError: We do not have a_module
    +
    +
    +
    +
    +__init__(msg: str) None
    +
    + +
    + +
    +
    +

    TripWireError

    +
    +
    +class nibabel.tripwire.TripWireError
    +

    Bases: AttributeError

    +

    Exception if trying to use TripWire object

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    + +
    +
    +

    is_tripwire

    +
    +
    +nibabel.tripwire.is_tripwire(obj: Any) bool
    +

    Returns True if obj appears to be a TripWire object

    +

    Examples

    +
    >>> is_tripwire(object())
    +False
    +>>> is_tripwire(TripWire('some message'))
    +True
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.viewers.html b/reference/nibabel.viewers.html new file mode 100644 index 0000000000..f9078d81b7 --- /dev/null +++ b/reference/nibabel.viewers.html @@ -0,0 +1,319 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    viewers

    +

    Utilities for viewing images

    +

    Includes version of OrthoSlicer3D code originally written by our own +Paul Ivanov.

    + + + + + + +

    OrthoSlicer3D(data[, affine, axes, title])

    Orthogonal-plane slice viewer

    +
    +

    OrthoSlicer3D

    +
    +
    +class nibabel.viewers.OrthoSlicer3D(data, affine=None, axes=None, title=None)
    +

    Bases: object

    +

    Orthogonal-plane slice viewer

    +

    OrthoSlicer3d expects 3- or 4-dimensional array data. It treats +4D data as a sequence of 3D spatial volumes, where a slice over the final +array axis gives a single 3D spatial volume.

    +

    For 3D data, the default behavior is to create a figure with 3 axes, one +for each slice orientation of the spatial volume.

    +

    Clicking and dragging the mouse in any one axis will select out the +corresponding slices in the other two. Scrolling up and +down moves the slice up and down in the current axis.

    +

    For 4D data, the fourth figure axis can be used to control which +3D volume is displayed. Alternatively, the - key can be used to +decrement the displayed volume and the + or = keys can be used to +increment it.

    +

    Examples

    +
    >>> import numpy as np
    +>>> a = np.sin(np.linspace(0, np.pi, 20))
    +>>> b = np.sin(np.linspace(0, np.pi*5, 20))
    +>>> data = np.outer(a, b)[..., np.newaxis] * a
    +>>> OrthoSlicer3D(data).show()  
    +
    +
    +
    +
    Parameters:
    +
    +
    dataarray-like

    The data that will be displayed by the slicer. Should have 3+ +dimensions.

    +
    +
    affinearray-like or None, optional

    Affine transform for the data. This is used to determine +how the data should be sliced for plotting into the sagittal, +coronal, and axial view axes. If None, identity is assumed. +The aspect ratio of the data are inferred from the affine +transform.

    +
    +
    axestuple of mpl.Axes or None, optional

    3 or 4 axes instances for the 3 slices plus volumes, +or None (default).

    +
    +
    titlestr or None, optional

    The title to display. Can be None (default) to display no +title.

    +
    +
    +
    +
    +
    +
    +__init__(data, affine=None, axes=None, title=None)
    +
    +
    Parameters:
    +
    +
    dataarray-like

    The data that will be displayed by the slicer. Should have 3+ +dimensions.

    +
    +
    affinearray-like or None, optional

    Affine transform for the data. This is used to determine +how the data should be sliced for plotting into the sagittal, +coronal, and axial view axes. If None, identity is assumed. +The aspect ratio of the data are inferred from the affine +transform.

    +
    +
    axestuple of mpl.Axes or None, optional

    3 or 4 axes instances for the 3 slices plus volumes, +or None (default).

    +
    +
    titlestr or None, optional

    The title to display. Can be None (default) to display no +title.

    +
    +
    +
    +
    +
    + +
    +
    +property clim
    +

    The current color limits

    +
    + +
    +
    +close()
    +

    Close the viewer figures

    +
    + +
    +
    +property cmap
    +

    The current colormap

    +
    + +
    +
    +draw()
    +

    Redraw the current image

    +
    + +
    +
    +property figs
    +

    A tuple of the figure(s) containing the axes

    +
    + +
    + +

    Link positional changes between two canvases

    +
    +
    Parameters:
    +
    +
    otherinstance of OrthoSlicer3D

    Other viewer to use to link movements.

    +
    +
    +
    +
    +
    + +
    +
    +property n_volumes
    +

    Number of volumes in the data

    +
    + +
    +
    +property position
    +

    The current coordinates

    +
    + +
    +
    +set_position(x=None, y=None, z=None)
    +

    Set current displayed slice indices

    +
    +
    Parameters:
    +
    +
    xfloat | None

    X coordinate to use. If None, do not change.

    +
    +
    yfloat | None

    Y coordinate to use. If None, do not change.

    +
    +
    zfloat | None

    Z coordinate to use. If None, do not change.

    +
    +
    +
    +
    +
    + +
    +
    +set_volume_idx(v)
    +

    Set current displayed volume index

    +
    +
    Parameters:
    +
    +
    vint

    Volume index.

    +
    +
    +
    +
    +
    + +
    +
    +show()
    +

    Show the slicer in blocking mode; convenience for plt.show()

    +
    + +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.volumeutils.html b/reference/nibabel.volumeutils.html new file mode 100644 index 0000000000..5b0be8df44 --- /dev/null +++ b/reference/nibabel.volumeutils.html @@ -0,0 +1,1123 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    volumeutils

    +

    Utility functions for analyze-like formats

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    DtypeMapper()

    Specialized mapper for numpy dtypes

    Recoder(codes, fields, map_maker, ...)

    class to return canonical code(s) from code or aliases

    apply_read_scaling(arr[, slope, inter])

    Apply scaling in slope and inter to array arr

    array_from_file(shape, in_dtype, infile[, ...])

    Get array from file with specified shape, dtype and file offset

    array_to_file(data, fileobj[, out_dtype, ...])

    Helper function for writing arrays to file objects

    best_write_scale_ftype(arr[, slope, inter, ...])

    Smallest float type to contain range of arr after scaling

    better_float_of(first, second[, default])

    Return more capable float type of first and second

    finite_range()

    Get range (min, max) or range and flag (min, max, has_nan) from arr

    fname_ext_ul_case(fname)

    fname with ext changed to upper / lower case if file exists

    int_scinter_ftype(ifmt[, slope, inter, default])

    float type containing int type ifmt * slope + inter

    make_dt_codes(codes_seqs)

    Create full dt codes Recoder instance from datatype codes

    pretty_mapping(mapping[, getterfunc])

    Make pretty string from mapping

    rec2dict(rec)

    Convert recarray to dictionary

    seek_tell(fileobj, offset[, write0])

    Seek in fileobj or check we're in the right place already

    shape_zoom_affine(shape, zooms[, x_flip])

    Get affine implied by given shape and zooms

    working_type(in_type[, slope, inter])

    Return array type from applying slope, inter to array of in_type

    write_zeros(fileobj, count[, block_size])

    Write count zero bytes to fileobj

    +
    +

    DtypeMapper

    +
    +
    +class nibabel.volumeutils.DtypeMapper
    +

    Bases: dict[Hashable, Hashable]

    +

    Specialized mapper for numpy dtypes

    +

    We pass this mapper into the Recoder class to deal with numpy dtype +hashing.

    +

    The hashing problem is that dtypes that compare equal may not have the same +hash. This is true for numpys up to the current at time of writing +(1.6.0). For numpy 1.2.1 at least, even dtypes that look exactly the same +in terms of fields don’t always have the same hash. This makes dtypes +difficult to use as keys in a dictionary.

    +

    This class wraps a dictionary in order to implement a __getitem__ to deal +with dtype hashing. If the key doesn’t appear to be in the mapping, and it +is a dtype, we compare (using ==) all known dtype keys to the input key, +and return any matching values for the matching key.

    +
    +
    +__init__() None
    +
    + +
    + +
    +
    +

    Recoder

    +
    +
    +class nibabel.volumeutils.Recoder(codes: ~typing.Sequence[~typing.Sequence[~typing.Hashable]], fields: ~typing.Sequence[str] = ('code',), map_maker: type[~typing.Mapping[~typing.Hashable, ~typing.Hashable]] = <class 'dict'>)
    +

    Bases: object

    +

    class to return canonical code(s) from code or aliases

    +

    The concept is a lot easier to read in the implementation and +tests than it is to explain, so…

    +
    >>> # If you have some codes, and several aliases, like this:
    +>>> code1 = 1; aliases1=['one', 'first']
    +>>> code2 = 2; aliases2=['two', 'second']
    +>>> # You might want to do this:
    +>>> codes = [[code1]+aliases1,[code2]+aliases2]
    +>>> recodes = Recoder(codes)
    +>>> recodes.code['one']
    +1
    +>>> recodes.code['second']
    +2
    +>>> recodes.code[2]
    +2
    +>>> # Or maybe you have a code, a label and some aliases
    +>>> codes=((1,'label1','one', 'first'),(2,'label2','two'))
    +>>> # you might want to get back the code or the label
    +>>> recodes = Recoder(codes, fields=('code','label'))
    +>>> recodes.code['first']
    +1
    +>>> recodes.code['label1']
    +1
    +>>> recodes.label[2]
    +'label2'
    +>>> # For convenience, you can get the first entered name by
    +>>> # indexing the object directly
    +>>> recodes[2]
    +2
    +
    +
    +

    Create recoder object

    +

    codes give a sequence of code, alias sequences +fields are names by which the entries in these sequences can be +accessed.

    +

    By default fields gives the first column the name +“code”. The first column is the vector of first entries +in each of the sequences found in codes. Thence you can +get the equivalent first column value with ob.code[value], +where value can be a first column value, or a value in any of +the other columns in that sequence.

    +

    You can give other columns names too, and access them in the +same way - see the examples in the class docstring.

    +
    +
    Parameters:
    +
    +
    codessequence of sequences

    Each sequence defines values (codes) that are equivalent

    +
    +
    fields{(‘code’,) string sequence}, optional

    names by which elements in sequences can be accessed

    +
    +
    map_maker: callable, optional

    constructor for dict-like objects used to store key value pairs. +Default is dict. map_maker() generates an empty mapping. +The mapping need only implement __getitem__, __setitem__, keys, +values.

    +
    +
    +
    +
    +
    +
    +__init__(codes: ~typing.Sequence[~typing.Sequence[~typing.Hashable]], fields: ~typing.Sequence[str] = ('code',), map_maker: type[~typing.Mapping[~typing.Hashable, ~typing.Hashable]] = <class 'dict'>)
    +

    Create recoder object

    +

    codes give a sequence of code, alias sequences +fields are names by which the entries in these sequences can be +accessed.

    +

    By default fields gives the first column the name +“code”. The first column is the vector of first entries +in each of the sequences found in codes. Thence you can +get the equivalent first column value with ob.code[value], +where value can be a first column value, or a value in any of +the other columns in that sequence.

    +

    You can give other columns names too, and access them in the +same way - see the examples in the class docstring.

    +
    +
    Parameters:
    +
    +
    codessequence of sequences

    Each sequence defines values (codes) that are equivalent

    +
    +
    fields{(‘code’,) string sequence}, optional

    names by which elements in sequences can be accessed

    +
    +
    map_maker: callable, optional

    constructor for dict-like objects used to store key value pairs. +Default is dict. map_maker() generates an empty mapping. +The mapping need only implement __getitem__, __setitem__, keys, +values.

    +
    +
    +
    +
    +
    + +
    +
    +add_codes(code_syn_seqs: Sequence[Sequence[Hashable]]) None
    +

    Add codes to object

    +
    +
    Parameters:
    +
    +
    code_syn_seqssequence

    sequence of sequences, where each sequence S = code_syn_seqs[n] +for n in 0..len(code_syn_seqs), is a sequence giving values in the +same order as self.fields. Each S should be at least of the +same length as self.fields. After this call, if self.fields +== ['field1', 'field2'], then ``self.field1[S[n]] == S[0] for all +n in 0..len(S) and self.field2[S[n]] == S[1] for all n in +0..len(S).

    +
    +
    +
    +
    +

    Examples

    +
    >>> code_syn_seqs = ((2, 'two'), (1, 'one'))
    +>>> rc = Recoder(code_syn_seqs)
    +>>> rc.value_set() == set((1,2))
    +True
    +>>> rc.add_codes(((3, 'three'), (1, 'first')))
    +>>> rc.value_set() == set((1,2,3))
    +True
    +>>> print(rc.value_set())  # set is actually ordered
    +OrderedSet([2, 1, 3])
    +
    +
    +
    + +
    +
    +fields: tuple[str, ...]
    +
    + +
    +
    +keys()
    +

    Return all available code and alias values

    +

    Returns same value as obj.field1.keys() and, with the +default initializing fields argument of fields=(‘code’,), +this will return the same as obj.code.keys()

    +
    >>> codes = ((1, 'one'), (2, 'two'), (1, 'repeat value'))
    +>>> k = Recoder(codes).keys()
    +>>> set(k) == set([1, 2, 'one', 'repeat value', 'two'])
    +True
    +
    +
    +
    + +
    +
    +value_set(name: str | None = None) OrderedSet
    +

    Return OrderedSet of possible returned values for column

    +

    By default, the column is the first column.

    +

    Returns same values as set(obj.field1.values()) and, +with the default initializing``fields`` argument of +fields=(‘code’,), this will return the same as +set(obj.code.values())

    +
    +
    Parameters:
    +
    +
    name{None, string}

    Where default of none gives result for first column

    +
    +
    >>> codes = ((1, ‘one’), (2, ‘two’), (1, ‘repeat value’))
    +
    >>> vs = Recoder(codes).value_set()
    +
    >>> vs == set([1, 2]) # Sets are not ordered, hence this test
    +
    True
    +
    >>> rc = Recoder(codes, fields=(‘code’, ‘label’))
    +
    >>> rc.value_set(‘label’) == set((‘one’, ‘two’, ‘repeat value’))
    +
    True
    +
    +
    +
    +
    + +
    + +
    +
    +

    apply_read_scaling

    +
    +
    +nibabel.volumeutils.apply_read_scaling(arr: np.ndarray, slope: Scalar | None = None, inter: Scalar | None = None) np.ndarray
    +

    Apply scaling in slope and inter to array arr

    +

    This is for loading the array from a file (as opposed to the reverse +scaling when saving an array to file)

    +

    Return data will be arr * slope + inter. The trick is that we have to +find a good precision to use for applying the scaling. The heuristic is +that the data is always upcast to the higher of the types from arr, +`slope, inter if slope and / or inter are not default values. If the +dtype of arr is an integer, then we assume the data more or less fills +the integer range, and upcast to a type such that the min, max of +arr.dtype * scale + inter, will be finite.

    +
    +
    Parameters:
    +
    +
    arrarray-like
    +
    slopeNone or float, optional

    slope value to apply to arr (arr * slope + inter). None +corresponds to a value of 1.0

    +
    +
    interNone or float, optional

    intercept value to apply to arr (arr * slope + inter). None +corresponds to a value of 0.0

    +
    +
    +
    +
    Returns:
    +
    +
    retarray

    array with scaling applied. Maybe upcast in order to give room for the +scaling. If scaling is default (1, 0), then ret may be arr ret is +arr.

    +
    +
    +
    +
    +
    + +
    +
    +

    array_from_file

    +
    +
    +nibabel.volumeutils.array_from_file(shape: tuple[int, ...], in_dtype: np.dtype[DT], infile: io.IOBase, offset: int = 0, order: ty.Literal['C', 'F'] = 'F', mmap: bool | ty.Literal['c', 'r', 'r+'] = True) npt.NDArray[DT]
    +

    Get array from file with specified shape, dtype and file offset

    +
    +
    Parameters:
    +
    +
    shapesequence

    sequence specifying output array shape

    +
    +
    in_dtypenumpy dtype

    fully specified numpy dtype, including correct endianness

    +
    +
    infilefile-like

    open file-like object implementing at least read() and seek()

    +
    +
    offsetint, optional

    offset in bytes into infile to start reading array data. Default is 0

    +
    +
    order{‘F’, ‘C’} string

    order in which to write data. Default is ‘F’ (fortran order).

    +
    +
    mmap{True, False, ‘c’, ‘r’, ‘r+’}

    mmap controls the use of numpy memory mapping for reading data. If +False, do not try numpy memmap for data array. If one of {‘c’, +‘r’, ‘r+’}, try numpy memmap with mode=mmap. A mmap value of +True gives the same behavior as mmap='c'. If infile cannot be +memory-mapped, ignore mmap value and read array from file.

    +
    +
    +
    +
    Returns:
    +
    +
    arrarray-like

    array like object that can be sliced, containing data

    +
    +
    +
    +
    +

    Examples

    +
    >>> from io import BytesIO
    +>>> bio = BytesIO()
    +>>> arr = np.arange(6).reshape(1,2,3)
    +>>> _ = bio.write(arr.tobytes('F'))  # outputs int
    +>>> arr2 = array_from_file((1,2,3), arr.dtype, bio)
    +>>> np.all(arr == arr2)
    +True
    +>>> bio = BytesIO()
    +>>> _ = bio.write(b' ' * 10)
    +>>> _ = bio.write(arr.tobytes('F'))
    +>>> arr2 = array_from_file((1,2,3), arr.dtype, bio, 10)
    +>>> np.all(arr == arr2)
    +True
    +
    +
    +
    + +
    +
    +

    array_to_file

    +
    +
    +nibabel.volumeutils.array_to_file(data: npt.ArrayLike, fileobj: io.IOBase, out_dtype: np.dtype | None = None, offset: int = 0, intercept: Scalar = 0.0, divslope: Scalar | None = 1.0, mn: Scalar | None = None, mx: Scalar | None = None, order: ty.Literal['C', 'F'] = 'F', nan2zero: bool = True) None
    +

    Helper function for writing arrays to file objects

    +

    Writes arrays as scaled by intercept and divslope, and clipped +at (prescaling) mn minimum, and mx maximum.

    +
      +
    • Clip data array at min mn, max max where there are not None -> +clipped (this is pre scale clipping)

    • +
    • Scale clipped with clipped_scaled = (clipped - intercept) / +divslope

    • +
    • Clip clipped_scaled to fit into range of out_dtype (post scale +clipping) -> clipped_scaled_clipped

    • +
    • If converting to integer out_dtype and nan2zero is True, set NaN +values in clipped_scaled_clipped to 0

    • +
    • Write clipped_scaled_clipped_n2z to fileobj fileobj starting at +offset offset in memory layout order

    • +
    +
    +
    Parameters:
    +
    +
    dataarray-like

    array or array-like to write.

    +
    +
    fileobjfile-like

    file-like object implementing write method.

    +
    +
    out_dtypeNone or dtype, optional

    dtype to write array as. Data array will be coerced to this dtype +before writing. If None (default) then use input data type.

    +
    +
    offsetNone or int, optional

    offset into fileobj at which to start writing data. Default is 0. None +means start at current file position

    +
    +
    interceptscalar, optional

    scalar to subtract from data, before dividing by divslope. Default +is 0.0

    +
    +
    divslopeNone or scalar, optional

    scalefactor to divide data by before writing. Default is 1.0. If +None, there is no valid data, we write zeros.

    +
    +
    mnscalar, optional

    minimum threshold in (unscaled) data, such that all data below this +value are set to this value. Default is None (no threshold). The +typical use is to set -np.inf in the data to have this value (which +might be the minimum non-finite value in the data).

    +
    +
    mxscalar, optional

    maximum threshold in (unscaled) data, such that all data above this +value are set to this value. Default is None (no threshold). The +typical use is to set np.inf in the data to have this value (which +might be the maximum non-finite value in the data).

    +
    +
    order{‘F’, ‘C’}, optional

    memory order to write array. Default is ‘F’

    +
    +
    nan2zero{True, False}, optional

    Whether to set NaN values to 0 when writing integer output. Defaults +to True. If False, NaNs will be represented as numpy does when +casting; this depends on the underlying C library and is undefined. In +practice nan2zero == False might be a good choice when you completely +sure there will be no NaNs in the data. This value ignored for float +output types. NaNs are treated as zero before applying intercept +and divslope - so an array [np.nan] with an intercept of 10 +becomes [-10] after conversion to integer out_dtype with +nan2zero set. That is because you will likely apply divslope and +intercept in reverse order when reading the data back, returning the +zero you probably expected from the input NaN.

    +
    +
    +
    +
    +

    Examples

    +
    >>> from io import BytesIO
    +>>> sio = BytesIO()
    +>>> data = np.arange(10, dtype=np.float64)
    +>>> array_to_file(data, sio, np.float64)
    +>>> sio.getvalue() == data.tobytes('F')
    +True
    +>>> _ = sio.truncate(0); _ = sio.seek(0)  # outputs 0
    +>>> array_to_file(data, sio, np.int16)
    +>>> sio.getvalue() == data.astype(np.int16).tobytes()
    +True
    +>>> _ = sio.truncate(0); _ = sio.seek(0)
    +>>> array_to_file(data.byteswap(), sio, np.float64)
    +>>> sio.getvalue() == data.byteswap().tobytes('F')
    +True
    +>>> _ = sio.truncate(0); _ = sio.seek(0)
    +>>> array_to_file(data, sio, np.float64, order='C')
    +>>> sio.getvalue() == data.tobytes('C')
    +True
    +
    +
    +
    + +
    +
    +

    best_write_scale_ftype

    +
    +
    +nibabel.volumeutils.best_write_scale_ftype(arr: np.ndarray, slope: npt.ArrayLike = 1.0, inter: npt.ArrayLike = 0.0, default: type[np.number] = <class 'numpy.float32'>) type[np.floating]
    +

    Smallest float type to contain range of arr after scaling

    +

    Scaling that will be applied to arr is (arr - inter) / slope.

    +

    Note that slope and inter get promoted to 1D arrays for this +purpose to avoid the numpy scalar casting rules, which prevent scalars +upcasting the array.

    +
    +
    Parameters:
    +
    +
    arrarray-like

    array that will be scaled

    +
    +
    slopearray-like, optional

    scalar such that output array will be (arr - inter) / slope.

    +
    +
    interarray-like, optional

    scalar such that output array will be (arr - inter) / slope

    +
    +
    defaultnumpy type, optional

    minimum float type to return

    +
    +
    +
    +
    Returns:
    +
    +
    ftypenumpy type

    Best floating point type for scaling. If no floating point type +prevents overflow, return the top floating point type. If the input +array arr already contains inf values, return the greater of the +input type and the default type.

    +
    +
    +
    +
    +

    Examples

    +
    >>> arr = np.array([0, 1, 2], dtype=np.int16)
    +>>> best_write_scale_ftype(arr, 1, 0) is np.float32
    +True
    +
    +
    +

    Specify higher default return value

    +
    >>> best_write_scale_ftype(arr, 1, 0, default=np.float64) is np.float64
    +True
    +
    +
    +

    Even large values that don’t overflow don’t change output

    +
    >>> arr = np.array([0, np.finfo(np.float32).max], dtype=np.float32)
    +>>> best_write_scale_ftype(arr, 1, 0) is np.float32
    +True
    +
    +
    +

    Scaling > 1 reduces output values, so no upcast needed

    +
    >>> best_write_scale_ftype(arr, np.float32(2), 0) is np.float32
    +True
    +
    +
    +

    Scaling < 1 increases values, so upcast may be needed (and is here)

    +
    >>> best_write_scale_ftype(arr, np.float32(0.5), 0) is np.float64
    +True
    +
    +
    +
    + +
    +
    +

    better_float_of

    +
    +
    +nibabel.volumeutils.better_float_of(first: npt.DTypeLike, second: npt.DTypeLike, default: type[np.floating] = <class 'numpy.float32'>) type[np.floating]
    +

    Return more capable float type of first and second

    +

    Return default if neither of first or second is a float

    +
    +
    Parameters:
    +
    +
    firstnumpy type specifier

    Any valid input to np.dtype()`

    +
    +
    secondnumpy type specifier

    Any valid input to np.dtype()`

    +
    +
    defaultnumpy type specifier, optional

    Any valid input to np.dtype()`

    +
    +
    +
    +
    Returns:
    +
    +
    better_typenumpy type

    More capable of first or second if both are floats; if only one is +a float return that, otherwise return default.

    +
    +
    +
    +
    +

    Examples

    +
    >>> better_float_of(np.float32, np.float64) is np.float64
    +True
    +>>> better_float_of(np.float32, 'i4') is np.float32
    +True
    +>>> better_float_of('i2', 'u4') is np.float32
    +True
    +>>> better_float_of('i2', 'u4', np.float64) is np.float64
    +True
    +
    +
    +
    + +
    +
    +

    finite_range

    +
    +
    +nibabel.volumeutils.finite_range(arr: npt.ArrayLike, check_nan: Literal[False] = False) tuple[Scalar, Scalar]
    +
    +nibabel.volumeutils.finite_range(arr: npt.ArrayLike, check_nan: Literal[True]) tuple[Scalar, Scalar, bool]
    +

    Get range (min, max) or range and flag (min, max, has_nan) from arr

    +
    +
    Parameters:
    +
    +
    arrarray-like
    +
    check_nan{False, True}, optional

    Whether to return third output, a bool signaling whether there are NaN +values in arr

    +
    +
    +
    +
    Returns:
    +
    +
    mnscalar

    minimum of values in (flattened) array

    +
    +
    mxscalar

    maximum of values in (flattened) array

    +
    +
    has_nanbool

    Returned if check_nan is True. has_nan is True if there are one or +more NaN values in arr

    +
    +
    +
    +
    +

    Examples

    +
    >>> a = np.array([[-1, 0, 1],[np.inf, np.nan, -np.inf]])
    +>>> finite_range(a)
    +(-1.0, 1.0)
    +>>> a = np.array([[-1, 0, 1],[np.inf, np.nan, -np.inf]])
    +>>> finite_range(a, check_nan=True)
    +(-1.0, 1.0, True)
    +>>> a = np.array([[np.nan],[np.nan]])
    +>>> finite_range(a) == (np.inf, -np.inf)
    +True
    +>>> a = np.array([[-3, 0, 1],[2,-1,4]], dtype=int)
    +>>> finite_range(a)
    +(-3, 4)
    +>>> a = np.array([[1, 0, 1],[2,3,4]], dtype=np.uint)
    +>>> finite_range(a)
    +(0, 4)
    +>>> a = a + 1j
    +>>> finite_range(a)
    +(1j, (4+1j))
    +>>> a = np.zeros((2,), dtype=[('f1', 'i2')])
    +>>> finite_range(a)
    +Traceback (most recent call last):
    +   ...
    +TypeError: Can only handle numeric types
    +
    +
    +
    + +
    +
    +

    fname_ext_ul_case

    +
    +
    +nibabel.volumeutils.fname_ext_ul_case(fname: str) str
    +

    fname with ext changed to upper / lower case if file exists

    +

    Check for existence of fname. If it does exist, return unmodified. If +it doesn’t, check for existence of fname with case changed from lower to +upper, or upper to lower. Return this modified fname if it exists. +Otherwise return fname unmodified

    +
    +
    Parameters:
    +
    +
    fnamestr

    filename.

    +
    +
    +
    +
    Returns:
    +
    +
    mod_fnamestr

    filename, maybe with extension of opposite case

    +
    +
    +
    +
    +
    + +
    +
    +

    int_scinter_ftype

    +
    +
    +nibabel.volumeutils.int_scinter_ftype(ifmt: type[np.integer], slope: npt.ArrayLike = 1.0, inter: npt.ArrayLike = 0.0, default: type[np.floating] = <class 'numpy.float32'>) type[np.floating]
    +

    float type containing int type ifmt * slope + inter

    +

    Return float type that can represent the max and the min of the ifmt type +after multiplication with slope and addition of inter with something +like np.array([imin, imax], dtype=ifmt) * slope + inter.

    +

    Note that slope and inter get promoted to 1D arrays for this +purpose to avoid the numpy scalar casting rules, which prevent scalars +upcasting the array.

    +
    +
    Parameters:
    +
    +
    ifmtobject

    numpy integer type (e.g. np.int32)

    +
    +
    slopefloat, optional

    slope, default 1.0

    +
    +
    interfloat, optional

    intercept, default 0.0

    +
    +
    default_outobject, optional

    numpy floating point type, default is np.float32

    +
    +
    +
    +
    Returns:
    +
    +
    ftypeobject

    numpy floating point type

    +
    +
    +
    +
    +

    Notes

    +

    It is difficult to make floats overflow with just addition because the +deltas are so large at the extremes of floating point. For example:

    +
    >>> arr = np.array([np.finfo(np.float32).max], dtype=np.float32)
    +>>> res = arr + np.iinfo(np.int16).max
    +>>> arr == res
    +array([ True])
    +
    +
    +

    Examples

    +
    >>> int_scinter_ftype(np.int8, 1.0, 0.0) == np.float32
    +True
    +>>> int_scinter_ftype(np.int8, 1e38, 0.0) == np.float64
    +True
    +
    +
    +
    + +
    +
    +

    make_dt_codes

    +
    +
    +nibabel.volumeutils.make_dt_codes(codes_seqs: Sequence[Sequence]) Recoder
    +

    Create full dt codes Recoder instance from datatype codes

    +

    Include created numpy dtype (from numpy type) and opposite endian +numpy dtype

    +
    +
    Parameters:
    +
    +
    codes_seqssequence of sequences

    contained sequences make be length 3 or 4, but must all be the same +length. Elements are data type code, data type name, and numpy +type (such as np.float32). The fourth element is the nifti string +representation of the code (e.g. “NIFTI_TYPE_FLOAT32”)

    +
    +
    +
    +
    Returns:
    +
    +
    recRecoder instance

    Recoder that, by default, returns code when indexed with any +of the corresponding code, name, type, dtype, or swapped dtype. +You can also index with niistring values if codes_seqs had sequences +of length 4 instead of 3.

    +
    +
    +
    +
    +
    + +
    +
    +

    pretty_mapping

    +
    +
    +nibabel.volumeutils.pretty_mapping(mapping: ty.Mapping[K, V], getterfunc: ty.Callable[[ty.Mapping[K, V], K], V] | None = None) str
    +

    Make pretty string from mapping

    +

    Adjusts text column to print values on basis of longest key. +Probably only sensible if keys are mainly strings.

    +

    You can pass in a callable that does clever things to get the values +out of the mapping, given the names. By default, we just use +__getitem__

    +
    +
    Parameters:
    +
    +
    mappingmapping

    implementing iterator returning keys and .items()

    +
    +
    getterfuncNone or callable

    callable taking two arguments, obj and key where obj +is the passed mapping. If None, just use lambda obj, key: +obj[key]

    +
    +
    +
    +
    Returns:
    +
    +
    strstring
    +
    +
    +
    +

    Examples

    +
    >>> d = {'a key': 'a value'}
    +>>> print(pretty_mapping(d))
    +a key  : a value
    +>>> class C: # to control ordering, show get_ method
    +...     def __iter__(self):
    +...         return iter(('short_field','longer_field'))
    +...     def __getitem__(self, key):
    +...         if key == 'short_field':
    +...             return 0
    +...         if key == 'longer_field':
    +...             return 'str'
    +...     def get_longer_field(self):
    +...         return 'method string'
    +>>> def getter(obj, key):
    +...     # Look for any 'get_<name>' methods
    +...     try:
    +...         return obj.__getattribute__('get_' + key)()
    +...     except AttributeError:
    +...         return obj[key]
    +>>> print(pretty_mapping(C(), getter))
    +short_field   : 0
    +longer_field  : method string
    +
    +
    +
    + +
    +
    +

    rec2dict

    +
    +
    +nibabel.volumeutils.rec2dict(rec: ndarray) dict[str, generic | ndarray]
    +

    Convert recarray to dictionary

    +

    Also converts scalar values to scalars

    +
    +
    Parameters:
    +
    +
    recndarray

    structured ndarray

    +
    +
    +
    +
    Returns:
    +
    +
    dctdict

    dict with key, value pairs as for rec

    +
    +
    +
    +
    +

    Examples

    +
    >>> r = np.zeros((), dtype = [('x', 'i4'), ('s', 'S10')])
    +>>> d = rec2dict(r)
    +>>> d == {'x': 0, 's': b''}
    +True
    +
    +
    +
    + +
    +
    +

    seek_tell

    +
    +
    +nibabel.volumeutils.seek_tell(fileobj: io.IOBase, offset: int, write0: bool = False) None
    +

    Seek in fileobj or check we’re in the right place already

    +
    +
    Parameters:
    +
    +
    fileobjfile-like

    object implementing seek and (if seek raises an OSError) tell

    +
    +
    offsetint

    position in file to which to seek

    +
    +
    write0{False, True}, optional

    If True, and standard seek fails, try to write zeros to the file to +reach offset. This can be useful when writing bz2 files, that cannot +do write seeks.

    +
    +
    +
    +
    +
    + +
    +
    +

    shape_zoom_affine

    +
    +
    +nibabel.volumeutils.shape_zoom_affine(shape: Sequence[int] | ndarray, zooms: Sequence[float] | ndarray, x_flip: bool = True) ndarray
    +

    Get affine implied by given shape and zooms

    +

    We get the translations from the center of the image (implied by +shape).

    +
    +
    Parameters:
    +
    +
    shape(N,) array-like

    shape of image data. N is the number of dimensions

    +
    +
    zooms(N,) array-like

    zooms (voxel sizes) of the image

    +
    +
    x_flip{True, False}

    whether to flip the X row of the affine. Corresponds to +radiological storage on disk.

    +
    +
    +
    +
    Returns:
    +
    +
    aff(4,4) array

    affine giving correspondence of voxel coordinates to mm +coordinates, taking the center of the image as origin

    +
    +
    +
    +
    +

    Examples

    +
    >>> shape = (3, 5, 7)
    +>>> zooms = (3, 2, 1)
    +>>> shape_zoom_affine((3, 5, 7), (3, 2, 1))
    +array([[-3.,  0.,  0.,  3.],
    +       [ 0.,  2.,  0., -4.],
    +       [ 0.,  0.,  1., -3.],
    +       [ 0.,  0.,  0.,  1.]])
    +>>> shape_zoom_affine((3, 5, 7), (3, 2, 1), False)
    +array([[ 3.,  0.,  0., -3.],
    +       [ 0.,  2.,  0., -4.],
    +       [ 0.,  0.,  1., -3.],
    +       [ 0.,  0.,  0.,  1.]])
    +
    +
    +
    + +
    +
    +

    working_type

    +
    +
    +nibabel.volumeutils.working_type(in_type: npt.DTypeLike, slope: npt.ArrayLike = 1.0, inter: npt.ArrayLike = 0.0) type[np.number]
    +

    Return array type from applying slope, inter to array of in_type

    +

    Numpy type that results from an array of type in_type being combined with +slope and inter. It returns something like the dtype type of +((np.zeros((2,), dtype=in_type) - inter) / slope), but ignoring the +actual values of slope and inter.

    +

    Note that you would not necessarily get the same type by applying slope and +inter the other way round. Also, you’ll see that the order in which slope +and inter are applied is the opposite of the order in which they are +passed.

    +
    +
    Parameters:
    +
    +
    in_typenumpy type specifier

    Numpy type of input array. Any valid input for np.dtype()

    +
    +
    slopescalar, optional

    slope to apply to array. If 1.0 (default), ignore this value and its +type.

    +
    +
    interscalar, optional

    intercept to apply to array. If 0.0 (default), ignore this value and +its type.

    +
    +
    +
    +
    Returns:
    +
    +
    wtype: numpy type

    Numpy type resulting from applying inter and slope to array of type +in_type.

    +
    +
    +
    +
    +
    + +
    +
    +

    write_zeros

    +
    +
    +nibabel.volumeutils.write_zeros(fileobj: io.IOBase, count: int, block_size: int = 8194) None
    +

    Write count zero bytes to fileobj

    +
    +
    Parameters:
    +
    +
    fileobjfile-like object

    with write method

    +
    +
    countint

    number of bytes to write

    +
    +
    block_sizeint, optional

    largest continuous block to write.

    +
    +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.wrapstruct.html b/reference/nibabel.wrapstruct.html new file mode 100644 index 0000000000..51ca73f938 --- /dev/null +++ b/reference/nibabel.wrapstruct.html @@ -0,0 +1,721 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    wrapstruct

    +

    Class to wrap numpy structured array

    +
    +

    wrapstruct

    +

    The WrapStruct class is a wrapper around a numpy structured array +type.

    +

    It implements:

    +
      +
    • Mappingness from the underlying structured array fields

    • +
    • from_fileobj, write_to methods to read and write data to fileobj

    • +
    • A mechanism for setting checks and fixes to the data on object creation

    • +
    • Endianness guessing, and on-the-fly swapping

    • +
    +

    The LabeledWrapStruct subclass adds:

    + +
    +

    Mappingness

    +

    You can access and set fields of the contained structarr using standard +__getitem__ / __setitem__ syntax:

    +
    +

    wrapped[‘field’] = 10

    +
    +

    Wrapped structures also implement general mappingness:

    +
    +

    wrapped.keys() +wrapped.items() +wrapped.values()

    +
    +

    Properties:

    +
    .endianness (read only)
    +.binaryblock (read only)
    +.structarr (read only)
    +
    +
    +

    Methods:

    +
    .as_byteswapped(endianness)
    +.check_fix()
    +.__str__
    +.__eq__
    +.__ne__
    +.get_value_label(name)
    +
    +
    +

    Class methods:

    +
    .diagnose_binaryblock
    +.as_byteswapped(endianness)
    +.write_to(fileobj)
    +.from_fileobj(fileobj)
    +.default_structarr() - return default structured array
    +.guessed_endian(structarr) - return guessed endian code from this structarr
    +
    +
    +
    +
    Class variables:

    template_dtype - native endian version of dtype for contained structarr

    +
    +
    +
    +
    +

    Consistency checks

    +

    We have a file, and we would like information as to whether there are any +problems with the binary data in this file, and whether they are fixable. +WrapStruct can hold checks for internal consistency of the contained data:

    +
    wrapped = WrapStruct.from_fileobj(open('myfile.bin'), check=False)
    +dx_result = WrapStruct.diagnose_binaryblock(wrapped.binaryblock)
    +
    +
    +

    This will run all known checks, with no fixes, returning a string with +diagnostic output. See below for the check=False flag.

    +

    In creating a WrapStruct object, we often want to check the consistency of +the contained data. The checks can test for problems of various levels of +severity. If the problem is severe enough, it should raise an Error. So, with +data that is consistent - no error:

    +
    wrapped = WrapStruct.from_fileobj(good_fileobj)
    +
    +
    +

    whereas:

    +
    wrapped = WrapStruct.from_fileobj(bad_fileobj)
    +
    +
    +

    would raise some error, with output to logging (see below).

    +

    If we want the created object, come what may:

    +
    hdr = WrapStruct.from_fileobj(bad_fileobj, check=False)
    +
    +
    +

    We set the error level (the level of problem that the check=True +versions will accept as OK) from global defaults:

    +
    import nibabel as nib
    +nib.imageglobals.error_level = 30
    +
    +
    +

    The same for logging:

    +
    nib.imageglobals.logger = logger
    +
    +
    +
    +
    + + + + + + + + + + + + +

    LabeledWrapStruct([binaryblock, endianness, ...])

    A WrapStruct with some fields having value labels for printing etc

    WrapStruct([binaryblock, endianness, check])

    Initialize WrapStruct from binary data block

    WrapStructError

    +
    +

    LabeledWrapStruct

    +
    +
    +class nibabel.wrapstruct.LabeledWrapStruct(binaryblock=None, endianness=None, check=True)
    +

    Bases: WrapStruct

    +

    A WrapStruct with some fields having value labels for printing etc

    +

    Initialize WrapStruct from binary data block

    +
    +
    Parameters:
    +
    +
    binaryblock{None, string} optional

    binary block to set into object. By default, None, in +which case we insert the default empty block

    +
    +
    endianness{None, ‘<’,’>’, other endian code} string, optional

    endianness of the binaryblock. If None, guess endianness +from the data.

    +
    +
    checkbool, optional

    Whether to check content of binary data in initialization. +Default is True.

    +
    +
    +
    +
    +

    Examples

    +
    >>> wstr1 = WrapStruct() # a default structure
    +>>> wstr1.endianness == native_code
    +True
    +>>> wstr1['integer']
    +array(0, dtype=int16)
    +>>> wstr1['integer'] = 1
    +>>> wstr1['integer']
    +array(1, dtype=int16)
    +
    +
    +
    +
    +__init__(binaryblock=None, endianness=None, check=True)
    +

    Initialize WrapStruct from binary data block

    +
    +
    Parameters:
    +
    +
    binaryblock{None, string} optional

    binary block to set into object. By default, None, in +which case we insert the default empty block

    +
    +
    endianness{None, ‘<’,’>’, other endian code} string, optional

    endianness of the binaryblock. If None, guess endianness +from the data.

    +
    +
    checkbool, optional

    Whether to check content of binary data in initialization. +Default is True.

    +
    +
    +
    +
    +

    Examples

    +
    >>> wstr1 = WrapStruct() # a default structure
    +>>> wstr1.endianness == native_code
    +True
    +>>> wstr1['integer']
    +array(0, dtype=int16)
    +>>> wstr1['integer'] = 1
    +>>> wstr1['integer']
    +array(1, dtype=int16)
    +
    +
    +
    + +
    +
    +get_value_label(fieldname)
    +

    Returns label for coded field

    +

    A coded field is an int field containing codes that stand for +discrete values that also have string labels.

    +
    +
    Parameters:
    +
    +
    fieldnamestr

    name of header field to get label for

    +
    +
    +
    +
    Returns:
    +
    +
    labelstr

    label for code value in header field fieldname

    +
    +
    +
    +
    Raises:
    +
    +
    ValueError

    if field is not coded.

    +
    +
    +
    +
    +

    Examples

    +
    >>> from nibabel.volumeutils import Recoder
    +>>> recoder = Recoder(((1, 'one'), (2, 'two')), ('code', 'label'))
    +>>> class C(LabeledWrapStruct):
    +...     template_dtype = np.dtype([('datatype', 'i2')])
    +...     _field_recoders = dict(datatype = recoder)
    +>>> hdr  = C()
    +>>> hdr.get_value_label('datatype')
    +'<unknown code 0>'
    +>>> hdr['datatype'] = 2
    +>>> hdr.get_value_label('datatype')
    +'two'
    +
    +
    +
    + +
    + +
    +
    +

    WrapStruct

    +
    +
    +class nibabel.wrapstruct.WrapStruct(binaryblock=None, endianness=None, check=True)
    +

    Bases: object

    +

    Initialize WrapStruct from binary data block

    +
    +
    Parameters:
    +
    +
    binaryblock{None, string} optional

    binary block to set into object. By default, None, in +which case we insert the default empty block

    +
    +
    endianness{None, ‘<’,’>’, other endian code} string, optional

    endianness of the binaryblock. If None, guess endianness +from the data.

    +
    +
    checkbool, optional

    Whether to check content of binary data in initialization. +Default is True.

    +
    +
    +
    +
    +

    Examples

    +
    >>> wstr1 = WrapStruct() # a default structure
    +>>> wstr1.endianness == native_code
    +True
    +>>> wstr1['integer']
    +array(0, dtype=int16)
    +>>> wstr1['integer'] = 1
    +>>> wstr1['integer']
    +array(1, dtype=int16)
    +
    +
    +
    +
    +__init__(binaryblock=None, endianness=None, check=True)
    +

    Initialize WrapStruct from binary data block

    +
    +
    Parameters:
    +
    +
    binaryblock{None, string} optional

    binary block to set into object. By default, None, in +which case we insert the default empty block

    +
    +
    endianness{None, ‘<’,’>’, other endian code} string, optional

    endianness of the binaryblock. If None, guess endianness +from the data.

    +
    +
    checkbool, optional

    Whether to check content of binary data in initialization. +Default is True.

    +
    +
    +
    +
    +

    Examples

    +
    >>> wstr1 = WrapStruct() # a default structure
    +>>> wstr1.endianness == native_code
    +True
    +>>> wstr1['integer']
    +array(0, dtype=int16)
    +>>> wstr1['integer'] = 1
    +>>> wstr1['integer']
    +array(1, dtype=int16)
    +
    +
    +
    + +
    +
    +as_byteswapped(endianness=None)
    +

    return new byteswapped object with given endianness

    +

    Guaranteed to make a copy even if endianness is the same as +the current endianness.

    +
    +
    Parameters:
    +
    +
    endiannessNone or string, optional

    endian code to which to swap. None means swap from current +endianness, and is the default

    +
    +
    +
    +
    Returns:
    +
    +
    wstrWrapStruct

    WrapStruct object with given endianness

    +
    +
    +
    +
    +

    Examples

    +
    >>> wstr = WrapStruct()
    +>>> wstr.endianness == native_code
    +True
    +>>> bs_wstr = wstr.as_byteswapped()
    +>>> bs_wstr.endianness == swapped_code
    +True
    +>>> bs_wstr = wstr.as_byteswapped(swapped_code)
    +>>> bs_wstr.endianness == swapped_code
    +True
    +>>> bs_wstr is wstr
    +False
    +>>> bs_wstr == wstr
    +True
    +
    +
    +

    If you write to the resulting byteswapped data, it does not +change the original.

    +
    >>> bs_wstr['integer'] = 3
    +>>> bs_wstr == wstr
    +False
    +
    +
    +

    If you swap to the same endianness, it returns a copy

    +
    >>> nbs_wstr = wstr.as_byteswapped(native_code)
    +>>> nbs_wstr.endianness == native_code
    +True
    +>>> nbs_wstr is wstr
    +False
    +
    +
    +
    + +
    +
    +property binaryblock
    +

    binary block of data as string

    +
    +
    Returns:
    +
    +
    binaryblockstring

    string giving binary data block

    +
    +
    +
    +
    +

    Examples

    +
    >>> # Make default empty structure
    +>>> wstr = WrapStruct()
    +>>> len(wstr.binaryblock)
    +2
    +
    +
    +
    + +
    +
    +check_fix(logger=None, error_level=None)
    +

    Check structured data with checks

    +
    +
    Parameters:
    +
    +
    loggerNone or logging.Logger
    +
    error_levelNone or int

    Level of error severity at which to raise error. Any error of +severity >= error_level will cause an exception.

    +
    +
    +
    +
    +
    + +
    +
    +copy()
    +

    Return copy of structure

    +
    >>> wstr = WrapStruct()
    +>>> wstr['integer'] = 3
    +>>> wstr2 = wstr.copy()
    +>>> wstr2 is wstr
    +False
    +>>> wstr2['integer']
    +array(3, dtype=int16)
    +
    +
    +
    + +
    +
    +classmethod default_structarr(endianness=None)
    +

    Return structured array for default structure with given endianness

    +
    + +
    +
    +classmethod diagnose_binaryblock(binaryblock, endianness=None)
    +

    Run checks over binary data, return string

    +
    + +
    +
    +property endianness
    +

    endian code of binary data

    +

    The endianness code gives the current byte order +interpretation of the binary data.

    +

    Notes

    +

    Endianness gives endian interpretation of binary data. It is +read only because the only common use case is to set the +endianness on initialization, or occasionally byteswapping the +data - but this is done via the as_byteswapped method

    +

    Examples

    +
    >>> wstr = WrapStruct()
    +>>> code = wstr.endianness
    +>>> code == native_code
    +True
    +
    +
    +
    + +
    +
    +classmethod from_fileobj(fileobj, endianness=None, check=True)
    +

    Return read structure with given or guessed endiancode

    +
    +
    Parameters:
    +
    +
    fileobjfile-like object

    Needs to implement read method

    +
    +
    endiannessNone or endian code, optional

    Code specifying endianness of read data

    +
    +
    +
    +
    Returns:
    +
    +
    wstrWrapStruct object

    WrapStruct object initialized from data in fileobj

    +
    +
    +
    +
    +
    + +
    +
    +get(k, d=None)
    +

    Return value for the key k if present or d otherwise

    +
    + +
    +
    +classmethod guessed_endian(mapping)
    +

    Guess intended endianness from mapping-like mapping

    +
    +
    Parameters:
    +
    +
    wstrmapping-like

    Something implementing a mapping. We will guess the endianness +from looking at the field values

    +
    +
    +
    +
    Returns:
    +
    +
    endianness{‘<’, ‘>’}

    Guessed endianness of binary data in wstr

    +
    +
    +
    +
    +
    + +
    +
    +items()
    +

    Return items from structured data

    +
    + +
    +
    +keys()
    +

    Return keys from structured data

    +
    + +
    +
    +property structarr
    +

    Structured data, with data fields

    +

    Examples

    +
    >>> wstr1 = WrapStruct() # with default data
    +>>> an_int = wstr1.structarr['integer']
    +>>> wstr1.structarr = None
    +Traceback (most recent call last):
    +   ...
    +AttributeError: ...
    +
    +
    +
    + +
    +
    +template_dtype = dtype([('integer', '<i2')])
    +
    + +
    +
    +values()
    +

    Return values from structured data

    +
    + +
    +
    +write_to(fileobj)
    +

    Write structure to fileobj

    +

    Write starts at fileobj current file position.

    +
    +
    Parameters:
    +
    +
    fileobjfile-like object

    Should implement write method

    +
    +
    +
    +
    Returns:
    +
    +
    None
    +
    +
    +
    +

    Examples

    +
    >>> wstr = WrapStruct()
    +>>> from io import BytesIO
    +>>> str_io = BytesIO()
    +>>> wstr.write_to(str_io)
    +>>> wstr.binaryblock == str_io.getvalue()
    +True
    +
    +
    +
    + +
    + +
    +
    +

    WrapStructError

    +
    +
    +class nibabel.wrapstruct.WrapStructError
    +

    Bases: Exception

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/reference/nibabel.xmlutils.html b/reference/nibabel.xmlutils.html new file mode 100644 index 0000000000..21d8b6928e --- /dev/null +++ b/reference/nibabel.xmlutils.html @@ -0,0 +1,282 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +
    +

    xmlutils

    +

    Thin layer around xml.etree.ElementTree, to abstract nibabel xml support

    + + + + + + + + + + + + +

    XmlBasedHeader()

    Basic wrapper around FileBasedHeader and XmlSerializable.

    XmlParser([encoding, buffer_size, verbose])

    Base class for defining how to parse xml-based image snippets.

    XmlSerializable()

    Basic interface for serializing an object to XML

    +
    +

    XmlBasedHeader

    +
    +
    +class nibabel.xmlutils.XmlBasedHeader
    +

    Bases: FileBasedHeader, XmlSerializable

    +

    Basic wrapper around FileBasedHeader and XmlSerializable.

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    + +
    +
    +

    XmlParser

    +
    +
    +class nibabel.xmlutils.XmlParser(encoding='utf-8', buffer_size=35000000, verbose=0)
    +

    Bases: object

    +

    Base class for defining how to parse xml-based image snippets.

    +
    +
    Image-specific parsers should define:

    StartElementHandler +EndElementHandler +CharacterDataHandler

    +
    +
    +
    +
    Parameters:
    +
    +
    encodingstr

    string containing xml document

    +
    +
    buffer_size: None or int, optional

    size of read buffer. None uses default buffer_size +from xml.parsers.expat.

    +
    +
    verboseint, optional

    amount of output during parsing (0=silent, by default).

    +
    +
    +
    +
    +
    +
    +__init__(encoding='utf-8', buffer_size=35000000, verbose=0)
    +
    +
    Parameters:
    +
    +
    encodingstr

    string containing xml document

    +
    +
    buffer_size: None or int, optional

    size of read buffer. None uses default buffer_size +from xml.parsers.expat.

    +
    +
    verboseint, optional

    amount of output during parsing (0=silent, by default).

    +
    +
    +
    +
    +
    + +
    +
    +CharacterDataHandler(data)
    +
    + +
    +
    +EndElementHandler(name)
    +
    + +
    +
    +HANDLER_NAMES = ['StartElementHandler', 'EndElementHandler', 'CharacterDataHandler']
    +
    + +
    +
    +StartElementHandler(name, attrs)
    +
    + +
    +
    +parse(string=None, fname=None, fptr=None)
    +
    +
    Parameters:
    +
    +
    stringbytes

    string (as a bytes object) containing xml document

    +
    +
    fnamestr

    file name of an xml document.

    +
    +
    fptrfile pointer

    open file pointer to an xml documents

    +
    +
    +
    +
    +
    + +
    + +
    +
    +

    XmlSerializable

    +
    +
    +class nibabel.xmlutils.XmlSerializable
    +

    Bases: object

    +

    Basic interface for serializing an object to XML

    +
    +
    +__init__(*args, **kwargs)
    +
    + +
    +
    +to_xml(enc='utf-8', **kwargs) bytes
    +

    Generate an XML bytestring with a given encoding.

    +
    +
    Parameters:
    +
    +
    encstring

    Encoding to use for the generated bytestring. Default: ‘utf-8’

    +
    +
    **kwargsdict

    Additional keyword arguments to xml.etree.ElementTree.tostring().

    +
    +
    +
    +
    +
    + +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index c65baf5cb8..0000000000 --- a/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -# Auto-generated by tools/update_requirements.py -numpy >=1.22 -packaging >=20 -importlib_resources >=5.12; python_version < '3.12' -typing_extensions >=4.6; python_version < '3.13' diff --git a/search.html b/search.html new file mode 100644 index 0000000000..3d4c2ffd62 --- /dev/null +++ b/search.html @@ -0,0 +1,103 @@ + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + + + + + + +
    +
    + +
    +
    +

    NiBabel

    +

    Access a cacophony of neuro-imaging file formats

    +
    +
    + + + + +
    +
    +
    +
    + +

    Search

    + + + + +

    + Searching for multiple words only shows matches that contain + all words. +

    + + +
    + + + +
    + + +
    + + +
    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/searchindex.js b/searchindex.js new file mode 100644 index 0000000000..e78e968e92 --- /dev/null +++ b/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({"alltitles": {"(i, j), columns, rows in DICOM": [[38, "i-j-columns-rows-in-dicom"]], "0.20061114 (Tue, 14 Nov 2006)": [[1, "tue-14-nov-2006"]], "0.20070214.1 (Wed, 14 Feb 2007)": [[1, "wed-14-feb-2007"]], "0.20070220.1 (Tue, 20 Feb 2007)": [[1, "tue-20-feb-2007"]], "0.20070301.1 (Thu, 1 Mar 2007)": [[1, "id95"]], "0.20070301.2 (Thu, 1 Mar 2007)": [[1, "thu-1-mar-2007"]], "0.20070315.1 (Thu, 15 Mar 2007)": [[1, "thu-15-mar-2007"]], "0.20070425.1 (Wed, 25 Apr 2007)": [[1, "wed-25-apr-2007"]], "0.20070803.1 (Fri, 3 Aug 2007)": [[1, "fri-3-aug-2007"]], "0.20070905.1 (Wed, 5 Sep 2007)": [[1, "wed-5-sep-2007"]], "0.20070917.1 (Mon, 17 Sep 2007)": [[1, "mon-17-sep-2007"]], "0.20070930.1 (Sun, 30 Sep 2007)": [[1, "sun-30-sep-2007"]], "0.20080624.1 (Tue, 24 Jun 2008)": [[1, "tue-24-jun-2008"]], "0.20080630.1 (Tue, 30 Jun 2008)": [[1, "tue-30-jun-2008"]], "0.20080710.1 (Thu, 7 Jul 2008)": [[1, "thu-7-jul-2008"]], "0.20081017.1 (Fri, 17 Oct 2008)": [[1, "fri-17-oct-2008"]], "0.20090205.1 (Thu, 5 Feb 2009)": [[1, "thu-5-feb-2009"]], "0.20090303.1 (Tue, 3 Mar 2009)": [[1, "tue-3-mar-2009"]], "0.20100412.1 (Mon, 12 Apr 2010)": [[1, "mon-12-apr-2010"]], "0.20100706.1 (Tue, 6 Jul 2010)": [[1, "tue-6-jul-2010"]], "1.0.0 (Thursday, 13, Oct 2010)": [[1, "thursday-13-oct-2010"]], "1.0.1 (Wednesday 23 Feb 2011)": [[1, "wednesday-23-feb-2011"]], "1.0.2 (Thursday 14 April 2011)": [[1, "thursday-14-april-2011"]], "1.1.0 (Thursday 28 April 2011)": [[1, "thursday-28-april-2011"]], "1.2.0 (Sunday 6 May 2012)": [[1, "sunday-6-may-2012"]], "1.2.1 (Wednesday 13 June 2012)": [[1, "wednesday-13-june-2012"]], "1.2.2 (Wednesday 27 June 2012)": [[1, "wednesday-27-june-2012"]], "1.3.0 (Tuesday 11 September 2012)": [[1, "tuesday-11-september-2012"]], "2.0.0 (Tuesday 9 December 2014)": [[1, "tuesday-9-december-2014"]], "2.0.1 (Saturday 27 June 2015)": [[1, "saturday-27-june-2015"]], "2.0.2 (Monday 23 November 2015)": [[1, "monday-23-november-2015"]], "2.1 (Monday 22 August 2016)": [[1, "monday-22-august-2016"]], "2.2 (Friday 13 October 2017)": [[1, "friday-13-october-2017"]], "2.2.1 (Wednesday 22 November 2017)": [[1, "wednesday-22-november-2017"]], "2.3 (Tuesday 12 June 2018)": [[1, "tuesday-12-june-2018"]], "2.3.1 (Tuesday 16 October 2018)": [[1, "tuesday-16-october-2018"]], "2.3.2 (Wednesday 2 January 2019)": [[1, "wednesday-2-january-2019"]], "2.3.3 (Wednesday 16 January 2019)": [[1, "wednesday-16-january-2019"]], "2.4.0 (Monday 1 April 2019)": [[1, "monday-1-april-2019"]], "2.4.1 (Monday 27 May 2019)": [[1, "monday-27-may-2019"]], "2.5.0 (Sunday 4 August 2019)": [[1, "sunday-4-august-2019"]], "2.5.1 (Monday 23 September 2019)": [[1, "monday-23-september-2019"]], "2.5.2 (Wednesday 8 April 2020)": [[1, "wednesday-8-april-2020"]], "3.0.0 (Wednesday 18 December 2019)": [[1, "wednesday-18-december-2019"]], "3.0.1 (Monday 27 January 2020)": [[1, "monday-27-january-2020"]], "3.0.2 (Monday 9 March 2020)": [[1, "monday-9-march-2020"]], "3.1.0 (Monday 20 April 2020)": [[1, "monday-20-april-2020"]], "3.1.1 (Friday 26 June 2020)": [[1, "friday-26-june-2020"]], "3.2.0 (Tuesday 20 October 2020)": [[1, "tuesday-20-october-2020"]], "3.2.1 (Saturday 28 November 2020)": [[1, "saturday-28-november-2020"]], "3.2.2 (Monday 7 February 2022)": [[1, "monday-7-february-2022"]], "3D affine formulae": [[38, "d-affine-formulae"]], "3rd party code and data": [[58, "rd-party-code-and-data"]], "4.0.0 (Saturday 18 June 2022)": [[1, "id23"]], "4.0.1 (Saturday 18 June 2022)": [[1, "saturday-18-june-2022"]], "4.0.2 (Wednesday 31 August 2022)": [[1, "wednesday-31-august-2022"]], "5.0.0 (Monday 9 January 2023)": [[1, "monday-9-january-2023"]], "5.0.1 (Sunday 12 February 2023)": [[1, "sunday-12-february-2023"]], "5.1.0 (Monday 3 April 2023)": [[1, "monday-3-april-2023"]], "5.2.0 (Monday 11 December 2023)": [[1, "monday-11-december-2023"]], "5.2.1 (Monday 26 February 2024)": [[1, "monday-26-february-2024"]], "5.3.0 (Tuesday 8 October 2024)": [[1, "tuesday-8-october-2024"]], "A data set": [[35, "a-data-set"]], "A few commits": [[51, "a-few-commits"]], "A guide to making a nibabel release": [[26, null]], "A long series of commits": [[51, "a-long-series-of-commits"]], "A recipe for writing a new image format": [[3, "a-recipe-for-writing-a-new-image-format"]], "AFNIArrayProxy": [[74, "afniarrayproxy"]], "AFNIHeader": [[74, "afniheader"]], "AFNIHeaderError": [[74, "afniheadererror"]], "AFNIImage": [[74, "afniimage"]], "AFNIImageError": [[74, "afniimageerror"]], "API Documentation": [[0, null]], "API Reference": [[65, null]], "API changes and deprecations": [[1, "api-changes-and-deprecations"], [1, "id8"], [1, "id18"], [1, "id28"], [1, "id36"], [1, "id41"], [1, "id52"], [1, "id56"], [1, "id60"], [1, "id64"], [1, "id73"], [1, "id82"], [1, "id89"], [1, "id94"]], "API for surface data": [[28, "api-for-surface-data"]], "Abstract": [[9, "abstract"], [16, "abstract"], [23, "abstract"]], "Acknowledgments": [[19, "acknowledgments"], [23, "acknowledgments"]], "Add a load_multi top-level function": [[13, "add-a-load-multi-top-level-function"]], "Adding as a submodule to nibabel-data": [[4, "adding-as-a-submodule-to-nibabel-data"]], "Adding test data": [[4, null]], "Adding the file to nibabel/tests/data": [[4, "adding-the-file-to-nibabel-tests-data"]], "Advanced Testing": [[5, null]], "Advanced git workflow": [[49, "advanced-git-workflow"]], "AffineError": [[68, "affineerror"]], "Aliases": [[42, "aliases"]], "Alignment of world and voxel axes": [[60, "alignment-of-world-and-voxel-axes"]], "Alphabetical API reference": [[0, "alphabetical-api-reference"]], "Alternatives": [[9, "alternatives"], [16, "alternatives"]], "AnalyzeHeader": [[69, "analyzeheader"]], "AnalyzeImage": [[69, "analyzeimage"]], "Applying the affine": [[2, "applying-the-affine"]], "Array images": [[7, "array-images"], [61, "array-images"]], "Array images, proxy images, copy, view": [[7, "array-images-proxy-images-copy-view"]], "Array proxies and proxy images": [[61, "array-proxies-and-proxy-images"]], "ArrayLike": [[70, "arraylike"]], "ArrayProxy": [[70, "id1"]], "ArraySequence": [[119, "arraysequence"]], "ArrayWriter": [[71, "arraywriter"]], "AscconvParseError": [[102, "ascconvparseerror"]], "Ask for your changes to be reviewed or merged": [[43, "ask-for-your-changes-to-be-reviewed-or-merged"]], "Atom": [[102, "atom"]], "Attribute Tag": [[35, "attribute-tag"]], "Authentication and validation": [[20, "authentication-and-validation"]], "Authors and Contributors": [[56, "authors-and-contributors"]], "Axis": [[77, "axis"]], "Axis and tick labels": [[28, "axis-and-tick-labels"]], "B2q": [[102, "b2q"]], "BIAP 0 - Purpose and process": [[6, null]], "BIAP Workflow": [[6, "biap-workflow"]], "BIAP X \u2014 Template and Instructions": [[16, null]], "BIAP1 - Towards immutable images": [[7, null]], "BIAP2 - Slicecopy": [[8, null]], "BIAP3 - A JSON nifti header extension": [[9, null]], "BIAP4 - Merging nibabel and dcmstack": [[10, null]], "BIAP5 - A streamlines converter": [[11, null]], "BIAP6 - Identifying image axes": [[12, null]], "BIAP7 - Loading multiple images": [[13, null]], "BIAP8 - Always load image data as floating point": [[14, null]], "BIAP9 - The Coordinate Image API": [[15, null]], "BIAPs": [[17, null]], "BV internal format axes": [[18, "bv-internal-format-axes"]], "Background": [[7, "background"], [8, "background"], [9, "background"], [12, "background"], [13, "background"], [14, "background"], [15, "background"], [28, "background"]], "Background - the DICOM world": [[35, "background-the-dicom-world"]], "Backward compatibility": [[16, "backward-compatibility"]], "BatteryRunner": [[72, "batteryrunner"]], "Bomber": [[79, "bomber"]], "BomberError": [[79, "bombererror"]], "BrainModelAxis": [[77, "brainmodelaxis"]], "BrainVoyager file formats": [[18, null]], "Bug fixes": [[1, "bug-fixes"], [1, "id2"], [1, "id6"], [1, "id10"], [1, "id12"], [1, "id16"], [1, "id19"], [1, "id21"], [1, "id26"], [1, "id29"], [1, "id34"], [1, "id37"], [1, "id39"], [1, "id42"], [1, "id44"], [1, "id46"], [1, "id50"], [1, "id54"], [1, "id58"], [1, "id62"], [1, "id67"], [1, "id71"], [1, "id76"], [1, "id80"], [1, "id83"], [1, "id87"], [1, "id92"]], "CSA header": [[39, "csa-header"]], "CSA1": [[39, "csa1"]], "CSA2": [[39, "csa2"]], "CSAError": [[102, "csaerror"]], "CSAReadError": [[102, "csareaderror"]], "CachingError": [[83, "cachingerror"]], "CaretMetaData": [[75, "caretmetadata"]], "CastingError": [[76, "castingerror"]], "Changelog": [[22, "changelog"]], "Check the history": [[51, "check-the-history"]], "Choosing the image affine": [[62, "choosing-the-image-affine"]], "Cifti2BrainModel": [[77, "cifti2brainmodel"]], "Cifti2Extension": [[77, "cifti2extension"]], "Cifti2Header": [[77, "cifti2header"]], "Cifti2HeaderError": [[77, "cifti2headererror"]], "Cifti2Image": [[77, "cifti2image"]], "Cifti2Label": [[77, "cifti2label"]], "Cifti2LabelTable": [[77, "cifti2labeltable"]], "Cifti2Matrix": [[77, "cifti2matrix"]], "Cifti2MatrixIndicesMap": [[77, "cifti2matrixindicesmap"]], "Cifti2MetaData": [[77, "cifti2metadata"]], "Cifti2NamedMap": [[77, "cifti2namedmap"]], "Cifti2Parcel": [[77, "cifti2parcel"]], "Cifti2Parser": [[77, "cifti2parser"]], "Cifti2Surface": [[77, "cifti2surface"]], "Cifti2TransformationMatrixVoxelIndicesIJKtoXYZ": [[77, "cifti2transformationmatrixvoxelindicesijktoxyz"]], "Cifti2VertexIndices": [[77, "cifti2vertexindices"]], "Cifti2Vertices": [[77, "cifti2vertices"]], "Cifti2Volume": [[77, "cifti2volume"]], "Cifti2VoxelIndicesIJK": [[77, "cifti2voxelindicesijk"]], "Citation": [[56, "citation"], [66, "citation"]], "Clone your fork": [[53, "clone-your-fork"]], "Closing issues and pull requests": [[19, "closing-issues-and-pull-requests"]], "Code Documentation": [[22, "code-documentation"]], "Commits": [[22, "commits"]], "Community guidelines": [[22, "code-of-conduct"]], "Comparative terminology": [[20, "comparative-terminology"]], "Compared to Debian packaging": [[20, "compared-to-debian-packaging"]], "Compiling dcm2nii": [[31, "compiling-dcm2nii"]], "Configure git": [[42, null]], "Consider deleting your master branch": [[43, "consider-deleting-your-master-branch"]], "Consistency checks": [[124, "consistency-checks"]], "Contributors": [[23, "contributors"]], "Coordinate systems and affines": [[2, null]], "CoordinateArray": [[110, "coordinatearray"]], "Copyright and Licenses": [[58, null]], "Core Developer Guide": [[19, null]], "Core Developers": [[23, "core-developers"]], "Create your own forked copy of nibabel": [[45, "create-your-own-forked-copy-of-nibabel"]], "Creating new CIFTI-2 axes": [[77, "creating-new-cifti-2-axes"]], "Current implementation": [[14, "current-implementation"]], "Currently supported surface formats": [[15, "currently-supported-surface-formats"]], "DFTError": [[83, "dfterror"]], "DICOM Entities and Information Object Definitions": [[35, "dicom-entities-and-information-object-definitions"]], "DICOM Tags in the NIfTI Header": [[37, null]], "DICOM affine Definitions": [[38, "dicom-affine-definitions"]], "DICOM affine formula": [[38, "dicom-affine-formula"]], "DICOM affines again": [[38, "dicom-affines-again"]], "DICOM concepts and implementations": [[32, null]], "DICOM data format": [[35, "dicom-data-format"]], "DICOM data structures": [[35, "dicom-data-structures"]], "DICOM elements": [[35, "dicom-elements"]], "DICOM fields": [[33, null]], "DICOM files": [[35, "dicom-files"]], "DICOM information": [[34, null]], "DICOM is messages": [[35, "dicom-is-messages"]], "DICOM orientation for mosaic": [[36, "dicom-orientation-for-mosaic"]], "DICOM patient coordinate system": [[38, "dicom-patient-coordinate-system"]], "DICOM pixel data": [[38, "dicom-pixel-data"]], "DICOM service object pairs (SOPs)": [[35, "dicom-service-object-pairs-sops"]], "DICOM services (DIMSE)": [[35, "dicom-services-dimse"]], "DICOM voxel to patient coordinate system mapping": [[38, "dicom-voxel-to-patient-coordinate-system-mapping"]], "DICOMFS": [[78, "dicomfs"]], "Data Sorting": [[109, "data-sorting"]], "Data and metadata": [[20, "data-and-metadata"]], "Data element tags and data dictionaries": [[35, "data-element-tags-and-data-dictionaries"]], "Data scaling": [[36, "data-scaling"], [62, "data-scaling"]], "Data type": [[109, "data-type"]], "DataError": [[79, "dataerror"], [119, "dataerror"]], "DataWarning": [[119, "datawarning"]], "DataobjImage": [[80, "dataobjimage"]], "Datasource": [[79, "datasource"]], "DcmMetaExtension tied to NiftiExtension": [[10, "dcmmetaextension-tied-to-niftiextension"]], "Debian/Ubuntu": [[57, "debian-ubuntu"]], "Decision Making Process": [[23, "decision-making-process"]], "Default sform and qform codes": [[62, "default-sform-and-qform-codes"]], "Defining the DICOM orientation": [[38, null]], "Delete a branch on github": [[43, "delete-a-branch-on-github"]], "Dependency management": [[20, "dependency-management"]], "Deprecator": [[82, "id1"]], "Derivations": [[38, "derivations"]], "Desiderata": [[20, "desiderata"]], "Desiderata for an API supporting surfaces": [[15, "desiderata-for-an-api-supporting-surfaces"]], "Detailed description": [[16, "detailed-description"]], "Details of files and images": [[61, "details-of-files-and-images"]], "Detecting slice or volume-specific data difficult for 3D and 4D DICOMS": [[10, "detecting-slice-or-volume-specific-data-difficult-for-3d-and-4d-dicoms"]], "DeterministicGzipFile": [[106, "deterministicgzipfile"]], "Developer discussions": [[21, null]], "Developer documentation page": [[25, null]], "Development workflow": [[43, null]], "DicomReadError": [[102, "dicomreaderror"]], "Differences from code packages": [[20, "differences-from-code-packages"]], "Discovery": [[20, "discovery"]], "Discussion": [[16, "discussion"]], "Distinguishing time and volume": [[12, "distinguishing-time-and-volume"]], "Documentation": [[1, "documentation"], [22, "documentation"], [56, "documentation"]], "Download and Installation": [[56, "download-and-installation"]], "DtypeMapper": [[123, "dtypemapper"]], "Each item": [[39, "each-item"], [39, "id4"]], "Each tag": [[39, "each-tag"], [39, "id3"]], "EcatHeader": [[84, "ecatheader"]], "EcatImage": [[84, "ecatimage"]], "EcatImageArrayProxy": [[84, "ecatimagearrayproxy"]], "EcatSubHeader": [[84, "ecatsubheader"]], "Editor": [[42, "editor"]], "Enhancement Proposals (BIAPs)": [[23, "enhancement-proposals-biaps"]], "Enhancements": [[1, "enhancements"], [1, "id1"], [1, "id5"], [1, "id9"], [1, "id15"], [1, "id25"], [1, "id33"], [1, "id49"], [1, "id53"], [1, "id57"], [1, "id61"], [1, "id66"], [1, "id70"], [1, "id75"], [1, "id79"], [1, "id86"], [1, "id91"]], "ErrorLevel": [[96, "errorlevel"]], "Example NIfTI images": [[62, "example-nifti-images"]], "Examples": [[77, "examples"]], "ExpiredDeprecationError": [[82, "expireddeprecationerror"]], "Explore your repository": [[43, "explore-your-repository"]], "Expressive API": [[28, "expressive-api"]], "ExtensionWarning": [[119, "extensionwarning"]], "Fancy log output": [[42, "fancy-log-output"]], "Field": [[119, "field"]], "Fields for ordering DICOM files into images": [[33, "fields-for-ordering-dicom-files-into-images"]], "File Formats": [[0, "file-formats"]], "File categorization": [[40, "file-categorization"]], "File opening": [[40, "file-opening"]], "FileBasedHeader": [[87, "filebasedheader"]], "FileBasedImage": [[87, "filebasedimage"]], "FileHandle": [[78, "filehandle"]], "FileHolder": [[88, "fileholder"]], "FileHolderError": [[88, "fileholdererror"]], "Fileish": [[106, "fileish"]], "Files interface": [[116, "files-interface"]], "Files with open licenses": [[4, "files-with-open-licenses"]], "FilterDwiIso": [[102, "filterdwiiso"]], "FilterMultiStack": [[102, "filtermultistack"]], "Final check": [[40, "final-check"]], "First pass": [[40, "first-pass"]], "Float / integer conversion": [[0, "float-integer-conversion"]], "FloatingError": [[76, "floatingerror"]], "Following the latest source": [[44, null]], "Format and Template": [[6, "format-and-template"]], "FrameFilter": [[102, "framefilter"]], "From scanner to subject": [[2, "from-scanner-to-subject"]], "Further resources": [[19, "further-resources"]], "Future Work": [[11, "future-work"]], "FutureWarningMixin": [[81, "futurewarningmixin"]], "General information": [[109, "general-information"]], "General principles": [[9, "general-principles"]], "General solution: associating axes and labels": [[12, "general-solution-associating-axes-and-labels"]], "General tutorials": [[126, null]], "Get the development sources": [[57, "get-the-development-sources"]], "Get the local copy of the code": [[44, "get-the-local-copy-of-the-code"]], "Getting Started": [[41, null]], "Getting a 3D affine from a DICOM slice or list of slices": [[38, "getting-a-3d-affine-from-a-dicom-slice-or-list-of-slices"]], "Getting the image data the easy way": [[61, "getting-the-image-data-the-easy-way"]], "Getting the slices from the mosaic": [[36, "getting-the-slices-from-the-mosaic"]], "GiftiCoordSystem": [[94, "gifticoordsystem"]], "GiftiDataArray": [[94, "giftidataarray"]], "GiftiImage": [[94, "giftiimage"]], "GiftiImageParser": [[94, "giftiimageparser"]], "GiftiLabel": [[94, "giftilabel"]], "GiftiLabelTable": [[94, "giftilabeltable"]], "GiftiMetaData": [[94, "giftimetadata"]], "GiftiNVPairs": [[94, "giftinvpairs"]], "GiftiParseError": [[94, "giftiparseerror"]], "Git Repository": [[22, "git-repository"]], "Git for development": [[46, null]], "Governance and Decision Making": [[23, null]], "Grid": [[110, "grid"]], "GridIndices": [[110, "gridindices"]], "HasDtype": [[116, "hasdtype"]], "Hdf5Bunch": [[100, "hdf5bunch"]], "Header": [[11, "header"]], "Header Preamble": [[6, "header-preamble"]], "HeaderDataError": [[116, "headerdataerror"]], "HeaderError": [[119, "headererror"]], "HeaderTypeError": [[116, "headertypeerror"]], "HeaderWarning": [[119, "headerwarning"]], "Helping us to review your code": [[3, "helping-us-to-review-your-code"]], "How a BIAP becomes Accepted": [[6, "how-a-biap-becomes-accepted"]], "How much data should go in a single submodule?": [[4, "how-much-data-should-go-in-a-single-submodule"]], "How to Conduct A Good Review": [[19, "how-to-conduct-a-good-review"]], "How to add a new image format to nibabel": [[3, null]], "IO implementations": [[64, "io-implementations"]], "IPython notebooks for Nibabel project": [[63, null]], "If in doubt": [[4, "if-in-doubt"]], "Image Utilities": [[0, "image-utilities"]], "Image information": [[109, "image-information"]], "Image slicing": [[61, "image-slicing"]], "Image use-cases in SPM": [[30, null]], "Image voxel orientation": [[54, null]], "ImageDataError": [[116, "imagedataerror"]], "ImageFileError": [[87, "imagefileerror"]], "ImageOpener": [[106, "imageopener"]], "Images": [[64, "images"]], "Images and memory": [[55, null]], "Images can have more than four axes": [[12, "images-can-have-more-than-four-axes"]], "Implementation": [[9, "implementation"], [16, "implementation"]], "Improving access to varying meta data through the Nifti": [[10, "improving-access-to-varying-meta-data-through-the-nifti"]], "In detail": [[14, "in-detail"], [42, "in-detail"], [47, "in-detail"], [52, "in-detail"], [53, "in-detail"]], "In more detail": [[43, "in-more-detail"]], "InGivenDirectory": [[120, "ingivendirectory"]], "InTemporaryDirectory": [[120, "intemporarydirectory"]], "Install a development version": [[57, "install-a-development-version"]], "Install git": [[47, null]], "Installation": [[56, "installation"], [57, null], [57, "id2"], [66, "installation"]], "Installer and packages": [[57, "installer-and-packages"]], "InstanceStackError": [[83, "instancestackerror"]], "Integrating changes": [[51, "integrating-changes"]], "Introducing Someone": [[2, "introducing-someone"]], "Introduction": [[48, null]], "Introduction to DICOM": [[35, null]], "Issues": [[10, "issues"], [11, "issues"], [20, "issues"]], "Issues for design": [[7, "issues-for-design"]], "It can be useful to load 4D images as multiple 3D images": [[13, "it-can-be-useful-to-load-4d-images-as-multiple-3d-images"]], "Keeping track of metadata when manipulating images": [[10, "keeping-track-of-metadata-when-manipulating-images"]], "Keeping track of whether images have been modified since load": [[27, null]], "LabelAxis": [[77, "labelaxis"]], "LabeledWrapStruct": [[124, "labeledwrapstruct"]], "Layout": [[22, "layout"]], "LazyDict": [[119, "lazydict"]], "LazyTractogram": [[119, "lazytractogram"]], "Learning from NRRDs": [[9, "learning-from-nrrds"]], "License": [[56, "license"], [66, "license"]], "License reprise": [[56, "license-reprise"]], "LimitedNifti2Header": [[77, "limitednifti2header"]], "Linking your repository to the upstream repo": [[53, "linking-your-repository-to-the-upstream-repo"]], "Loading and saving": [[61, "loading-and-saving"]], "Loading images, assert not modified": [[7, "loading-images-assert-not-modified"]], "Loading images, maximizing speed": [[7, "loading-images-maximizing-speed"]], "Loading images, minimizing memory": [[7, "loading-images-minimizing-memory"]], "LoggingOutputSuppressor": [[96, "loggingoutputsuppressor"]], "Long-running tests": [[5, "long-running-tests"]], "MGHError": [[92, "mgherror"]], "MGHHeader": [[92, "mghheader"]], "MGHImage": [[92, "mghimage"]], "MRIError": [[101, "mrierror"]], "Mailing List": [[56, "mailing-list"], [66, "mailing-list"]], "Maintainer workflow": [[51, null]], "Maintenance": [[1, "maintenance"], [1, "id3"], [1, "id7"], [1, "id11"], [1, "id13"], [1, "id17"], [1, "id20"], [1, "id22"], [1, "id27"], [1, "id30"], [1, "id31"], [1, "id35"], [1, "id40"], [1, "id43"], [1, "id45"], [1, "id47"], [1, "id51"], [1, "id55"], [1, "id59"], [1, "id63"], [1, "id68"], [1, "id69"], [1, "id72"], [1, "id77"], [1, "id81"], [1, "id84"], [1, "id88"], [1, "id93"], [6, "maintenance"]], "Make a new feature branch": [[43, "make-a-new-feature-branch"]], "Making a patch": [[52, null]], "Making patches": [[52, "making-patches"]], "Making the affine": [[40, "making-the-affine"]], "Making your own copy (fork) of nibabel": [[45, null]], "Manual pages online": [[49, "manual-pages-online"]], "Mappingness": [[124, "mappingness"]], "Merge Only Changes You Understand": [[19, "merge-only-changes-you-understand"]], "Merges": [[22, "merges"]], "Merging": [[42, "merging"]], "Meta data in nested DICOM sequences can not be independently classified": [[10, "meta-data-in-nested-dicom-sequences-can-not-be-independently-classified"]], "Minc1File": [[99, "minc1file"]], "Minc1Header": [[99, "minc1header"]], "Minc1Image": [[99, "minc1image"]], "Minc2File": [[100, "minc2file"]], "Minc2Header": [[100, "minc2header"]], "Minc2Image": [[100, "minc2image"]], "MincError": [[99, "mincerror"]], "MincHeader": [[99, "mincheader"]], "MincImageArrayProxy": [[99, "mincimagearrayproxy"]], "Miscellaneous Helpers": [[0, "miscellaneous-helpers"]], "Modeling": [[15, "modeling"]], "Module: benchmarks.bench_array_to_file": [[73, "module-nibabel.benchmarks.bench_array_to_file"]], "Module: benchmarks.bench_arrayproxy_slicing": [[73, "module-nibabel.benchmarks.bench_arrayproxy_slicing"]], "Module: benchmarks.bench_fileslice": [[73, "module-nibabel.benchmarks.bench_fileslice"]], "Module: benchmarks.bench_finite_range": [[73, "module-nibabel.benchmarks.bench_finite_range"]], "Module: benchmarks.bench_load_save": [[73, "module-nibabel.benchmarks.bench_load_save"]], "Module: benchmarks.butils": [[73, "module-nibabel.benchmarks.butils"]], "Module: cifti2.cifti2": [[77, "module-nibabel.cifti2.cifti2"]], "Module: cifti2.cifti2_axes": [[77, "module-nibabel.cifti2.cifti2_axes"]], "Module: cifti2.parse_cifti2": [[77, "module-nibabel.cifti2.parse_cifti2"]], "Module: cmdline.conform": [[78, "module-nibabel.cmdline.conform"]], "Module: cmdline.convert": [[78, "module-nibabel.cmdline.convert"]], "Module: cmdline.dicomfs": [[78, "module-nibabel.cmdline.dicomfs"]], "Module: cmdline.diff": [[78, "module-nibabel.cmdline.diff"]], "Module: cmdline.ls": [[78, "module-nibabel.cmdline.ls"]], "Module: cmdline.nifti_dx": [[78, "module-nibabel.cmdline.nifti_dx"]], "Module: cmdline.parrec2nii": [[78, "module-nibabel.cmdline.parrec2nii"]], "Module: cmdline.roi": [[78, "module-nibabel.cmdline.roi"]], "Module: cmdline.stats": [[78, "module-nibabel.cmdline.stats"]], "Module: cmdline.tck2trk": [[78, "module-nibabel.cmdline.tck2trk"]], "Module: cmdline.trk2tck": [[78, "module-nibabel.cmdline.trk2tck"]], "Module: cmdline.utils": [[78, "module-nibabel.cmdline.utils"]], "Module: freesurfer.io": [[92, "module-nibabel.freesurfer.io"]], "Module: freesurfer.mghformat": [[92, "module-nibabel.freesurfer.mghformat"]], "Module: gifti.gifti": [[94, "module-nibabel.gifti.gifti"]], "Module: gifti.parse_gifti_fast": [[94, "module-nibabel.gifti.parse_gifti_fast"]], "Module: gifti.util": [[94, "module-nibabel.gifti.util"]], "Module: nicom.ascconv": [[102, "module-nibabel.nicom.ascconv"]], "Module: nicom.csareader": [[102, "module-nibabel.nicom.csareader"]], "Module: nicom.dicomreaders": [[102, "module-nibabel.nicom.dicomreaders"]], "Module: nicom.dicomwrappers": [[102, "module-nibabel.nicom.dicomwrappers"]], "Module: nicom.dwiparams": [[102, "module-nibabel.nicom.dwiparams"]], "Module: nicom.structreader": [[102, "module-nibabel.nicom.structreader"]], "Module: nicom.utils": [[102, "module-nibabel.nicom.utils"]], "Module: streamlines.array_sequence": [[119, "module-nibabel.streamlines.array_sequence"]], "Module: streamlines.header": [[119, "module-nibabel.streamlines.header"]], "Module: streamlines.tck": [[119, "module-nibabel.streamlines.tck"]], "Module: streamlines.tractogram": [[119, "module-nibabel.streamlines.tractogram"]], "Module: streamlines.tractogram_file": [[119, "module-nibabel.streamlines.tractogram_file"]], "Module: streamlines.trk": [[119, "module-nibabel.streamlines.trk"]], "Module: streamlines.utils": [[119, "module-nibabel.streamlines.utils"]], "ModuleProxy": [[81, "moduleproxy"]], "More about checks": [[72, "more-about-checks"]], "MosaicWrapper": [[102, "mosaicwrapper"]], "Motivation": [[10, "motivation"], [11, "motivation"], [20, "motivation"], [27, "motivation"]], "Motivation and Scope": [[16, "motivation-and-scope"]], "Moving from patching to development": [[52, "moving-from-patching-to-development"]], "Multi-frame images": [[33, "multi-frame-images"]], "MultiframeWrapper": [[102, "multiframewrapper"]], "Naming reference spaces": [[2, "naming-reference-spaces"]], "NetCDF": [[58, "netcdf"]], "Neurological / radiological voxel layout": [[60, "neurological-radiological-voxel-layout"]], "Neurological and radiological display convention": [[60, "neurological-and-radiological-display-convention"]], "New features": [[1, "new-features"], [1, "id4"], [1, "id14"], [1, "id24"], [1, "id32"], [1, "id38"], [1, "id48"], [1, "id65"], [1, "id74"], [1, "id78"], [1, "id85"], [1, "id90"]], "Next steps:": [[13, "next-steps"]], "NiBabel": [[56, null], [58, "nibabel"]], "NiBabel Developer Guidelines": [[22, null]], "NiBabel Development Changelog": [[1, null]], "NiBabel Manual": [[59, null]], "NiBabel source code": [[22, "nibabel-source-code"]], "Nibabel always uses an RAS+ output space": [[2, "nibabel-always-uses-an-ras-output-space"]], "Nibabel images": [[61, null]], "Nibabel releases": [[1, "nibabel-releases"]], "Nifti extension types": [[9, "nifti-extension-types"]], "Nifti1DicomExtension": [[103, "nifti1dicomextension"]], "Nifti1Extension": [[103, "nifti1extension"]], "Nifti1Extensions": [[103, "nifti1extensions"]], "Nifti1Header": [[103, "nifti1header"]], "Nifti1Image": [[103, "nifti1image"]], "Nifti1Pair": [[103, "nifti1pair"]], "Nifti1PairHeader": [[103, "nifti1pairheader"]], "Nifti2Header": [[104, "nifti2header"]], "Nifti2Image": [[104, "nifti2image"]], "Nifti2Pair": [[104, "nifti2pair"]], "Nifti2PairHeader": [[104, "nifti2pairheader"]], "NiftiExtension": [[103, "niftiextension"]], "NoValue": [[102, "novalue"]], "Notes": [[69, "notes"], [74, "notes"]], "Opener": [[106, "opener"]], "Option 1: fancy slice object": [[8, "option-1-fancy-slice-object"]], "Option 2: not-fancy method call": [[8, "option-2-not-fancy-method-call"]], "Options": [[13, "options"]], "OrderedSet": [[58, "orderedset"]], "Orientation": [[109, "orientation"]], "OrientationError": [[108, "orientationerror"]], "OrthoSlicer3D": [[122, "orthoslicer3d"]], "Other reference spaces": [[2, "other-reference-spaces"]], "Other relevant formats": [[15, "other-relevant-formats"]], "Overview": [[10, "overview"], [11, "overview"], [18, "overview"], [42, "overview"], [43, "overview"], [47, "overview"], [52, "overview"], [53, "overview"]], "PAR file format": [[109, "par-file-format"]], "PARRECArrayProxy": [[109, "parrecarrayproxy"]], "PARRECError": [[109, "parrecerror"]], "PARRECHeader": [[109, "parrecheader"]], "PARRECImage": [[109, "parrecimage"]], "Package": [[20, "package"]], "Package instantiation": [[20, "package-instantiation"]], "Package name": [[20, "package-name"]], "Package provider bundle": [[20, "package-provider-bundle"]], "ParcelsAxis": [[77, "parcelsaxis"]], "PerArrayDict": [[119, "perarraydict"]], "PerArraySequenceDict": [[119, "perarraysequencedict"]], "Philips PAR/REC data": [[58, "philips-par-rec-data"]], "Philosophy": [[3, "philosophy"]], "Pinstance installation": [[20, "pinstance-installation"]], "Pinstance metadata query": [[20, "pinstance-metadata-query"]], "Pinstance metadata query source": [[20, "pinstance-metadata-query-source"]], "Pinstance release": [[20, "pinstance-release"]], "Pinstance revision": [[20, "pinstance-revision"]], "Pinstance revision id": [[20, "pinstance-revision-id"]], "Pinstance tag": [[20, "pinstance-tag"]], "Pinstance version": [[20, "pinstance-version"]], "Pixel spacing": [[38, "pixel-spacing"]], "Plan": [[10, "plan"], [10, "id1"], [10, "id2"], [10, "id3"]], "Plotting": [[15, "plotting"]], "Pointset": [[110, "id1"]], "Possible implementation": [[27, "possible-implementation"]], "Possible solutions to finding axes": [[12, "possible-solutions-to-finding-axes"]], "Possible volume resort": [[40, "possible-volume-resort"]], "Pre-commit hooks": [[22, "pre-commit-hooks"]], "Preliminaries": [[62, "preliminaries"]], "Principles of data package": [[20, null]], "Private attribute tags": [[35, "private-attribute-tags"]], "Prominent use cases": [[15, "prominent-use-cases"]], "Proposal": [[7, "proposal"], [9, "proposal"], [15, "proposal"]], "Proposal - add, prefer get_fdata method": [[14, "proposal-add-prefer-get-fdata-method"]], "Provider bundle format": [[20, "provider-bundle-format"]], "Proxies and caching": [[61, "proxies-and-caching"]], "Proxy images": [[7, "proxy-images"]], "Prundle discovery": [[20, "prundle-discovery"]], "Prundle discovery source": [[20, "prundle-discovery-source"]], "Push to trunk": [[51, "push-to-trunk"]], "PyNifti releases": [[1, "pynifti-releases"]], "Questions": [[7, "questions"], [8, "questions"], [9, "questions"]], "Quickstart": [[66, "quickstart"]], "Radiological vs neurological conventions": [[60, null]], "Range": [[28, "range"]], "Rebasing on trunk": [[43, "rebasing-on-trunk"]], "Recoder": [[123, "recoder"]], "Recovering from mess-ups": [[43, "recovering-from-mess-ups"]], "References": [[105, "references"]], "References and Footnotes": [[6, "references-and-footnotes"]], "Related Work": [[16, "related-work"]], "Relationship between images and io implementations": [[64, null]], "Release checklist": [[26, "release-checklist"]], "Report": [[72, "report"]], "Requirements": [[57, "requirements"]], "ResetMixin": [[105, "resetmixin"]], "Resolution": [[7, "resolution"]], "Return an image sequence from load for some file formats": [[13, "return-an-image-sequence-from-load-for-some-file-formats"]], "Review and Resolution": [[6, "review-and-resolution"]], "Reviewing": [[19, "reviewing"]], "Rewriting commit history": [[43, "rewriting-commit-history"]], "Roadmap": [[28, null]], "Roles And Responsibilities": [[23, "roles-and-responsibilities"]], "Rotation matrix orthogonality": [[63, "rotation-matrix-orthogonality"]], "SPM DICOM conversion": [[40, null]], "SPM image methods / functions": [[30, "spm-image-methods-functions"]], "SQ VR type (Sequence of items type)": [[40, "sq-vr-type-sequence-of-items-type"]], "Sample images": [[34, "sample-images"]], "Saving memory": [[55, "saving-memory"]], "Saving time and memory": [[55, "saving-time-and-memory"]], "ScalarAxis": [[77, "scalaraxis"]], "Scalefactors and intercepts": [[29, null]], "ScalingError": [[71, "scalingerror"]], "Second pass": [[40, "second-pass"]], "See also": [[9, "see-also"]], "Separation of ideas": [[20, "separation-of-ideas"]], "SerializableImage": [[87, "serializableimage"]], "SeriesAxis": [[77, "seriesaxis"]], "Set up and configure a github account": [[45, "set-up-and-configure-a-github-account"]], "Set up your fork": [[53, null]], "Setup": [[5, "setup"]], "Several people sharing a single repository": [[43, "several-people-sharing-a-single-repository"]], "Should slice0 be a copy or a view?": [[8, "should-slice0-be-a-copy-or-a-view"]], "Siemens format DICOM with CSA header": [[39, null]], "Siemens mosaic format": [[36, null]], "SiemensWrapper": [[102, "siemenswrapper"]], "SliceableDataDict": [[119, "sliceabledatadict"]], "SlopeArrayWriter": [[71, "slopearraywriter"]], "SlopeInterArrayWriter": [[71, "slopeinterarraywriter"]], "Small files": [[4, "small-files"]], "Smoothing": [[15, "smoothing"]], "Some formats store images with different shapes in the same file": [[13, "some-formats-store-images-with-different-shapes-in-the-same-file"]], "Some other things you might want to do": [[43, "some-other-things-you-might-want-to-do"]], "Some tag modifications": [[31, "some-tag-modifications"]], "Some usecases": [[20, "some-usecases"]], "Sorting files into volumes": [[40, "sorting-files-into-volumes"]], "Sorting slices into volumes": [[31, "sorting-slices-into-volumes"]], "Spatial transforms": [[28, "spatial-transforms"]], "SpatialFirstSlicer": [[116, "spatialfirstslicer"]], "SpatialHeader": [[116, "spatialheader"]], "SpatialImage": [[116, "spatialimage"]], "SpatialProtocol": [[116, "spatialprotocol"]], "Sphinx autosummary extension": [[58, "sphinx-autosummary-extension"]], "Spm2AnalyzeHeader": [[117, "spm2analyzeheader"]], "Spm2AnalyzeImage": [[117, "spm2analyzeimage"]], "Spm99AnalyzeHeader": [[118, "spm99analyzeheader"]], "Spm99AnalyzeImage": [[118, "spm99analyzeimage"]], "SpmAnalyzeHeader": [[118, "spmanalyzeheader"]], "Standard attribute tags": [[35, "standard-attribute-tags"]], "Start header": [[39, "start-header"], [39, "id2"]], "Status": [[8, "status"], [10, "status"]], "Steering Council": [[23, "steering-council"]], "Style guide": [[22, "style-guide"]], "Subsampling CIFTI-2": [[15, "subsampling-cifti-2"]], "Summary": [[14, "summary"], [20, "summary"], [27, "summary"]], "Summary and sign-off": [[64, "summary-and-sign-off"]], "Support": [[56, "support"]], "Surface data is generally kept separate from geometric metadata": [[15, "surface-data-is-generally-kept-separate-from-geometric-metadata"]], "System utilities": [[0, "system-utilities"]], "Tag length": [[40, "tag-length"]], "TckFile": [[119, "tckfile"]], "TemporaryDirectory": [[120, "temporarydirectory"]], "Terminology": [[15, "terminology"]], "Testing": [[22, "testing"], [56, "testing"], [66, "testing"]], "The Analyze header format": [[69, "the-analyze-header-format"]], "The Community": [[23, "the-community"]], "The DICOM standard": [[35, "the-dicom-standard"]], "The NIfTI affines": [[62, "the-nifti-affines"]], "The NIfTI header": [[62, "the-nifti-header"]], "The affine as a series of transformations": [[2, "the-affine-as-a-series-of-transformations"]], "The affine by example": [[2, "the-affine-by-example"]], "The affine matrix as a transformation between spaces": [[2, "the-affine-matrix-as-a-transformation-between-spaces"]], "The axis metadata element": [[9, "the-axis-metadata-element"]], "The current nibabel convention": [[12, "the-current-nibabel-convention"]], "The editing workflow": [[43, "the-editing-workflow"]], "The fall-back header affine": [[62, "the-fall-back-header-affine"]], "The format can be read-only": [[3, "the-format-can-be-read-only"]], "The header must contain the header version": [[9, "the-header-must-contain-the-header-version"]], "The header will often contain axis metadata": [[9, "the-header-will-often-contain-axis-metadata"]], "The header will usually contain axis names": [[9, "the-header-will-usually-contain-axis-names"]], "The header will usually contain image metadata fields": [[9, "the-header-will-usually-contain-image-metadata-fields"]], "The image API": [[3, "the-image-api"]], "The image data array": [[61, "the-image-data-array"]], "The image header": [[61, "the-image-header"]], "The image object": [[61, "the-image-object"]], "The inverse of the affine gives the mapping from scanner to voxel": [[2, "the-inverse-of-the-affine-gives-the-mapping-from-scanner-to-voxel"]], "The nibabel image object": [[24, null]], "The q_vector axis metadata field": [[9, "the-q-vector-axis-metadata-field"]], "The qform affine": [[62, "the-qform-affine"]], "The scanner axes": [[2, "the-scanner-axes"]], "The scanner-subject reference space": [[2, "the-scanner-subject-reference-space"]], "The sform affine": [[62, "the-sform-affine"]], "There are several ways of writing data.": [[116, "there-are-several-ways-of-writing-data"]], "Time axis as the fourth axis": [[12, "time-axis-as-the-fourth-axis"]], "Tractogram": [[119, "tractogram"]], "TractogramFile": [[119, "tractogramfile"]], "TractogramItem": [[119, "tractogramitem"]], "TripWire": [[121, "id1"]], "TripWireError": [[121, "tripwireerror"]], "TrkFile": [[119, "trkfile"]], "Tutorials and summaries": [[49, "tutorials-and-summaries"]], "Types": [[6, "types"]], "Types of BV files": [[18, "types-of-bv-files"]], "TypesFilenamesError": [[89, "typesfilenameserror"]], "Uncache the array": [[55, "uncache-the-array"]], "Unpacker": [[102, "unpacker"]], "Update the mirror of trunk": [[43, "update-the-mirror-of-trunk"]], "Updating the code": [[44, "updating-the-code"]], "Usage and Impact": [[16, "usage-and-impact"]], "Use case": [[9, "use-case"]], "Use cases": [[7, "use-cases"]], "Use the array proxy instead of get_fdata()": [[55, "use-the-array-proxy-instead-of-get-fdata"]], "Use the caching keyword to get_fdata()": [[55, "use-the-caching-keyword-to-get-fdata"]], "Using convention : enforcing time as 4th axis": [[12, "using-convention-enforcing-time-as-4th-axis"]], "Using in_memory to check the state of the cache": [[55, "using-in-memory-to-check-the-state-of-the-cache"]], "Using submodules for tests": [[4, "using-submodules-for-tests"]], "Using uncache": [[55, "using-uncache"]], "Validating your install": [[57, "validating-your-install"]], "Value Multiplicity in the data dictionary": [[35, "value-multiplicity-in-the-data-dictionary"]], "Value Representation": [[35, "value-representation"]], "Value Representation in the data dictionary": [[35, "value-representation-in-the-data-dictionary"]], "Value field": [[35, "value-field"]], "Value length": [[35, "value-length"]], "Vendor": [[102, "vendor"]], "VersionedDatasource": [[79, "versioneddatasource"]], "VisibleDeprecationWarning": [[81, "visibledeprecationwarning"]], "VolumeError": [[83, "volumeerror"]], "Voxel coordinates and points in space": [[2, "voxel-coordinates-and-points-in-space"]], "Voxel coordinates are coordinates in the image data array": [[2, "voxel-coordinates-are-coordinates-in-the-image-data-array"]], "Voxel coordinates are in voxel space": [[2, "voxel-coordinates-are-in-voxel-space"]], "What is a BIAP?": [[6, "what-is-a-biap"]], "What slices should the slicing allow?": [[8, "what-slices-should-the-slicing-allow"]], "When do you want a copy and when do you want a view?": [[7, "when-do-you-want-a-copy-and-when-do-you-want-a-view"]], "Where to start with the code": [[3, "where-to-start-with-the-code"]], "Workflow summary": [[43, "workflow-summary"]], "Working out the Z coordinates for a set of slices": [[38, "working-out-the-z-coordinates-for-a-set-of-slices"]], "Working with NIfTI images": [[62, null]], "Working with nibabel source code": [[50, null]], "WrapStruct": [[124, "id2"]], "WrapStructError": [[124, "wrapstructerror"]], "Wrapper": [[102, "wrapper"]], "WrapperError": [[102, "wrappererror"]], "WrapperPrecisionError": [[102, "wrapperprecisionerror"]], "WriterError": [[71, "writererror"]], "Writing DICOM volumes": [[40, "writing-dicom-volumes"]], "Writing the voxel data": [[40, "writing-the-voxel-data"]], "XmlBasedHeader": [[125, "xmlbasedheader"]], "XmlParser": [[125, "xmlparser"]], "XmlSerializable": [[125, "xmlserializable"]], "_compression": [[67, null]], "able_int_type": [[76, "able-int-type"]], "abstractclassmethod": [[119, "abstractclassmethod"]], "acquisition_times applying to slices": [[9, "acquisition-times-applying-to-slices"]], "acquisition_times applying to slices and volumes": [[9, "acquisition-times-applying-to-slices-and-volumes"]], "acquisition_times applying to volumes": [[9, "acquisition-times-applying-to-volumes"]], "acquisition_times field": [[9, "acquisition-times-field"]], "adapt_affine": [[111, "adapt-affine"]], "aff2axcodes": [[108, "aff2axcodes"]], "affines": [[68, null]], "alert_future_error": [[81, "alert-future-error"]], "analyze": [[69, null]], "angle_axis2euler": [[86, "angle-axis2euler"]], "angle_axis2mat": [[113, "angle-axis2mat"]], "angle_axis2quat": [[113, "angle-axis2quat"]], "ap": [[78, "ap"]], "append_diag": [[68, "append-diag"]], "apply_affine": [[68, "apply-affine"]], "apply_orientation": [[108, "apply-orientation"]], "apply_read_scaling": [[123, "apply-read-scaling"]], "are_values_different": [[78, "are-values-different"]], "array_from_file": [[123, "array-from-file"]], "array_to_file": [[123, "array-to-file"]], "arrayproxy": [[70, null]], "arraywriters": [[71, null]], "as_closest_canonical": [[93, "as-closest-canonical"]], "as_int": [[76, "as-int"]], "assign2atoms": [[102, "assign2atoms"]], "axcodes2ornt": [[108, "axcodes2ornt"]], "axis_meanings field": [[9, "axis-meanings-field"]], "batteryrunners": [[72, null]], "bench": [[66, "bench"]], "bench_array_to_file": [[73, "bench-array-to-file"]], "bench_arrayproxy_slicing": [[73, "bench-arrayproxy-slicing"]], "bench_fileslice": [[73, "bench-fileslice"]], "bench_finite_range": [[73, "bench-finite-range"]], "bench_load_save": [[73, "bench-load-save"]], "benchmarks": [[73, null]], "best_float": [[76, "best-float"]], "best_write_scale_ftype": [[123, "best-write-scale-ftype"]], "better_float_of": [[123, "better-float-of"]], "brikhead": [[74, null]], "calc_slicedefs": [[90, "calc-slicedefs"]], "calculate_dwell_time": [[101, "calculate-dwell-time"]], "canonical_slicers": [[90, "canonical-slicers"]], "caret": [[75, null]], "casting": [[76, null]], "ceil_exact": [[76, "ceil-exact"]], "cifti2": [[77, null]], "clear_cache": [[83, "clear-cache"]], "cmdline": [[78, null]], "concat_images": [[93, "concat-images"]], "concatenate": [[119, "concatenate"]], "conform": [[111, "conform"]], "conjugate": [[113, "conjugate"]], "copy_file_map": [[88, "copy-file-map"]], "count_nonzero_voxels": [[97, "count-nonzero-voxels"]], "create_arraysequences_from_generator": [[119, "create-arraysequences-from-generator"]], "data": [[79, null]], "dataobj_images": [[80, null]], "datasource_or_bomber": [[79, "datasource-or-bomber"]], "dcm2nii algorithms": [[31, null]], "decode_value_from_name": [[119, "decode-value-from-name"]], "deprecated": [[81, null]], "deprecator": [[82, null]], "detect_format": [[119, "detect-format"]], "dft": [[83, null]], "dicom_test": [[112, "dicom-test"]], "diff": [[78, "diff"]], "display_diff": [[78, "display-diff"]], "dot_reduce": [[68, "dot-reduce"]], "dummy_fuse": [[78, "dummy-fuse"]], "ecat": [[84, null]], "encode_value_in_name": [[119, "encode-value-in-name"]], "environment": [[85, null]], "error": [[78, "error"]], "euler2angle_axis": [[86, "euler2angle-axis"]], "euler2mat": [[86, "euler2mat"]], "euler2quat": [[86, "euler2quat"]], "eulerangles": [[86, null]], "exts2pars": [[109, "exts2pars"]], "eye": [[113, "eye"]], "filebasedimages": [[87, null]], "fileholders": [[88, null]], "filename_parser": [[89, null]], "fileslice": [[90, null], [90, "id1"]], "fileutils": [[91, null]], "fill_slicer": [[90, "fill-slicer"]], "fillpositive": [[113, "fillpositive"]], "find_data_dir": [[79, "find-data-dir"]], "find_private_section": [[102, "find-private-section"]], "finite_range": [[123, "finite-range"]], "flip_axis": [[108, "flip-axis"]], "float_to_int": [[76, "float-to-int"]], "floor_exact": [[76, "floor-exact"]], "floor_log2": [[76, "floor-log2"]], "fname_ext_ul_case": [[123, "fname-ext-ul-case"]], "four_to_three": [[93, "four-to-three"]], "freesurfer": [[92, null]], "from_index_mapping": [[77, "from-index-mapping"]], "from_matvec": [[68, "from-matvec"]], "funcs": [[93, null]], "fuse": [[78, "fuse"]], "fwhm2sigma": [[111, "fwhm2sigma"]], "get_acq_mat_txt": [[102, "get-acq-mat-txt"]], "get_affine_from_reference": [[119, "get-affine-from-reference"]], "get_affine_rasmm_to_trackvis": [[119, "get-affine-rasmm-to-trackvis"]], "get_affine_trackvis_to_rasmm": [[119, "get-affine-trackvis-to-rasmm"]], "get_b_matrix": [[102, "get-b-matrix"]], "get_b_value": [[102, "get-b-value"]], "get_csa_header": [[102, "get-csa-header"]], "get_data_diff": [[78, "get-data-diff"]], "get_data_hash_diff": [[78, "get-data-hash-diff"]], "get_data_path": [[79, "get-data-path"]], "get_frame_order": [[84, "get-frame-order"]], "get_g_vector": [[102, "get-g-vector"]], "get_headers_diff": [[78, "get-headers-diff"]], "get_home_dir": [[85, "get-home-dir"]], "get_ice_dims": [[102, "get-ice-dims"]], "get_info": [[66, "get-info"]], "get_n_mosaic": [[102, "get-n-mosaic"]], "get_nipy_system_dir": [[85, "get-nipy-system-dir"]], "get_nipy_user_dir": [[85, "get-nipy-user-dir"]], "get_obj_dtype": [[70, "get-obj-dtype"]], "get_opt_parser": [[78, "get-opt-parser"], [78, "id3"], [78, "id5"], [78, "id8"]], "get_scalar": [[102, "get-scalar"]], "get_series_framenumbers": [[84, "get-series-framenumbers"]], "get_slice_normal": [[102, "get-slice-normal"]], "get_slope_inter": [[71, "get-slope-inter"]], "get_studies": [[83, "get-studies"]], "get_vector": [[102, "get-vector"]], "gifti": [[94, null]], "git resources": [[49, null]], "guessed_image_type": [[98, "guessed-image-type"]], "have_binary128": [[76, "have-binary128"]], "imageclasses": [[95, null]], "imageglobals": [[96, null]], "imagestats": [[97, null]], "int_abs": [[76, "int-abs"]], "int_scinter_ftype": [[123, "int-scinter-ftype"]], "int_to_float": [[76, "int-to-float"]], "inv_ornt_aff": [[108, "inv-ornt-aff"]], "inverse": [[113, "inverse"]], "io_orientation": [[108, "io-orientation"]], "is_array_sequence": [[119, "is-array-sequence"]], "is_data_dict": [[119, "is-data-dict"]], "is_fancy": [[90, "is-fancy"]], "is_lazy_dict": [[119, "is-lazy-dict"]], "is_mosaic": [[102, "is-mosaic"]], "is_ndarray_of_int_or_bool": [[119, "is-ndarray-of-int-or-bool"]], "is_proxy": [[70, "is-proxy"]], "is_supported": [[119, "is-supported"]], "is_tripwire": [[121, "is-tripwire"]], "isunit": [[113, "isunit"]], "load": [[98, "load"], [103, "load"], [104, "load"], [119, "load"]], "loadsave": [[98, null]], "longdouble_lte_float64": [[76, "longdouble-lte-float64"]], "longdouble_precision_improved": [[76, "longdouble-precision-improved"]], "lossless_slice": [[78, "lossless-slice"]], "main": [[78, "main"], [78, "id1"], [78, "id2"], [78, "id4"], [78, "id6"], [78, "id7"], [78, "id9"], [78, "id11"], [78, "id12"], [78, "id13"], [78, "id14"]], "make_array_writer": [[71, "make-array-writer"]], "make_datasource": [[79, "make-datasource"]], "make_dt_codes": [[123, "make-dt-codes"]], "mask_volume": [[97, "mask-volume"]], "mat2euler": [[86, "mat2euler"]], "mat2quat": [[113, "mat2quat"]], "minc1": [[99, null]], "minc2": [[100, null]], "mni_icbm152_t1_tal_nlin_asym_09a": [[58, "mni-icbm152-t1-tal-nlin-asym-09a"]], "mosaic_to_nii": [[102, "mosaic-to-nii"]], "mriutils": [[101, null]], "mult": [[113, "mult"]], "multi_affine field": [[9, "multi-affine-field"]], "nearest_pos_semi_def": [[102, "nearest-pos-semi-def"]], "nearly_equivalent": [[113, "nearly-equivalent"]], "nibabel": [[66, null]], "nicom": [[102, null]], "nifti1": [[103, null]], "nifti2": [[104, null]], "none_or_close": [[102, "none-or-close"]], "norm": [[113, "norm"]], "nt_str": [[102, "nt-str"]], "obj_from_atoms": [[102, "obj-from-atoms"]], "obliquity": [[68, "obliquity"]], "ok_floats": [[76, "ok-floats"]], "on_powerpc": [[76, "on-powerpc"]], "one_line": [[109, "one-line"]], "onetime": [[105, null]], "openers": [[106, null]], "optimize_read_slicers": [[90, "optimize-read-slicers"]], "optimize_slicer": [[90, "optimize-slicer"]], "optional_package": [[107, "optional-package"]], "optpkg": [[107, null]], "orientations": [[108, null]], "ornt2axcodes": [[108, "ornt2axcodes"]], "ornt_transform": [[108, "ornt-transform"]], "parrec": [[109, null]], "parse_AFNI_header": [[74, "parse-afni-header"]], "parse_PAR_header": [[109, "parse-par-header"]], "parse_args": [[78, "parse-args"], [78, "id15"]], "parse_ascconv": [[102, "parse-ascconv"]], "parse_filename": [[89, "parse-filename"]], "parse_slice": [[78, "parse-slice"]], "peek_next": [[119, "peek-next"]], "pip and the Python package index": [[57, "pip-and-the-python-package-index"]], "pointset": [[110, null]], "predict_shape": [[90, "predict-shape"]], "pretty_mapping": [[123, "pretty-mapping"]], "print_git_title": [[73, "print-git-title"]], "proc_file": [[78, "proc-file"], [78, "id10"]], "processing": [[111, null]], "pydicom_compat": [[112, null]], "q2bg": [[102, "q2bg"]], "quat2angle_axis": [[113, "quat2angle-axis"]], "quat2euler": [[86, "quat2euler"]], "quat2mat": [[113, "quat2mat"]], "quaternions": [[113, null]], "read": [[102, "read"]], "read_annot": [[92, "read-annot"]], "read_data_block": [[94, "read-data-block"]], "read_geometry": [[92, "read-geometry"]], "read_img_data": [[98, "read-img-data"]], "read_label": [[92, "read-label"]], "read_mlist": [[84, "read-mlist"]], "read_morph_data": [[92, "read-morph-data"]], "read_mosaic_dir": [[102, "read-mosaic-dir"]], "read_mosaic_dwi_dir": [[102, "read-mosaic-dwi-dir"]], "read_segments": [[90, "read-segments"]], "read_subheaders": [[84, "read-subheaders"]], "read_zt_byte_strings": [[91, "read-zt-byte-strings"]], "rec2dict": [[123, "rec2dict"]], "resample_from_to": [[111, "resample-from-to"]], "resample_to_output": [[111, "resample-to-output"]], "rescale_affine": [[68, "rescale-affine"]], "reshape_dataobj": [[70, "reshape-dataobj"]], "rotate_vector": [[113, "rotate-vector"]], "rst_table": [[114, "rst-table"]], "rstutils": [[114, null]], "run_slices": [[73, "run-slices"]], "safe_get": [[78, "safe-get"]], "sanitize": [[78, "sanitize"]], "save": [[98, "save"], [103, "save"], [104, "save"], [119, "save"]], "seek_tell": [[123, "seek-tell"]], "shape_zoom_affine": [[123, "shape-zoom-affine"]], "shared_range": [[76, "shared-range"]], "sigma2fwhm": [[111, "sigma2fwhm"]], "slice2len": [[90, "slice2len"]], "slice2outax": [[90, "slice2outax"]], "slice2volume": [[115, "slice2volume"]], "slicers2segments": [[90, "slicers2segments"]], "slices_to_series": [[102, "slices-to-series"]], "smooth_image": [[111, "smooth-image"]], "spaces": [[115, null]], "spatial_axes_first": [[95, "spatial-axes-first"]], "spatialimages": [[116, null]], "splitext_addext": [[89, "splitext-addext"]], "spm2analyze": [[117, null]], "spm99analyze": [[118, null]], "spm_dicom_convert.m": [[40, "spm-dicom-convert-m"]], "spm_dicom_dict.mat": [[40, "spm-dicom-dict-mat"]], "spm_dicom_headers.m": [[40, "spm-dicom-headers-m"]], "squeeze_image": [[93, "squeeze-image"]], "streamlines": [[119, null]], "strided_scalar": [[90, "strided-scalar"]], "supported_np_types": [[116, "supported-np-types"]], "table2string": [[78, "table2string"]], "tag read for Philips Integra": [[40, "tag-read-for-philips-integra"]], "test": [[66, "test"]], "threshold_heuristic": [[90, "threshold-heuristic"]], "tmpdirs": [[120, null]], "to_header": [[77, "to-header"]], "to_matvec": [[68, "to-matvec"]], "tripwire": [[121, null]], "type_info": [[76, "type-info"]], "types_filenames": [[89, "types-filenames"]], "ulp": [[76, "ulp"]], "update_cache": [[83, "update-cache"]], "user.name and user.email": [[42, "user-name-and-user-email"]], "vendor_from_private": [[102, "vendor-from-private"]], "verbose": [[78, "verbose"], [78, "id16"]], "viewers": [[122, null]], "vol_is_full": [[109, "vol-is-full"]], "vol_numbers": [[109, "vol-numbers"]], "volumeutils": [[123, null]], "vox2out_vox": [[115, "vox2out-vox"]], "voxel_sizes": [[68, "voxel-sizes"]], "working_type": [[123, "working-type"]], "wrapper_from_data": [[102, "wrapper-from-data"]], "wrapper_from_file": [[102, "wrapper-from-file"]], "wrapstruct": [[124, null], [124, "id1"]], "write_annot": [[92, "write-annot"]], "write_geometry": [[92, "write-geometry"]], "write_morph_data": [[92, "write-morph-data"]], "write_zeros": [[123, "write-zeros"]], "xmlutils": [[125, null]]}, "docnames": ["api", "changelog", "coordinate_systems", "devel/add_image_format", "devel/add_test_data", "devel/advanced_testing", "devel/biaps/biap_0000", "devel/biaps/biap_0001", "devel/biaps/biap_0002", "devel/biaps/biap_0003", "devel/biaps/biap_0004", "devel/biaps/biap_0005", "devel/biaps/biap_0006", "devel/biaps/biap_0007", "devel/biaps/biap_0008", "devel/biaps/biap_0009", "devel/biaps/biap_template", "devel/biaps/index", "devel/bv_formats", "devel/core_developer", "devel/data_pkg_discuss", "devel/devdiscuss", "devel/devguide", "devel/governance", "devel/image_design", "devel/index", "devel/make_release", "devel/modified_images", "devel/roadmap", "devel/scaling", "devel/spm_use", "dicom/dcm2nii_algorithms", "dicom/dicom", "dicom/dicom_fields", "dicom/dicom_info", "dicom/dicom_intro", "dicom/dicom_mosaic", "dicom/dicom_niftiheader", "dicom/dicom_orientation", "dicom/siemens_csa", "dicom/spm_dicom", "gettingstarted", "gitwash/configure_git", "gitwash/development_workflow", "gitwash/following_latest", "gitwash/forking_hell", "gitwash/git_development", "gitwash/git_install", "gitwash/git_intro", "gitwash/git_resources", "gitwash/index", "gitwash/maintainer_workflow", "gitwash/patching", "gitwash/set_up_fork", "image_orientation", "images_and_memory", "index", "installation", "legal", "manual", "neuro_radio_conventions", "nibabel_images", "nifti_images", "notebooks/index", "old/ioimplementation", "reference/index", "reference/nibabel", "reference/nibabel._compression", "reference/nibabel.affines", "reference/nibabel.analyze", "reference/nibabel.arrayproxy", "reference/nibabel.arraywriters", "reference/nibabel.batteryrunners", "reference/nibabel.benchmarks", "reference/nibabel.brikhead", "reference/nibabel.caret", "reference/nibabel.casting", "reference/nibabel.cifti2", "reference/nibabel.cmdline", "reference/nibabel.data", "reference/nibabel.dataobj_images", "reference/nibabel.deprecated", "reference/nibabel.deprecator", "reference/nibabel.dft", "reference/nibabel.ecat", "reference/nibabel.environment", "reference/nibabel.eulerangles", "reference/nibabel.filebasedimages", "reference/nibabel.fileholders", "reference/nibabel.filename_parser", "reference/nibabel.fileslice", "reference/nibabel.fileutils", "reference/nibabel.freesurfer", "reference/nibabel.funcs", "reference/nibabel.gifti", "reference/nibabel.imageclasses", "reference/nibabel.imageglobals", "reference/nibabel.imagestats", "reference/nibabel.loadsave", "reference/nibabel.minc1", "reference/nibabel.minc2", "reference/nibabel.mriutils", "reference/nibabel.nicom", "reference/nibabel.nifti1", "reference/nibabel.nifti2", "reference/nibabel.onetime", "reference/nibabel.openers", "reference/nibabel.optpkg", "reference/nibabel.orientations", "reference/nibabel.parrec", "reference/nibabel.pointset", "reference/nibabel.processing", "reference/nibabel.pydicom_compat", "reference/nibabel.quaternions", "reference/nibabel.rstutils", "reference/nibabel.spaces", "reference/nibabel.spatialimages", "reference/nibabel.spm2analyze", "reference/nibabel.spm99analyze", "reference/nibabel.streamlines", "reference/nibabel.tmpdirs", "reference/nibabel.tripwire", "reference/nibabel.viewers", "reference/nibabel.volumeutils", "reference/nibabel.wrapstruct", "reference/nibabel.xmlutils", "tutorials"], "envversion": {"sphinx": 63, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.todo": 2}, "filenames": ["api.rst", "changelog.rst", "coordinate_systems.rst", "devel/add_image_format.rst", "devel/add_test_data.rst", "devel/advanced_testing.rst", "devel/biaps/biap_0000.rst", "devel/biaps/biap_0001.rst", "devel/biaps/biap_0002.rst", "devel/biaps/biap_0003.rst", "devel/biaps/biap_0004.rst", "devel/biaps/biap_0005.rst", "devel/biaps/biap_0006.rst", "devel/biaps/biap_0007.rst", "devel/biaps/biap_0008.rst", "devel/biaps/biap_0009.rst", "devel/biaps/biap_template.rst", "devel/biaps/index.rst", "devel/bv_formats.rst", "devel/core_developer.rst", "devel/data_pkg_discuss.rst", "devel/devdiscuss.rst", "devel/devguide.rst", "devel/governance.rst", "devel/image_design.rst", "devel/index.rst", "devel/make_release.rst", "devel/modified_images.rst", "devel/roadmap.rst", "devel/scaling.rst", "devel/spm_use.rst", "dicom/dcm2nii_algorithms.rst", "dicom/dicom.rst", "dicom/dicom_fields.rst", "dicom/dicom_info.rst", "dicom/dicom_intro.rst", "dicom/dicom_mosaic.rst", "dicom/dicom_niftiheader.rst", "dicom/dicom_orientation.rst", "dicom/siemens_csa.rst", "dicom/spm_dicom.rst", "gettingstarted.rst", "gitwash/configure_git.rst", "gitwash/development_workflow.rst", "gitwash/following_latest.rst", "gitwash/forking_hell.rst", "gitwash/git_development.rst", "gitwash/git_install.rst", "gitwash/git_intro.rst", "gitwash/git_resources.rst", "gitwash/index.rst", "gitwash/maintainer_workflow.rst", "gitwash/patching.rst", "gitwash/set_up_fork.rst", "image_orientation.rst", "images_and_memory.rst", "index.rst", "installation.rst", "legal.rst", "manual.rst", "neuro_radio_conventions.rst", "nibabel_images.rst", "nifti_images.rst", "notebooks/index.rst", "old/ioimplementation.rst", "reference/index.rst", "reference/nibabel.rst", "reference/nibabel._compression.rst", "reference/nibabel.affines.rst", "reference/nibabel.analyze.rst", "reference/nibabel.arrayproxy.rst", "reference/nibabel.arraywriters.rst", "reference/nibabel.batteryrunners.rst", "reference/nibabel.benchmarks.rst", "reference/nibabel.brikhead.rst", "reference/nibabel.caret.rst", "reference/nibabel.casting.rst", "reference/nibabel.cifti2.rst", "reference/nibabel.cmdline.rst", "reference/nibabel.data.rst", "reference/nibabel.dataobj_images.rst", "reference/nibabel.deprecated.rst", "reference/nibabel.deprecator.rst", "reference/nibabel.dft.rst", "reference/nibabel.ecat.rst", "reference/nibabel.environment.rst", "reference/nibabel.eulerangles.rst", "reference/nibabel.filebasedimages.rst", "reference/nibabel.fileholders.rst", "reference/nibabel.filename_parser.rst", "reference/nibabel.fileslice.rst", "reference/nibabel.fileutils.rst", "reference/nibabel.freesurfer.rst", "reference/nibabel.funcs.rst", "reference/nibabel.gifti.rst", "reference/nibabel.imageclasses.rst", "reference/nibabel.imageglobals.rst", "reference/nibabel.imagestats.rst", "reference/nibabel.loadsave.rst", "reference/nibabel.minc1.rst", "reference/nibabel.minc2.rst", "reference/nibabel.mriutils.rst", "reference/nibabel.nicom.rst", "reference/nibabel.nifti1.rst", "reference/nibabel.nifti2.rst", "reference/nibabel.onetime.rst", "reference/nibabel.openers.rst", "reference/nibabel.optpkg.rst", "reference/nibabel.orientations.rst", "reference/nibabel.parrec.rst", "reference/nibabel.pointset.rst", "reference/nibabel.processing.rst", "reference/nibabel.pydicom_compat.rst", "reference/nibabel.quaternions.rst", "reference/nibabel.rstutils.rst", "reference/nibabel.spaces.rst", "reference/nibabel.spatialimages.rst", "reference/nibabel.spm2analyze.rst", "reference/nibabel.spm99analyze.rst", "reference/nibabel.streamlines.rst", "reference/nibabel.tmpdirs.rst", "reference/nibabel.tripwire.rst", "reference/nibabel.viewers.rst", "reference/nibabel.volumeutils.rst", "reference/nibabel.wrapstruct.rst", "reference/nibabel.xmlutils.rst", "tutorials.rst"], "indexentries": {"__init__() (nibabel.affines.affineerror method)": [[68, "nibabel.affines.AffineError.__init__", false]], "__init__() (nibabel.analyze.analyzeheader method)": [[69, "nibabel.analyze.AnalyzeHeader.__init__", false]], "__init__() (nibabel.analyze.analyzeimage method)": [[69, "nibabel.analyze.AnalyzeImage.__init__", false]], "__init__() (nibabel.arrayproxy.arraylike method)": [[70, "nibabel.arrayproxy.ArrayLike.__init__", false]], "__init__() (nibabel.arrayproxy.arrayproxy method)": [[70, "nibabel.arrayproxy.ArrayProxy.__init__", false]], "__init__() (nibabel.arraywriters.arraywriter method)": [[71, "nibabel.arraywriters.ArrayWriter.__init__", false]], "__init__() (nibabel.arraywriters.scalingerror method)": [[71, "nibabel.arraywriters.ScalingError.__init__", false]], "__init__() (nibabel.arraywriters.slopearraywriter method)": [[71, "nibabel.arraywriters.SlopeArrayWriter.__init__", false]], "__init__() (nibabel.arraywriters.slopeinterarraywriter method)": [[71, "nibabel.arraywriters.SlopeInterArrayWriter.__init__", false]], "__init__() (nibabel.arraywriters.writererror method)": [[71, "nibabel.arraywriters.WriterError.__init__", false]], "__init__() (nibabel.batteryrunners.batteryrunner method)": [[72, "nibabel.batteryrunners.BatteryRunner.__init__", false]], "__init__() (nibabel.batteryrunners.report method)": [[72, "nibabel.batteryrunners.Report.__init__", false]], "__init__() (nibabel.brikhead.afniarrayproxy method)": [[74, "nibabel.brikhead.AFNIArrayProxy.__init__", false]], "__init__() (nibabel.brikhead.afniheader method)": [[74, "nibabel.brikhead.AFNIHeader.__init__", false]], "__init__() (nibabel.brikhead.afniheadererror method)": [[74, "nibabel.brikhead.AFNIHeaderError.__init__", false]], "__init__() (nibabel.brikhead.afniimage method)": [[74, "nibabel.brikhead.AFNIImage.__init__", false]], "__init__() (nibabel.brikhead.afniimageerror method)": [[74, "nibabel.brikhead.AFNIImageError.__init__", false]], "__init__() (nibabel.caret.caretmetadata method)": [[75, "nibabel.caret.CaretMetaData.__init__", false]], "__init__() (nibabel.casting.castingerror method)": [[76, "nibabel.casting.CastingError.__init__", false]], "__init__() (nibabel.casting.floatingerror method)": [[76, "nibabel.casting.FloatingError.__init__", false]], "__init__() (nibabel.cifti2.cifti2.cifti2brainmodel method)": [[77, "nibabel.cifti2.cifti2.Cifti2BrainModel.__init__", false]], "__init__() (nibabel.cifti2.cifti2.cifti2header method)": [[77, "nibabel.cifti2.cifti2.Cifti2Header.__init__", false]], "__init__() (nibabel.cifti2.cifti2.cifti2headererror method)": [[77, "nibabel.cifti2.cifti2.Cifti2HeaderError.__init__", false]], "__init__() (nibabel.cifti2.cifti2.cifti2image method)": [[77, "nibabel.cifti2.cifti2.Cifti2Image.__init__", false]], "__init__() (nibabel.cifti2.cifti2.cifti2label method)": [[77, "nibabel.cifti2.cifti2.Cifti2Label.__init__", false]], "__init__() (nibabel.cifti2.cifti2.cifti2labeltable method)": [[77, "nibabel.cifti2.cifti2.Cifti2LabelTable.__init__", false]], "__init__() (nibabel.cifti2.cifti2.cifti2matrix method)": [[77, "nibabel.cifti2.cifti2.Cifti2Matrix.__init__", false]], "__init__() (nibabel.cifti2.cifti2.cifti2matrixindicesmap method)": [[77, "nibabel.cifti2.cifti2.Cifti2MatrixIndicesMap.__init__", false]], "__init__() (nibabel.cifti2.cifti2.cifti2metadata method)": [[77, "nibabel.cifti2.cifti2.Cifti2MetaData.__init__", false]], "__init__() (nibabel.cifti2.cifti2.cifti2namedmap method)": [[77, "nibabel.cifti2.cifti2.Cifti2NamedMap.__init__", false]], "__init__() (nibabel.cifti2.cifti2.cifti2parcel method)": [[77, "nibabel.cifti2.cifti2.Cifti2Parcel.__init__", false]], "__init__() (nibabel.cifti2.cifti2.cifti2surface method)": [[77, "nibabel.cifti2.cifti2.Cifti2Surface.__init__", false]], "__init__() (nibabel.cifti2.cifti2.cifti2transformationmatrixvoxelindicesijktoxyz method)": [[77, "nibabel.cifti2.cifti2.Cifti2TransformationMatrixVoxelIndicesIJKtoXYZ.__init__", false]], "__init__() (nibabel.cifti2.cifti2.cifti2vertexindices method)": [[77, "nibabel.cifti2.cifti2.Cifti2VertexIndices.__init__", false]], "__init__() (nibabel.cifti2.cifti2.cifti2vertices method)": [[77, "nibabel.cifti2.cifti2.Cifti2Vertices.__init__", false]], "__init__() (nibabel.cifti2.cifti2.cifti2volume method)": [[77, "nibabel.cifti2.cifti2.Cifti2Volume.__init__", false]], "__init__() (nibabel.cifti2.cifti2.cifti2voxelindicesijk method)": [[77, "nibabel.cifti2.cifti2.Cifti2VoxelIndicesIJK.__init__", false]], "__init__() (nibabel.cifti2.cifti2.limitednifti2header method)": [[77, "nibabel.cifti2.cifti2.LimitedNifti2Header.__init__", false]], "__init__() (nibabel.cifti2.cifti2_axes.axis method)": [[77, "nibabel.cifti2.cifti2_axes.Axis.__init__", false]], "__init__() (nibabel.cifti2.cifti2_axes.brainmodelaxis method)": [[77, "nibabel.cifti2.cifti2_axes.BrainModelAxis.__init__", false]], "__init__() (nibabel.cifti2.cifti2_axes.labelaxis method)": [[77, "nibabel.cifti2.cifti2_axes.LabelAxis.__init__", false]], "__init__() (nibabel.cifti2.cifti2_axes.parcelsaxis method)": [[77, "nibabel.cifti2.cifti2_axes.ParcelsAxis.__init__", false]], "__init__() (nibabel.cifti2.cifti2_axes.scalaraxis method)": [[77, "nibabel.cifti2.cifti2_axes.ScalarAxis.__init__", false]], "__init__() (nibabel.cifti2.cifti2_axes.seriesaxis method)": [[77, "nibabel.cifti2.cifti2_axes.SeriesAxis.__init__", false]], "__init__() (nibabel.cifti2.parse_cifti2.cifti2extension method)": [[77, "nibabel.cifti2.parse_cifti2.Cifti2Extension.__init__", false]], "__init__() (nibabel.cifti2.parse_cifti2.cifti2parser method)": [[77, "nibabel.cifti2.parse_cifti2.Cifti2Parser.__init__", false]], "__init__() (nibabel.cmdline.dicomfs.dicomfs method)": [[78, "nibabel.cmdline.dicomfs.DICOMFS.__init__", false]], "__init__() (nibabel.cmdline.dicomfs.dummy_fuse method)": [[78, "nibabel.cmdline.dicomfs.dummy_fuse.__init__", false]], "__init__() (nibabel.cmdline.dicomfs.filehandle method)": [[78, "nibabel.cmdline.dicomfs.FileHandle.__init__", false]], "__init__() (nibabel.data.bomber method)": [[79, "nibabel.data.Bomber.__init__", false]], "__init__() (nibabel.data.bombererror method)": [[79, "nibabel.data.BomberError.__init__", false]], "__init__() (nibabel.data.dataerror method)": [[79, "nibabel.data.DataError.__init__", false]], "__init__() (nibabel.data.datasource method)": [[79, "nibabel.data.Datasource.__init__", false]], "__init__() (nibabel.data.versioneddatasource method)": [[79, "nibabel.data.VersionedDatasource.__init__", false]], "__init__() (nibabel.dataobj_images.dataobjimage method)": [[80, "nibabel.dataobj_images.DataobjImage.__init__", false]], "__init__() (nibabel.deprecated.futurewarningmixin method)": [[81, "nibabel.deprecated.FutureWarningMixin.__init__", false]], "__init__() (nibabel.deprecated.moduleproxy method)": [[81, "nibabel.deprecated.ModuleProxy.__init__", false]], "__init__() (nibabel.deprecated.visibledeprecationwarning method)": [[81, "nibabel.deprecated.VisibleDeprecationWarning.__init__", false]], "__init__() (nibabel.deprecator.deprecator method)": [[82, "nibabel.deprecator.Deprecator.__init__", false]], "__init__() (nibabel.deprecator.expireddeprecationerror method)": [[82, "nibabel.deprecator.ExpiredDeprecationError.__init__", false]], "__init__() (nibabel.dft.cachingerror method)": [[83, "nibabel.dft.CachingError.__init__", false]], "__init__() (nibabel.dft.dfterror method)": [[83, "nibabel.dft.DFTError.__init__", false]], "__init__() (nibabel.dft.instancestackerror method)": [[83, "nibabel.dft.InstanceStackError.__init__", false]], "__init__() (nibabel.dft.volumeerror method)": [[83, "nibabel.dft.VolumeError.__init__", false]], "__init__() (nibabel.ecat.ecatheader method)": [[84, "nibabel.ecat.EcatHeader.__init__", false]], "__init__() (nibabel.ecat.ecatimage method)": [[84, "nibabel.ecat.EcatImage.__init__", false]], "__init__() (nibabel.ecat.ecatimagearrayproxy method)": [[84, "nibabel.ecat.EcatImageArrayProxy.__init__", false]], "__init__() (nibabel.ecat.ecatsubheader method)": [[84, "nibabel.ecat.EcatSubHeader.__init__", false]], "__init__() (nibabel.filebasedimages.filebasedheader method)": [[87, "nibabel.filebasedimages.FileBasedHeader.__init__", false]], "__init__() (nibabel.filebasedimages.filebasedimage method)": [[87, "nibabel.filebasedimages.FileBasedImage.__init__", false]], "__init__() (nibabel.filebasedimages.imagefileerror method)": [[87, "nibabel.filebasedimages.ImageFileError.__init__", false]], "__init__() (nibabel.filebasedimages.serializableimage method)": [[87, "nibabel.filebasedimages.SerializableImage.__init__", false]], "__init__() (nibabel.fileholders.fileholder method)": [[88, "nibabel.fileholders.FileHolder.__init__", false]], "__init__() (nibabel.fileholders.fileholdererror method)": [[88, "nibabel.fileholders.FileHolderError.__init__", false]], "__init__() (nibabel.filename_parser.typesfilenameserror method)": [[89, "nibabel.filename_parser.TypesFilenamesError.__init__", false]], "__init__() (nibabel.freesurfer.mghformat.mgherror method)": [[92, "nibabel.freesurfer.mghformat.MGHError.__init__", false]], "__init__() (nibabel.freesurfer.mghformat.mghheader method)": [[92, "nibabel.freesurfer.mghformat.MGHHeader.__init__", false]], "__init__() (nibabel.freesurfer.mghformat.mghimage method)": [[92, "nibabel.freesurfer.mghformat.MGHImage.__init__", false]], "__init__() (nibabel.gifti.gifti.gifticoordsystem method)": [[94, "nibabel.gifti.gifti.GiftiCoordSystem.__init__", false]], "__init__() (nibabel.gifti.gifti.giftidataarray method)": [[94, "nibabel.gifti.gifti.GiftiDataArray.__init__", false]], "__init__() (nibabel.gifti.gifti.giftiimage method)": [[94, "nibabel.gifti.gifti.GiftiImage.__init__", false]], "__init__() (nibabel.gifti.gifti.giftilabel method)": [[94, "nibabel.gifti.gifti.GiftiLabel.__init__", false]], "__init__() (nibabel.gifti.gifti.giftilabeltable method)": [[94, "nibabel.gifti.gifti.GiftiLabelTable.__init__", false]], "__init__() (nibabel.gifti.gifti.giftimetadata method)": [[94, "nibabel.gifti.gifti.GiftiMetaData.__init__", false]], "__init__() (nibabel.gifti.gifti.giftinvpairs method)": [[94, "nibabel.gifti.gifti.GiftiNVPairs.__init__", false]], "__init__() (nibabel.gifti.parse_gifti_fast.giftiimageparser method)": [[94, "nibabel.gifti.parse_gifti_fast.GiftiImageParser.__init__", false]], "__init__() (nibabel.gifti.parse_gifti_fast.giftiparseerror method)": [[94, "nibabel.gifti.parse_gifti_fast.GiftiParseError.__init__", false]], "__init__() (nibabel.imageglobals.errorlevel method)": [[96, "nibabel.imageglobals.ErrorLevel.__init__", false]], "__init__() (nibabel.imageglobals.loggingoutputsuppressor method)": [[96, "nibabel.imageglobals.LoggingOutputSuppressor.__init__", false]], "__init__() (nibabel.minc1.minc1file method)": [[99, "nibabel.minc1.Minc1File.__init__", false]], "__init__() (nibabel.minc1.minc1header method)": [[99, "nibabel.minc1.Minc1Header.__init__", false]], "__init__() (nibabel.minc1.minc1image method)": [[99, "nibabel.minc1.Minc1Image.__init__", false]], "__init__() (nibabel.minc1.mincerror method)": [[99, "nibabel.minc1.MincError.__init__", false]], "__init__() (nibabel.minc1.mincheader method)": [[99, "nibabel.minc1.MincHeader.__init__", false]], "__init__() (nibabel.minc1.mincimagearrayproxy method)": [[99, "nibabel.minc1.MincImageArrayProxy.__init__", false]], "__init__() (nibabel.minc2.hdf5bunch method)": [[100, "nibabel.minc2.Hdf5Bunch.__init__", false]], "__init__() (nibabel.minc2.minc2file method)": [[100, "nibabel.minc2.Minc2File.__init__", false]], "__init__() (nibabel.minc2.minc2header method)": [[100, "nibabel.minc2.Minc2Header.__init__", false]], "__init__() (nibabel.minc2.minc2image method)": [[100, "nibabel.minc2.Minc2Image.__init__", false]], "__init__() (nibabel.mriutils.mrierror method)": [[101, "nibabel.mriutils.MRIError.__init__", false]], "__init__() (nibabel.nicom.ascconv.ascconvparseerror method)": [[102, "nibabel.nicom.ascconv.AscconvParseError.__init__", false]], "__init__() (nibabel.nicom.ascconv.atom method)": [[102, "nibabel.nicom.ascconv.Atom.__init__", false]], "__init__() (nibabel.nicom.ascconv.novalue method)": [[102, "nibabel.nicom.ascconv.NoValue.__init__", false]], "__init__() (nibabel.nicom.csareader.csaerror method)": [[102, "nibabel.nicom.csareader.CSAError.__init__", false]], "__init__() (nibabel.nicom.csareader.csareaderror method)": [[102, "nibabel.nicom.csareader.CSAReadError.__init__", false]], "__init__() (nibabel.nicom.dicomreaders.dicomreaderror method)": [[102, "nibabel.nicom.dicomreaders.DicomReadError.__init__", false]], "__init__() (nibabel.nicom.dicomwrappers.filterdwiiso method)": [[102, "nibabel.nicom.dicomwrappers.FilterDwiIso.__init__", false]], "__init__() (nibabel.nicom.dicomwrappers.filtermultistack method)": [[102, "nibabel.nicom.dicomwrappers.FilterMultiStack.__init__", false]], "__init__() (nibabel.nicom.dicomwrappers.framefilter method)": [[102, "nibabel.nicom.dicomwrappers.FrameFilter.__init__", false]], "__init__() (nibabel.nicom.dicomwrappers.mosaicwrapper method)": [[102, "nibabel.nicom.dicomwrappers.MosaicWrapper.__init__", false]], "__init__() (nibabel.nicom.dicomwrappers.multiframewrapper method)": [[102, "nibabel.nicom.dicomwrappers.MultiframeWrapper.__init__", false]], "__init__() (nibabel.nicom.dicomwrappers.siemenswrapper method)": [[102, "nibabel.nicom.dicomwrappers.SiemensWrapper.__init__", false]], "__init__() (nibabel.nicom.dicomwrappers.wrapper method)": [[102, "nibabel.nicom.dicomwrappers.Wrapper.__init__", false]], "__init__() (nibabel.nicom.dicomwrappers.wrappererror method)": [[102, "nibabel.nicom.dicomwrappers.WrapperError.__init__", false]], "__init__() (nibabel.nicom.dicomwrappers.wrapperprecisionerror method)": [[102, "nibabel.nicom.dicomwrappers.WrapperPrecisionError.__init__", false]], "__init__() (nibabel.nicom.structreader.unpacker method)": [[102, "nibabel.nicom.structreader.Unpacker.__init__", false]], "__init__() (nibabel.nicom.utils.vendor method)": [[102, "nibabel.nicom.utils.Vendor.__init__", false]], "__init__() (nibabel.nifti1.nifti1dicomextension method)": [[103, "nibabel.nifti1.Nifti1DicomExtension.__init__", false]], "__init__() (nibabel.nifti1.nifti1extension method)": [[103, "nibabel.nifti1.Nifti1Extension.__init__", false]], "__init__() (nibabel.nifti1.nifti1extensions method)": [[103, "nibabel.nifti1.Nifti1Extensions.__init__", false]], "__init__() (nibabel.nifti1.nifti1header method)": [[103, "nibabel.nifti1.Nifti1Header.__init__", false]], "__init__() (nibabel.nifti1.nifti1image method)": [[103, "nibabel.nifti1.Nifti1Image.__init__", false]], "__init__() (nibabel.nifti1.nifti1pair method)": [[103, "nibabel.nifti1.Nifti1Pair.__init__", false]], "__init__() (nibabel.nifti1.nifti1pairheader method)": [[103, "nibabel.nifti1.Nifti1PairHeader.__init__", false]], "__init__() (nibabel.nifti1.niftiextension method)": [[103, "nibabel.nifti1.NiftiExtension.__init__", false]], "__init__() (nibabel.nifti2.nifti2header method)": [[104, "nibabel.nifti2.Nifti2Header.__init__", false]], "__init__() (nibabel.nifti2.nifti2image method)": [[104, "nibabel.nifti2.Nifti2Image.__init__", false]], "__init__() (nibabel.nifti2.nifti2pair method)": [[104, "nibabel.nifti2.Nifti2Pair.__init__", false]], "__init__() (nibabel.nifti2.nifti2pairheader method)": [[104, "nibabel.nifti2.Nifti2PairHeader.__init__", false]], "__init__() (nibabel.onetime.resetmixin method)": [[105, "nibabel.onetime.ResetMixin.__init__", false]], "__init__() (nibabel.openers.deterministicgzipfile method)": [[106, "nibabel.openers.DeterministicGzipFile.__init__", false]], "__init__() (nibabel.openers.fileish method)": [[106, "nibabel.openers.Fileish.__init__", false]], "__init__() (nibabel.openers.imageopener method)": [[106, "nibabel.openers.ImageOpener.__init__", false]], "__init__() (nibabel.openers.opener method)": [[106, "nibabel.openers.Opener.__init__", false]], "__init__() (nibabel.orientations.orientationerror method)": [[108, "nibabel.orientations.OrientationError.__init__", false]], "__init__() (nibabel.parrec.parrecarrayproxy method)": [[109, "nibabel.parrec.PARRECArrayProxy.__init__", false]], "__init__() (nibabel.parrec.parrecerror method)": [[109, "nibabel.parrec.PARRECError.__init__", false]], "__init__() (nibabel.parrec.parrecheader method)": [[109, "nibabel.parrec.PARRECHeader.__init__", false]], "__init__() (nibabel.parrec.parrecimage method)": [[109, "nibabel.parrec.PARRECImage.__init__", false]], "__init__() (nibabel.pointset.coordinatearray method)": [[110, "nibabel.pointset.CoordinateArray.__init__", false]], "__init__() (nibabel.pointset.grid method)": [[110, "nibabel.pointset.Grid.__init__", false]], "__init__() (nibabel.pointset.gridindices method)": [[110, "nibabel.pointset.GridIndices.__init__", false]], "__init__() (nibabel.pointset.pointset method)": [[110, "nibabel.pointset.Pointset.__init__", false]], "__init__() (nibabel.spatialimages.hasdtype method)": [[116, "nibabel.spatialimages.HasDtype.__init__", false]], "__init__() (nibabel.spatialimages.headerdataerror method)": [[116, "nibabel.spatialimages.HeaderDataError.__init__", false]], "__init__() (nibabel.spatialimages.headertypeerror method)": [[116, "nibabel.spatialimages.HeaderTypeError.__init__", false]], "__init__() (nibabel.spatialimages.imagedataerror method)": [[116, "nibabel.spatialimages.ImageDataError.__init__", false]], "__init__() (nibabel.spatialimages.spatialfirstslicer method)": [[116, "nibabel.spatialimages.SpatialFirstSlicer.__init__", false]], "__init__() (nibabel.spatialimages.spatialheader method)": [[116, "nibabel.spatialimages.SpatialHeader.__init__", false]], "__init__() (nibabel.spatialimages.spatialimage method)": [[116, "nibabel.spatialimages.SpatialImage.__init__", false]], "__init__() (nibabel.spatialimages.spatialprotocol method)": [[116, "nibabel.spatialimages.SpatialProtocol.__init__", false]], "__init__() (nibabel.spm2analyze.spm2analyzeheader method)": [[117, "nibabel.spm2analyze.Spm2AnalyzeHeader.__init__", false]], "__init__() (nibabel.spm2analyze.spm2analyzeimage method)": [[117, "nibabel.spm2analyze.Spm2AnalyzeImage.__init__", false]], "__init__() (nibabel.spm99analyze.spm99analyzeheader method)": [[118, "nibabel.spm99analyze.Spm99AnalyzeHeader.__init__", false]], "__init__() (nibabel.spm99analyze.spm99analyzeimage method)": [[118, "nibabel.spm99analyze.Spm99AnalyzeImage.__init__", false]], "__init__() (nibabel.spm99analyze.spmanalyzeheader method)": [[118, "nibabel.spm99analyze.SpmAnalyzeHeader.__init__", false]], "__init__() (nibabel.streamlines.array_sequence.arraysequence method)": [[119, "nibabel.streamlines.array_sequence.ArraySequence.__init__", false]], "__init__() (nibabel.streamlines.header.field method)": [[119, "nibabel.streamlines.header.Field.__init__", false]], "__init__() (nibabel.streamlines.tck.tckfile method)": [[119, "nibabel.streamlines.tck.TckFile.__init__", false]], "__init__() (nibabel.streamlines.tractogram.lazydict method)": [[119, "nibabel.streamlines.tractogram.LazyDict.__init__", false]], "__init__() (nibabel.streamlines.tractogram.lazytractogram method)": [[119, "nibabel.streamlines.tractogram.LazyTractogram.__init__", false]], "__init__() (nibabel.streamlines.tractogram.perarraydict method)": [[119, "nibabel.streamlines.tractogram.PerArrayDict.__init__", false]], "__init__() (nibabel.streamlines.tractogram.perarraysequencedict method)": [[119, "nibabel.streamlines.tractogram.PerArraySequenceDict.__init__", false]], "__init__() (nibabel.streamlines.tractogram.sliceabledatadict method)": [[119, "nibabel.streamlines.tractogram.SliceableDataDict.__init__", false]], "__init__() (nibabel.streamlines.tractogram.tractogram method)": [[119, "nibabel.streamlines.tractogram.Tractogram.__init__", false]], "__init__() (nibabel.streamlines.tractogram.tractogramitem method)": [[119, "nibabel.streamlines.tractogram.TractogramItem.__init__", false]], "__init__() (nibabel.streamlines.tractogram_file.abstractclassmethod method)": [[119, "nibabel.streamlines.tractogram_file.abstractclassmethod.__init__", false]], "__init__() (nibabel.streamlines.tractogram_file.dataerror method)": [[119, "nibabel.streamlines.tractogram_file.DataError.__init__", false]], "__init__() (nibabel.streamlines.tractogram_file.datawarning method)": [[119, "nibabel.streamlines.tractogram_file.DataWarning.__init__", false]], "__init__() (nibabel.streamlines.tractogram_file.extensionwarning method)": [[119, "nibabel.streamlines.tractogram_file.ExtensionWarning.__init__", false]], "__init__() (nibabel.streamlines.tractogram_file.headererror method)": [[119, "nibabel.streamlines.tractogram_file.HeaderError.__init__", false]], "__init__() (nibabel.streamlines.tractogram_file.headerwarning method)": [[119, "nibabel.streamlines.tractogram_file.HeaderWarning.__init__", false]], "__init__() (nibabel.streamlines.tractogram_file.tractogramfile method)": [[119, "nibabel.streamlines.tractogram_file.TractogramFile.__init__", false]], "__init__() (nibabel.streamlines.trk.trkfile method)": [[119, "nibabel.streamlines.trk.TrkFile.__init__", false]], "__init__() (nibabel.tmpdirs.temporarydirectory method)": [[120, "nibabel.tmpdirs.TemporaryDirectory.__init__", false]], "__init__() (nibabel.tripwire.tripwire method)": [[121, "nibabel.tripwire.TripWire.__init__", false]], "__init__() (nibabel.tripwire.tripwireerror method)": [[121, "nibabel.tripwire.TripWireError.__init__", false]], "__init__() (nibabel.viewers.orthoslicer3d method)": [[122, "nibabel.viewers.OrthoSlicer3D.__init__", false]], "__init__() (nibabel.volumeutils.dtypemapper method)": [[123, "nibabel.volumeutils.DtypeMapper.__init__", false]], "__init__() (nibabel.volumeutils.recoder method)": [[123, "nibabel.volumeutils.Recoder.__init__", false]], "__init__() (nibabel.wrapstruct.labeledwrapstruct method)": [[124, "nibabel.wrapstruct.LabeledWrapStruct.__init__", false]], "__init__() (nibabel.wrapstruct.wrapstruct method)": [[124, "nibabel.wrapstruct.WrapStruct.__init__", false]], "__init__() (nibabel.wrapstruct.wrapstructerror method)": [[124, "nibabel.wrapstruct.WrapStructError.__init__", false]], "__init__() (nibabel.xmlutils.xmlbasedheader method)": [[125, "nibabel.xmlutils.XmlBasedHeader.__init__", false]], "__init__() (nibabel.xmlutils.xmlparser method)": [[125, "nibabel.xmlutils.XmlParser.__init__", false]], "__init__() (nibabel.xmlutils.xmlserializable method)": [[125, "nibabel.xmlutils.XmlSerializable.__init__", false]], "able_int_type() (in module nibabel.casting)": [[76, "nibabel.casting.able_int_type", false]], "abstractclassmethod (class in nibabel.streamlines.tractogram_file)": [[119, "nibabel.streamlines.tractogram_file.abstractclassmethod", false]], "adapt_affine() (in module nibabel.processing)": [[111, "nibabel.processing.adapt_affine", false]], "add_codes() (nibabel.volumeutils.recoder method)": [[123, "nibabel.volumeutils.Recoder.add_codes", false]], "add_gifti_data_array() (nibabel.gifti.gifti.giftiimage method)": [[94, "nibabel.gifti.gifti.GiftiImage.add_gifti_data_array", false]], "aff2axcodes() (in module nibabel.orientations)": [[108, "nibabel.orientations.aff2axcodes", false]], "affine (nibabel.cifti2.cifti2_axes.brainmodelaxis property)": [[77, "nibabel.cifti2.cifti2_axes.BrainModelAxis.affine", false]], "affine (nibabel.cifti2.cifti2_axes.parcelsaxis property)": [[77, "nibabel.cifti2.cifti2_axes.ParcelsAxis.affine", false]], "affine (nibabel.ecat.ecatimage property)": [[84, "nibabel.ecat.EcatImage.affine", false]], "affine (nibabel.nicom.dicomwrappers.wrapper property)": [[102, "nibabel.nicom.dicomwrappers.Wrapper.affine", false]], "affine (nibabel.pointset.pointset attribute)": [[110, "nibabel.pointset.Pointset.affine", false]], "affine (nibabel.spatialimages.spatialimage property)": [[116, "nibabel.spatialimages.SpatialImage.affine", false]], "affine (nibabel.streamlines.tractogram_file.tractogramfile property)": [[119, "nibabel.streamlines.tractogram_file.TractogramFile.affine", false]], "affine_to_rasmm (nibabel.streamlines.tractogram.tractogram property)": [[119, "nibabel.streamlines.tractogram.Tractogram.affine_to_rasmm", false]], "affineerror (class in nibabel.affines)": [[68, "nibabel.affines.AffineError", false]], "afniarrayproxy (class in nibabel.brikhead)": [[74, "nibabel.brikhead.AFNIArrayProxy", false]], "afniheader (class in nibabel.brikhead)": [[74, "nibabel.brikhead.AFNIHeader", false]], "afniheadererror (class in nibabel.brikhead)": [[74, "nibabel.brikhead.AFNIHeaderError", false]], "afniimage (class in nibabel.brikhead)": [[74, "nibabel.brikhead.AFNIImage", false]], "afniimageerror (class in nibabel.brikhead)": [[74, "nibabel.brikhead.AFNIImageError", false]], "agg_data() (nibabel.gifti.gifti.giftiimage method)": [[94, "nibabel.gifti.gifti.GiftiImage.agg_data", false]], "alert_future_error() (in module nibabel.deprecated)": [[81, "nibabel.deprecated.alert_future_error", false]], "analyzeheader (class in nibabel.analyze)": [[69, "nibabel.analyze.AnalyzeHeader", false]], "analyzeimage (class in nibabel.analyze)": [[69, "nibabel.analyze.AnalyzeImage", false]], "angle_axis2euler() (in module nibabel.eulerangles)": [[86, "nibabel.eulerangles.angle_axis2euler", false]], "angle_axis2mat() (in module nibabel.quaternions)": [[113, "nibabel.quaternions.angle_axis2mat", false]], "angle_axis2quat() (in module nibabel.quaternions)": [[113, "nibabel.quaternions.angle_axis2quat", false]], "ap() (in module nibabel.cmdline.utils)": [[78, "nibabel.cmdline.utils.ap", false]], "append() (nibabel.cifti2.cifti2.cifti2labeltable method)": [[77, "nibabel.cifti2.cifti2.Cifti2LabelTable.append", false]], "append() (nibabel.streamlines.array_sequence.arraysequence method)": [[119, "nibabel.streamlines.array_sequence.ArraySequence.append", false]], "append_cifti_vertices() (nibabel.cifti2.cifti2.cifti2parcel method)": [[77, "nibabel.cifti2.cifti2.Cifti2Parcel.append_cifti_vertices", false]], "append_diag() (in module nibabel.affines)": [[68, "nibabel.affines.append_diag", false]], "applies() (nibabel.nicom.dicomwrappers.filterdwiiso method)": [[102, "nibabel.nicom.dicomwrappers.FilterDwiIso.applies", false]], "applies() (nibabel.nicom.dicomwrappers.filtermultistack method)": [[102, "nibabel.nicom.dicomwrappers.FilterMultiStack.applies", false]], "applies() (nibabel.nicom.dicomwrappers.framefilter method)": [[102, "nibabel.nicom.dicomwrappers.FrameFilter.applies", false]], "apply_affine() (in module nibabel.affines)": [[68, "nibabel.affines.apply_affine", false]], "apply_affine() (nibabel.streamlines.tractogram.lazytractogram method)": [[119, "nibabel.streamlines.tractogram.LazyTractogram.apply_affine", false]], "apply_affine() (nibabel.streamlines.tractogram.tractogram method)": [[119, "nibabel.streamlines.tractogram.Tractogram.apply_affine", false]], "apply_orientation() (in module nibabel.orientations)": [[108, "nibabel.orientations.apply_orientation", false]], "apply_read_scaling() (in module nibabel.volumeutils)": [[123, "nibabel.volumeutils.apply_read_scaling", false]], "are_values_different() (in module nibabel.cmdline.diff)": [[78, "nibabel.cmdline.diff.are_values_different", false]], "array (nibabel.arraywriters.arraywriter property)": [[71, "nibabel.arraywriters.ArrayWriter.array", false]], "array_from_file() (in module nibabel.volumeutils)": [[123, "nibabel.volumeutils.array_from_file", false]], "array_to_file() (in module nibabel.volumeutils)": [[123, "nibabel.volumeutils.array_to_file", false]], "arraylike (class in nibabel.arrayproxy)": [[70, "nibabel.arrayproxy.ArrayLike", false]], "arrayproxy (class in nibabel.arrayproxy)": [[70, "nibabel.arrayproxy.ArrayProxy", false]], "arraysequence (class in nibabel.streamlines.array_sequence)": [[119, "nibabel.streamlines.array_sequence.ArraySequence", false]], "arraywriter (class in nibabel.arraywriters)": [[71, "nibabel.arraywriters.ArrayWriter", false]], "as_analyze_map() (nibabel.analyze.analyzeheader method)": [[69, "nibabel.analyze.AnalyzeHeader.as_analyze_map", false]], "as_analyze_map() (nibabel.parrec.parrecheader method)": [[109, "nibabel.parrec.PARRECHeader.as_analyze_map", false]], "as_byteswapped() (nibabel.freesurfer.mghformat.mghheader method)": [[92, "nibabel.freesurfer.mghformat.MGHHeader.as_byteswapped", false]], "as_byteswapped() (nibabel.wrapstruct.wrapstruct method)": [[124, "nibabel.wrapstruct.WrapStruct.as_byteswapped", false]], "as_closest_canonical() (in module nibabel.funcs)": [[93, "nibabel.funcs.as_closest_canonical", false]], "as_int() (in module nibabel.casting)": [[76, "nibabel.casting.as_int", false]], "as_reoriented() (nibabel.nifti1.nifti1pair method)": [[103, "nibabel.nifti1.Nifti1Pair.as_reoriented", false]], "as_reoriented() (nibabel.spatialimages.spatialimage method)": [[116, "nibabel.spatialimages.SpatialImage.as_reoriented", false]], "ascconvparseerror (class in nibabel.nicom.ascconv)": [[102, "nibabel.nicom.ascconv.AscconvParseError", false]], "assign2atoms() (in module nibabel.nicom.ascconv)": [[102, "nibabel.nicom.ascconv.assign2atoms", false]], "atom (class in nibabel.nicom.ascconv)": [[102, "nibabel.nicom.ascconv.Atom", false]], "axcodes2ornt() (in module nibabel.orientations)": [[108, "nibabel.orientations.axcodes2ornt", false]], "axis (class in nibabel.cifti2.cifti2_axes)": [[77, "nibabel.cifti2.cifti2_axes.Axis", false]], "b2q() (in module nibabel.nicom.dwiparams)": [[102, "nibabel.nicom.dwiparams.B2q", false]], "b_matrix (nibabel.nicom.dicomwrappers.siemenswrapper property)": [[102, "nibabel.nicom.dicomwrappers.SiemensWrapper.b_matrix", false]], "b_matrix (nibabel.nicom.dicomwrappers.wrapper attribute)": [[102, "nibabel.nicom.dicomwrappers.Wrapper.b_matrix", false]], "b_value (nibabel.nicom.dicomwrappers.wrapper property)": [[102, "nibabel.nicom.dicomwrappers.Wrapper.b_value", false]], "b_vector (nibabel.nicom.dicomwrappers.wrapper property)": [[102, "nibabel.nicom.dicomwrappers.Wrapper.b_vector", false]], "batteryrunner (class in nibabel.batteryrunners)": [[72, "nibabel.batteryrunners.BatteryRunner", false]], "bench() (in module nibabel)": [[66, "nibabel.bench", false]], "bench_array_to_file() (in module nibabel.benchmarks.bench_array_to_file)": [[73, "nibabel.benchmarks.bench_array_to_file.bench_array_to_file", false]], "bench_arrayproxy_slicing() (in module nibabel.benchmarks.bench_arrayproxy_slicing)": [[73, "nibabel.benchmarks.bench_arrayproxy_slicing.bench_arrayproxy_slicing", false]], "bench_fileslice() (in module nibabel.benchmarks.bench_fileslice)": [[73, "nibabel.benchmarks.bench_fileslice.bench_fileslice", false]], "bench_finite_range() (in module nibabel.benchmarks.bench_finite_range)": [[73, "nibabel.benchmarks.bench_finite_range.bench_finite_range", false]], "bench_load_save() (in module nibabel.benchmarks.bench_load_save)": [[73, "nibabel.benchmarks.bench_load_save.bench_load_save", false]], "best_float() (in module nibabel.casting)": [[76, "nibabel.casting.best_float", false]], "best_write_scale_ftype() (in module nibabel.volumeutils)": [[123, "nibabel.volumeutils.best_write_scale_ftype", false]], "better_float_of() (in module nibabel.volumeutils)": [[123, "nibabel.volumeutils.better_float_of", false]], "binaryblock (nibabel.wrapstruct.wrapstruct property)": [[124, "nibabel.wrapstruct.WrapStruct.binaryblock", false]], "bomber (class in nibabel.data)": [[79, "nibabel.data.Bomber", false]], "bombererror (class in nibabel.data)": [[79, "nibabel.data.BomberError", false]], "brain_models (nibabel.cifti2.cifti2.cifti2matrixindicesmap property)": [[77, "nibabel.cifti2.cifti2.Cifti2MatrixIndicesMap.brain_models", false]], "brainmodelaxis (class in nibabel.cifti2.cifti2_axes)": [[77, "nibabel.cifti2.cifti2_axes.BrainModelAxis", false]], "bz2_def (nibabel.openers.opener attribute)": [[106, "nibabel.openers.Opener.bz2_def", false]], "cachingerror (class in nibabel.dft)": [[83, "nibabel.dft.CachingError", false]], "calc_scale() (nibabel.arraywriters.slopearraywriter method)": [[71, "nibabel.arraywriters.SlopeArrayWriter.calc_scale", false]], "calc_slicedefs() (in module nibabel.fileslice)": [[90, "nibabel.fileslice.calc_slicedefs", false]], "calculate_dwell_time() (in module nibabel.mriutils)": [[101, "nibabel.mriutils.calculate_dwell_time", false]], "canonical_slicers() (in module nibabel.fileslice)": [[90, "nibabel.fileslice.canonical_slicers", false]], "caretmetadata (class in nibabel.caret)": [[75, "nibabel.caret.CaretMetaData", false]], "castingerror (class in nibabel.casting)": [[76, "nibabel.casting.CastingError", false]], "ceil_exact() (in module nibabel.casting)": [[76, "nibabel.casting.ceil_exact", false]], "characterdatahandler() (nibabel.cifti2.parse_cifti2.cifti2parser method)": [[77, "nibabel.cifti2.parse_cifti2.Cifti2Parser.CharacterDataHandler", false]], "characterdatahandler() (nibabel.gifti.parse_gifti_fast.giftiimageparser method)": [[94, "nibabel.gifti.parse_gifti_fast.GiftiImageParser.CharacterDataHandler", false]], "characterdatahandler() (nibabel.xmlutils.xmlparser method)": [[125, "nibabel.xmlutils.XmlParser.CharacterDataHandler", false]], "check_fix() (nibabel.batteryrunners.batteryrunner method)": [[72, "nibabel.batteryrunners.BatteryRunner.check_fix", false]], "check_fix() (nibabel.wrapstruct.wrapstruct method)": [[124, "nibabel.wrapstruct.WrapStruct.check_fix", false]], "check_only() (nibabel.batteryrunners.batteryrunner method)": [[72, "nibabel.batteryrunners.BatteryRunner.check_only", false]], "check_slicing() (nibabel.spatialimages.spatialfirstslicer method)": [[116, "nibabel.spatialimages.SpatialFirstSlicer.check_slicing", false]], "chk_version() (nibabel.freesurfer.mghformat.mghheader static method)": [[92, "nibabel.freesurfer.mghformat.MGHHeader.chk_version", false]], "cifti2brainmodel (class in nibabel.cifti2.cifti2)": [[77, "nibabel.cifti2.cifti2.Cifti2BrainModel", false]], "cifti2extension (class in nibabel.cifti2.parse_cifti2)": [[77, "nibabel.cifti2.parse_cifti2.Cifti2Extension", false]], "cifti2header (class in nibabel.cifti2.cifti2)": [[77, "nibabel.cifti2.cifti2.Cifti2Header", false]], "cifti2headererror (class in nibabel.cifti2.cifti2)": [[77, "nibabel.cifti2.cifti2.Cifti2HeaderError", false]], "cifti2image (class in nibabel.cifti2.cifti2)": [[77, "nibabel.cifti2.cifti2.Cifti2Image", false]], "cifti2label (class in nibabel.cifti2.cifti2)": [[77, "nibabel.cifti2.cifti2.Cifti2Label", false]], "cifti2labeltable (class in nibabel.cifti2.cifti2)": [[77, "nibabel.cifti2.cifti2.Cifti2LabelTable", false]], "cifti2matrix (class in nibabel.cifti2.cifti2)": [[77, "nibabel.cifti2.cifti2.Cifti2Matrix", false]], "cifti2matrixindicesmap (class in nibabel.cifti2.cifti2)": [[77, "nibabel.cifti2.cifti2.Cifti2MatrixIndicesMap", false]], "cifti2metadata (class in nibabel.cifti2.cifti2)": [[77, "nibabel.cifti2.cifti2.Cifti2MetaData", false]], "cifti2namedmap (class in nibabel.cifti2.cifti2)": [[77, "nibabel.cifti2.cifti2.Cifti2NamedMap", false]], "cifti2parcel (class in nibabel.cifti2.cifti2)": [[77, "nibabel.cifti2.cifti2.Cifti2Parcel", false]], "cifti2parser (class in nibabel.cifti2.parse_cifti2)": [[77, "nibabel.cifti2.parse_cifti2.Cifti2Parser", false]], "cifti2surface (class in nibabel.cifti2.cifti2)": [[77, "nibabel.cifti2.cifti2.Cifti2Surface", false]], "cifti2transformationmatrixvoxelindicesijktoxyz (class in nibabel.cifti2.cifti2)": [[77, "nibabel.cifti2.cifti2.Cifti2TransformationMatrixVoxelIndicesIJKtoXYZ", false]], "cifti2vertexindices (class in nibabel.cifti2.cifti2)": [[77, "nibabel.cifti2.cifti2.Cifti2VertexIndices", false]], "cifti2vertices (class in nibabel.cifti2.cifti2)": [[77, "nibabel.cifti2.cifti2.Cifti2Vertices", false]], "cifti2volume (class in nibabel.cifti2.cifti2)": [[77, "nibabel.cifti2.cifti2.Cifti2Volume", false]], "cifti2voxelindicesijk (class in nibabel.cifti2.cifti2)": [[77, "nibabel.cifti2.cifti2.Cifti2VoxelIndicesIJK", false]], "clear_cache() (in module nibabel.dft)": [[83, "nibabel.dft.clear_cache", false]], "clim (nibabel.viewers.orthoslicer3d property)": [[122, "nibabel.viewers.OrthoSlicer3D.clim", false]], "close() (nibabel.openers.opener method)": [[106, "nibabel.openers.Opener.close", false]], "close() (nibabel.viewers.orthoslicer3d method)": [[122, "nibabel.viewers.OrthoSlicer3D.close", false]], "close_if_mine() (nibabel.openers.opener method)": [[106, "nibabel.openers.Opener.close_if_mine", false]], "closed (nibabel.openers.opener property)": [[106, "nibabel.openers.Opener.closed", false]], "cmap (nibabel.viewers.orthoslicer3d property)": [[122, "nibabel.viewers.OrthoSlicer3D.cmap", false]], "code (nibabel.cifti2.parse_cifti2.cifti2extension attribute)": [[77, "nibabel.cifti2.parse_cifti2.Cifti2Extension.code", false]], "code (nibabel.nifti1.nifti1dicomextension attribute)": [[103, "nibabel.nifti1.Nifti1DicomExtension.code", false]], "code (nibabel.nifti1.nifti1extension attribute)": [[103, "nibabel.nifti1.Nifti1Extension.code", false]], "code (nibabel.nifti1.niftiextension attribute)": [[103, "nibabel.nifti1.NiftiExtension.code", false]], "common_shape (nibabel.streamlines.array_sequence.arraysequence property)": [[119, "nibabel.streamlines.array_sequence.ArraySequence.common_shape", false]], "compress_ext_icase (nibabel.openers.opener attribute)": [[106, "nibabel.openers.Opener.compress_ext_icase", false]], "compress_ext_map (nibabel.openers.imageopener attribute)": [[106, "nibabel.openers.ImageOpener.compress_ext_map", false]], "compress_ext_map (nibabel.openers.opener attribute)": [[106, "nibabel.openers.Opener.compress_ext_map", false]], "concat_images() (in module nibabel.funcs)": [[93, "nibabel.funcs.concat_images", false]], "concatenate() (in module nibabel.streamlines.array_sequence)": [[119, "nibabel.streamlines.array_sequence.concatenate", false]], "conform() (in module nibabel.processing)": [[111, "nibabel.processing.conform", false]], "conjugate() (in module nibabel.quaternions)": [[113, "nibabel.quaternions.conjugate", false]], "content (nibabel.nifti1.niftiextension property)": [[103, "nibabel.nifti1.NiftiExtension.content", false]], "coordinatearray (class in nibabel.pointset)": [[110, "nibabel.pointset.CoordinateArray", false]], "coordinates (nibabel.pointset.pointset attribute)": [[110, "nibabel.pointset.Pointset.coordinates", false]], "copy() (nibabel.arrayproxy.arrayproxy method)": [[70, "nibabel.arrayproxy.ArrayProxy.copy", false]], "copy() (nibabel.brikhead.afniheader method)": [[74, "nibabel.brikhead.AFNIHeader.copy", false]], "copy() (nibabel.filebasedimages.filebasedheader method)": [[87, "nibabel.filebasedimages.FileBasedHeader.copy", false]], "copy() (nibabel.freesurfer.mghformat.mghheader method)": [[92, "nibabel.freesurfer.mghformat.MGHHeader.copy", false]], "copy() (nibabel.nifti1.nifti1header method)": [[103, "nibabel.nifti1.Nifti1Header.copy", false]], "copy() (nibabel.parrec.parrecheader method)": [[109, "nibabel.parrec.PARRECHeader.copy", false]], "copy() (nibabel.spatialimages.spatialheader method)": [[116, "nibabel.spatialimages.SpatialHeader.copy", false]], "copy() (nibabel.streamlines.array_sequence.arraysequence method)": [[119, "nibabel.streamlines.array_sequence.ArraySequence.copy", false]], "copy() (nibabel.streamlines.tractogram.lazytractogram method)": [[119, "nibabel.streamlines.tractogram.LazyTractogram.copy", false]], "copy() (nibabel.streamlines.tractogram.tractogram method)": [[119, "nibabel.streamlines.tractogram.Tractogram.copy", false]], "copy() (nibabel.wrapstruct.wrapstruct method)": [[124, "nibabel.wrapstruct.WrapStruct.copy", false]], "copy_file_map() (in module nibabel.fileholders)": [[88, "nibabel.fileholders.copy_file_map", false]], "count() (nibabel.nifti1.nifti1extensions method)": [[103, "nibabel.nifti1.Nifti1Extensions.count", false]], "count_nonzero_voxels() (in module nibabel.imagestats)": [[97, "nibabel.imagestats.count_nonzero_voxels", false]], "create_arraysequences_from_generator() (in module nibabel.streamlines.array_sequence)": [[119, "nibabel.streamlines.array_sequence.create_arraysequences_from_generator", false]], "create_empty_header() (nibabel.streamlines.tck.tckfile class method)": [[119, "nibabel.streamlines.tck.TckFile.create_empty_header", false]], "create_empty_header() (nibabel.streamlines.tractogram_file.tractogramfile class method)": [[119, "nibabel.streamlines.tractogram_file.TractogramFile.create_empty_header", false]], "create_empty_header() (nibabel.streamlines.trk.trkfile class method)": [[119, "nibabel.streamlines.trk.TrkFile.create_empty_header", false]], "csaerror (class in nibabel.nicom.csareader)": [[102, "nibabel.nicom.csareader.CSAError", false]], "csareaderror (class in nibabel.nicom.csareader)": [[102, "nibabel.nicom.csareader.CSAReadError", false]], "data (nibabel.cifti2.cifti2.cifti2metadata property)": [[77, "nibabel.cifti2.cifti2.Cifti2MetaData.data", false]], "data (nibabel.gifti.gifti.giftimetadata property)": [[94, "nibabel.gifti.gifti.GiftiMetaData.data", false]], "data (nibabel.streamlines.tractogram.lazytractogram property)": [[119, "nibabel.streamlines.tractogram.LazyTractogram.data", false]], "data_from_fileobj() (nibabel.analyze.analyzeheader method)": [[69, "nibabel.analyze.AnalyzeHeader.data_from_fileobj", false]], "data_from_fileobj() (nibabel.ecat.ecatsubheader method)": [[84, "nibabel.ecat.EcatSubHeader.data_from_fileobj", false]], "data_from_fileobj() (nibabel.freesurfer.mghformat.mghheader method)": [[92, "nibabel.freesurfer.mghformat.MGHHeader.data_from_fileobj", false]], "data_from_fileobj() (nibabel.minc1.mincheader method)": [[99, "nibabel.minc1.MincHeader.data_from_fileobj", false]], "data_from_fileobj() (nibabel.spatialimages.spatialheader method)": [[116, "nibabel.spatialimages.SpatialHeader.data_from_fileobj", false]], "data_layout (nibabel.minc1.mincheader attribute)": [[99, "nibabel.minc1.MincHeader.data_layout", false]], "data_layout (nibabel.spatialimages.spatialheader attribute)": [[116, "nibabel.spatialimages.SpatialHeader.data_layout", false]], "data_per_point (nibabel.streamlines.tractogram.lazytractogram property)": [[119, "nibabel.streamlines.tractogram.LazyTractogram.data_per_point", false]], "data_per_point (nibabel.streamlines.tractogram.tractogram property)": [[119, "nibabel.streamlines.tractogram.Tractogram.data_per_point", false]], "data_per_streamline (nibabel.streamlines.tractogram.lazytractogram property)": [[119, "nibabel.streamlines.tractogram.LazyTractogram.data_per_streamline", false]], "data_per_streamline (nibabel.streamlines.tractogram.tractogram property)": [[119, "nibabel.streamlines.tractogram.Tractogram.data_per_streamline", false]], "data_to_fileobj() (nibabel.analyze.analyzeheader method)": [[69, "nibabel.analyze.AnalyzeHeader.data_to_fileobj", false]], "data_to_fileobj() (nibabel.minc1.mincheader method)": [[99, "nibabel.minc1.MincHeader.data_to_fileobj", false]], "data_to_fileobj() (nibabel.spatialimages.spatialheader method)": [[116, "nibabel.spatialimages.SpatialHeader.data_to_fileobj", false]], "dataerror (class in nibabel.data)": [[79, "nibabel.data.DataError", false]], "dataerror (class in nibabel.streamlines.tractogram_file)": [[119, "nibabel.streamlines.tractogram_file.DataError", false]], "dataobj (nibabel.dataobj_images.dataobjimage property)": [[80, "nibabel.dataobj_images.DataobjImage.dataobj", false]], "dataobjimage (class in nibabel.dataobj_images)": [[80, "nibabel.dataobj_images.DataobjImage", false]], "datasource (class in nibabel.data)": [[79, "nibabel.data.Datasource", false]], "datasource_or_bomber() (in module nibabel.data)": [[79, "nibabel.data.datasource_or_bomber", false]], "datawarning (class in nibabel.streamlines.tractogram_file)": [[119, "nibabel.streamlines.tractogram_file.DataWarning", false]], "decode_value_from_name() (in module nibabel.streamlines.trk)": [[119, "nibabel.streamlines.trk.decode_value_from_name", false]], "default_compresslevel (nibabel.openers.opener attribute)": [[106, "nibabel.openers.Opener.default_compresslevel", false]], "default_level_or_option (nibabel.openers.opener attribute)": [[106, "nibabel.openers.Opener.default_level_or_option", false]], "default_structarr() (nibabel.analyze.analyzeheader class method)": [[69, "nibabel.analyze.AnalyzeHeader.default_structarr", false]], "default_structarr() (nibabel.ecat.ecatheader class method)": [[84, "nibabel.ecat.EcatHeader.default_structarr", false]], "default_structarr() (nibabel.freesurfer.mghformat.mghheader class method)": [[92, "nibabel.freesurfer.mghformat.MGHHeader.default_structarr", false]], "default_structarr() (nibabel.nifti1.nifti1header class method)": [[103, "nibabel.nifti1.Nifti1Header.default_structarr", false]], "default_structarr() (nibabel.nifti2.nifti2header class method)": [[104, "nibabel.nifti2.Nifti2Header.default_structarr", false]], "default_structarr() (nibabel.spm99analyze.spmanalyzeheader class method)": [[118, "nibabel.spm99analyze.SpmAnalyzeHeader.default_structarr", false]], "default_structarr() (nibabel.wrapstruct.wrapstruct class method)": [[124, "nibabel.wrapstruct.WrapStruct.default_structarr", false]], "default_x_flip (nibabel.analyze.analyzeheader attribute)": [[69, "nibabel.analyze.AnalyzeHeader.default_x_flip", false]], "default_x_flip (nibabel.spatialimages.spatialheader attribute)": [[116, "nibabel.spatialimages.SpatialHeader.default_x_flip", false]], "default_zst_compresslevel (nibabel.openers.opener attribute)": [[106, "nibabel.openers.Opener.default_zst_compresslevel", false]], "deprecator (class in nibabel.deprecator)": [[82, "nibabel.deprecator.Deprecator", false]], "detect_format() (in module nibabel.streamlines)": [[119, "nibabel.streamlines.detect_format", false]], "deterministicgzipfile (class in nibabel.openers)": [[106, "nibabel.openers.DeterministicGzipFile", false]], "dfterror (class in nibabel.dft)": [[83, "nibabel.dft.DFTError", false]], "diagnose_binaryblock() (nibabel.freesurfer.mghformat.mghheader class method)": [[92, "nibabel.freesurfer.mghformat.MGHHeader.diagnose_binaryblock", false]], "diagnose_binaryblock() (nibabel.wrapstruct.wrapstruct class method)": [[124, "nibabel.wrapstruct.WrapStruct.diagnose_binaryblock", false]], "dicom_test() (in module nibabel.pydicom_compat)": [[112, "nibabel.pydicom_compat.dicom_test", false]], "dicomfs (class in nibabel.cmdline.dicomfs)": [[78, "nibabel.cmdline.dicomfs.DICOMFS", false]], "dicomreaderror (class in nibabel.nicom.dicomreaders)": [[102, "nibabel.nicom.dicomreaders.DicomReadError", false]], "diff() (in module nibabel.cmdline.diff)": [[78, "nibabel.cmdline.diff.diff", false]], "difference_update() (nibabel.cifti2.cifti2.cifti2metadata method)": [[77, "nibabel.cifti2.cifti2.Cifti2MetaData.difference_update", false]], "dim (nibabel.pointset.pointset property)": [[110, "nibabel.pointset.Pointset.dim", false]], "dimensions (nibabel.streamlines.header.field attribute)": [[119, "nibabel.streamlines.header.Field.DIMENSIONS", false]], "display_diff() (in module nibabel.cmdline.diff)": [[78, "nibabel.cmdline.diff.display_diff", false]], "dot_reduce() (in module nibabel.affines)": [[68, "nibabel.affines.dot_reduce", false]], "draw() (nibabel.viewers.orthoslicer3d method)": [[122, "nibabel.viewers.OrthoSlicer3D.draw", false]], "dtype (nibabel.arrayproxy.arrayproxy property)": [[70, "nibabel.arrayproxy.ArrayProxy.dtype", false]], "dtype (nibabel.parrec.parrecarrayproxy property)": [[109, "nibabel.parrec.PARRECArrayProxy.dtype", false]], "dtype (nibabel.pointset.gridindices attribute)": [[110, "nibabel.pointset.GridIndices.dtype", false]], "dtypemapper (class in nibabel.volumeutils)": [[123, "nibabel.volumeutils.DtypeMapper", false]], "dummy_fuse (class in nibabel.cmdline.dicomfs)": [[78, "nibabel.cmdline.dicomfs.dummy_fuse", false]], "ecatheader (class in nibabel.ecat)": [[84, "nibabel.ecat.EcatHeader", false]], "ecatimage (class in nibabel.ecat)": [[84, "nibabel.ecat.EcatImage", false]], "ecatimagearrayproxy (class in nibabel.ecat)": [[84, "nibabel.ecat.EcatImageArrayProxy", false]], "ecatsubheader (class in nibabel.ecat)": [[84, "nibabel.ecat.EcatSubHeader", false]], "encode_value_in_name() (in module nibabel.streamlines.trk)": [[119, "nibabel.streamlines.trk.encode_value_in_name", false]], "encoding (nibabel.nifti1.niftiextension attribute)": [[103, "nibabel.nifti1.NiftiExtension.encoding", false]], "endelementhandler() (nibabel.cifti2.parse_cifti2.cifti2parser method)": [[77, "nibabel.cifti2.parse_cifti2.Cifti2Parser.EndElementHandler", false]], "endelementhandler() (nibabel.gifti.parse_gifti_fast.giftiimageparser method)": [[94, "nibabel.gifti.parse_gifti_fast.GiftiImageParser.EndElementHandler", false]], "endelementhandler() (nibabel.xmlutils.xmlparser method)": [[125, "nibabel.xmlutils.XmlParser.EndElementHandler", false]], "endianness (nibabel.streamlines.header.field attribute)": [[119, "nibabel.streamlines.header.Field.ENDIANNESS", false]], "endianness (nibabel.wrapstruct.wrapstruct property)": [[124, "nibabel.wrapstruct.WrapStruct.endianness", false]], "eof_delimiter (nibabel.streamlines.tck.tckfile attribute)": [[119, "nibabel.streamlines.tck.TckFile.EOF_DELIMITER", false]], "error() (in module nibabel.cmdline.parrec2nii)": [[78, "nibabel.cmdline.parrec2nii.error", false]], "errorlevel (class in nibabel.imageglobals)": [[96, "nibabel.imageglobals.ErrorLevel", false]], "euler2angle_axis() (in module nibabel.eulerangles)": [[86, "nibabel.eulerangles.euler2angle_axis", false]], "euler2mat() (in module nibabel.eulerangles)": [[86, "nibabel.eulerangles.euler2mat", false]], "euler2quat() (in module nibabel.eulerangles)": [[86, "nibabel.eulerangles.euler2quat", false]], "expireddeprecationerror (class in nibabel.deprecator)": [[82, "nibabel.deprecator.ExpiredDeprecationError", false]], "extend() (nibabel.streamlines.array_sequence.arraysequence method)": [[119, "nibabel.streamlines.array_sequence.ArraySequence.extend", false]], "extend() (nibabel.streamlines.tractogram.lazytractogram method)": [[119, "nibabel.streamlines.tractogram.LazyTractogram.extend", false]], "extend() (nibabel.streamlines.tractogram.perarraydict method)": [[119, "nibabel.streamlines.tractogram.PerArrayDict.extend", false]], "extend() (nibabel.streamlines.tractogram.tractogram method)": [[119, "nibabel.streamlines.tractogram.Tractogram.extend", false]], "extensionwarning (class in nibabel.streamlines.tractogram_file)": [[119, "nibabel.streamlines.tractogram_file.ExtensionWarning", false]], "exts2pars() (in module nibabel.parrec)": [[109, "nibabel.parrec.exts2pars", false]], "exts_klass (nibabel.nifti1.nifti1header attribute)": [[103, "nibabel.nifti1.Nifti1Header.exts_klass", false]], "eye() (in module nibabel.quaternions)": [[113, "nibabel.quaternions.eye", false]], "fiber_delimiter (nibabel.streamlines.tck.tckfile attribute)": [[119, "nibabel.streamlines.tck.TckFile.FIBER_DELIMITER", false]], "field (class in nibabel.streamlines.header)": [[119, "nibabel.streamlines.header.Field", false]], "fields (nibabel.volumeutils.recoder attribute)": [[123, "nibabel.volumeutils.Recoder.fields", false]], "figs (nibabel.viewers.orthoslicer3d property)": [[122, "nibabel.viewers.OrthoSlicer3D.figs", false]], "file_like (nibabel.fileholders.fileholder property)": [[88, "nibabel.fileholders.FileHolder.file_like", false]], "filebasedheader (class in nibabel.filebasedimages)": [[87, "nibabel.filebasedimages.FileBasedHeader", false]], "filebasedimage (class in nibabel.filebasedimages)": [[87, "nibabel.filebasedimages.FileBasedImage", false]], "filehandle (class in nibabel.cmdline.dicomfs)": [[78, "nibabel.cmdline.dicomfs.FileHandle", false]], "fileholder (class in nibabel.fileholders)": [[88, "nibabel.fileholders.FileHolder", false]], "fileholdererror (class in nibabel.fileholders)": [[88, "nibabel.fileholders.FileHolderError", false]], "fileish (class in nibabel.openers)": [[106, "nibabel.openers.Fileish", false]], "fileno() (nibabel.openers.opener method)": [[106, "nibabel.openers.Opener.fileno", false]], "files_types (nibabel.analyze.analyzeimage attribute)": [[69, "nibabel.analyze.AnalyzeImage.files_types", false]], "files_types (nibabel.brikhead.afniimage attribute)": [[74, "nibabel.brikhead.AFNIImage.files_types", false]], "files_types (nibabel.cifti2.cifti2.cifti2image attribute)": [[77, "nibabel.cifti2.cifti2.Cifti2Image.files_types", false]], "files_types (nibabel.ecat.ecatimage attribute)": [[84, "nibabel.ecat.EcatImage.files_types", false]], "files_types (nibabel.filebasedimages.filebasedimage attribute)": [[87, "nibabel.filebasedimages.FileBasedImage.files_types", false]], "files_types (nibabel.freesurfer.mghformat.mghimage attribute)": [[92, "nibabel.freesurfer.mghformat.MGHImage.files_types", false]], "files_types (nibabel.gifti.gifti.giftiimage attribute)": [[94, "nibabel.gifti.gifti.GiftiImage.files_types", false]], "files_types (nibabel.minc1.minc1image attribute)": [[99, "nibabel.minc1.Minc1Image.files_types", false]], "files_types (nibabel.nifti1.nifti1image attribute)": [[103, "nibabel.nifti1.Nifti1Image.files_types", false]], "files_types (nibabel.parrec.parrecimage attribute)": [[109, "nibabel.parrec.PARRECImage.files_types", false]], "files_types (nibabel.spm99analyze.spm99analyzeimage attribute)": [[118, "nibabel.spm99analyze.Spm99AnalyzeImage.files_types", false]], "fileslice() (in module nibabel.fileslice)": [[90, "nibabel.fileslice.fileslice", false]], "filespec_to_file_map() (nibabel.brikhead.afniimage class method)": [[74, "nibabel.brikhead.AFNIImage.filespec_to_file_map", false]], "filespec_to_file_map() (nibabel.filebasedimages.filebasedimage class method)": [[87, "nibabel.filebasedimages.FileBasedImage.filespec_to_file_map", false]], "filespec_to_file_map() (nibabel.freesurfer.mghformat.mghimage class method)": [[92, "nibabel.freesurfer.mghformat.MGHImage.filespec_to_file_map", false]], "fill_slicer() (in module nibabel.fileslice)": [[90, "nibabel.fileslice.fill_slicer", false]], "fillpositive() (in module nibabel.quaternions)": [[113, "nibabel.quaternions.fillpositive", false]], "filterdwiiso (class in nibabel.nicom.dicomwrappers)": [[102, "nibabel.nicom.dicomwrappers.FilterDwiIso", false]], "filtermultistack (class in nibabel.nicom.dicomwrappers)": [[102, "nibabel.nicom.dicomwrappers.FilterMultiStack", false]], "finalize_append() (nibabel.streamlines.array_sequence.arraysequence method)": [[119, "nibabel.streamlines.array_sequence.ArraySequence.finalize_append", false]], "find_data_dir() (in module nibabel.data)": [[79, "nibabel.data.find_data_dir", false]], "find_private_section() (in module nibabel.nicom.utils)": [[102, "nibabel.nicom.utils.find_private_section", false]], "finite_range() (in module nibabel.volumeutils)": [[123, "nibabel.volumeutils.finite_range", false]], "finite_range() (nibabel.arraywriters.arraywriter method)": [[71, "nibabel.arraywriters.ArrayWriter.finite_range", false]], "flip_axis() (in module nibabel.orientations)": [[108, "nibabel.orientations.flip_axis", false]], "float_to_int() (in module nibabel.casting)": [[76, "nibabel.casting.float_to_int", false]], "floatingerror (class in nibabel.casting)": [[76, "nibabel.casting.FloatingError", false]], "floor_exact() (in module nibabel.casting)": [[76, "nibabel.casting.floor_exact", false]], "floor_log2() (in module nibabel.casting)": [[76, "nibabel.casting.floor_log2", false]], "flush_chardata() (nibabel.cifti2.parse_cifti2.cifti2parser method)": [[77, "nibabel.cifti2.parse_cifti2.Cifti2Parser.flush_chardata", false]], "flush_chardata() (nibabel.gifti.parse_gifti_fast.giftiimageparser method)": [[94, "nibabel.gifti.parse_gifti_fast.GiftiImageParser.flush_chardata", false]], "fname_ext_ul_case() (in module nibabel.volumeutils)": [[123, "nibabel.volumeutils.fname_ext_ul_case", false]], "fobj (nibabel.openers.opener attribute)": [[106, "nibabel.openers.Opener.fobj", false]], "four_to_three() (in module nibabel.funcs)": [[93, "nibabel.funcs.four_to_three", false]], "frame_order (nibabel.nicom.dicomwrappers.multiframewrapper property)": [[102, "nibabel.nicom.dicomwrappers.MultiframeWrapper.frame_order", false]], "framefilter (class in nibabel.nicom.dicomwrappers)": [[102, "nibabel.nicom.dicomwrappers.FrameFilter", false]], "from_axes() (nibabel.cifti2.cifti2.cifti2header class method)": [[77, "nibabel.cifti2.cifti2.Cifti2Header.from_axes", false]], "from_brain_models() (nibabel.cifti2.cifti2_axes.parcelsaxis class method)": [[77, "nibabel.cifti2.cifti2_axes.ParcelsAxis.from_brain_models", false]], "from_bytes() (nibabel.filebasedimages.serializableimage class method)": [[87, "nibabel.filebasedimages.SerializableImage.from_bytes", false]], "from_bytes() (nibabel.nifti1.niftiextension class method)": [[103, "nibabel.nifti1.NiftiExtension.from_bytes", false]], "from_data_func() (nibabel.streamlines.tractogram.lazytractogram class method)": [[119, "nibabel.streamlines.tractogram.LazyTractogram.from_data_func", false]], "from_dict() (nibabel.gifti.gifti.giftimetadata class method)": [[94, "nibabel.gifti.gifti.GiftiMetaData.from_dict", false]], "from_file_map() (nibabel.analyze.analyzeimage class method)": [[69, "nibabel.analyze.AnalyzeImage.from_file_map", false]], "from_file_map() (nibabel.brikhead.afniimage class method)": [[74, "nibabel.brikhead.AFNIImage.from_file_map", false]], "from_file_map() (nibabel.cifti2.cifti2.cifti2image class method)": [[77, "nibabel.cifti2.cifti2.Cifti2Image.from_file_map", false]], "from_file_map() (nibabel.dataobj_images.dataobjimage class method)": [[80, "nibabel.dataobj_images.DataobjImage.from_file_map", false]], "from_file_map() (nibabel.ecat.ecatimage class method)": [[84, "nibabel.ecat.EcatImage.from_file_map", false]], "from_file_map() (nibabel.filebasedimages.filebasedimage class method)": [[87, "nibabel.filebasedimages.FileBasedImage.from_file_map", false]], "from_file_map() (nibabel.freesurfer.mghformat.mghimage class method)": [[92, "nibabel.freesurfer.mghformat.MGHImage.from_file_map", false]], "from_file_map() (nibabel.gifti.gifti.giftiimage class method)": [[94, "nibabel.gifti.gifti.GiftiImage.from_file_map", false]], "from_file_map() (nibabel.minc1.minc1image class method)": [[99, "nibabel.minc1.Minc1Image.from_file_map", false]], "from_file_map() (nibabel.minc2.minc2image class method)": [[100, "nibabel.minc2.Minc2Image.from_file_map", false]], "from_file_map() (nibabel.parrec.parrecimage class method)": [[109, "nibabel.parrec.PARRECImage.from_file_map", false]], "from_file_map() (nibabel.spm99analyze.spm99analyzeimage class method)": [[118, "nibabel.spm99analyze.Spm99AnalyzeImage.from_file_map", false]], "from_filename() (nibabel.dataobj_images.dataobjimage class method)": [[80, "nibabel.dataobj_images.DataobjImage.from_filename", false]], "from_filename() (nibabel.filebasedimages.filebasedimage class method)": [[87, "nibabel.filebasedimages.FileBasedImage.from_filename", false]], "from_filename() (nibabel.gifti.gifti.giftiimage class method)": [[94, "nibabel.gifti.gifti.GiftiImage.from_filename", false]], "from_filename() (nibabel.parrec.parrecimage class method)": [[109, "nibabel.parrec.PARRECImage.from_filename", false]], "from_fileobj() (nibabel.brikhead.afniheader class method)": [[74, "nibabel.brikhead.AFNIHeader.from_fileobj", false]], "from_fileobj() (nibabel.filebasedimages.filebasedheader class method)": [[87, "nibabel.filebasedimages.FileBasedHeader.from_fileobj", false]], "from_fileobj() (nibabel.freesurfer.mghformat.mghheader class method)": [[92, "nibabel.freesurfer.mghformat.MGHHeader.from_fileobj", false]], "from_fileobj() (nibabel.nifti1.nifti1extensions class method)": [[103, "nibabel.nifti1.Nifti1Extensions.from_fileobj", false]], "from_fileobj() (nibabel.nifti1.nifti1header class method)": [[103, "nibabel.nifti1.Nifti1Header.from_fileobj", false]], "from_fileobj() (nibabel.parrec.parrecheader class method)": [[109, "nibabel.parrec.PARRECHeader.from_fileobj", false]], "from_fileobj() (nibabel.wrapstruct.wrapstruct class method)": [[124, "nibabel.wrapstruct.WrapStruct.from_fileobj", false]], "from_header() (nibabel.analyze.analyzeheader class method)": [[69, "nibabel.analyze.AnalyzeHeader.from_header", false]], "from_header() (nibabel.brikhead.afniheader class method)": [[74, "nibabel.brikhead.AFNIHeader.from_header", false]], "from_header() (nibabel.filebasedimages.filebasedheader class method)": [[87, "nibabel.filebasedimages.FileBasedHeader.from_header", false]], "from_header() (nibabel.freesurfer.mghformat.mghheader class method)": [[92, "nibabel.freesurfer.mghformat.MGHHeader.from_header", false]], "from_header() (nibabel.nifti1.nifti1header class method)": [[103, "nibabel.nifti1.Nifti1Header.from_header", false]], "from_header() (nibabel.parrec.parrecheader class method)": [[109, "nibabel.parrec.PARRECHeader.from_header", false]], "from_header() (nibabel.spatialimages.spatialheader class method)": [[116, "nibabel.spatialimages.SpatialHeader.from_header", false]], "from_image() (nibabel.cifti2.cifti2.cifti2image class method)": [[77, "nibabel.cifti2.cifti2.Cifti2Image.from_image", false]], "from_image() (nibabel.ecat.ecatimage class method)": [[84, "nibabel.ecat.EcatImage.from_image", false]], "from_image() (nibabel.filebasedimages.filebasedimage class method)": [[87, "nibabel.filebasedimages.FileBasedImage.from_image", false]], "from_image() (nibabel.pointset.grid class method)": [[110, "nibabel.pointset.Grid.from_image", false]], "from_image() (nibabel.spatialimages.spatialimage class method)": [[116, "nibabel.spatialimages.SpatialImage.from_image", false]], "from_index_mapping() (in module nibabel.cifti2.cifti2_axes)": [[77, "nibabel.cifti2.cifti2_axes.from_index_mapping", false]], "from_index_mapping() (nibabel.cifti2.cifti2_axes.brainmodelaxis class method)": [[77, "nibabel.cifti2.cifti2_axes.BrainModelAxis.from_index_mapping", false]], "from_index_mapping() (nibabel.cifti2.cifti2_axes.labelaxis class method)": [[77, "nibabel.cifti2.cifti2_axes.LabelAxis.from_index_mapping", false]], "from_index_mapping() (nibabel.cifti2.cifti2_axes.parcelsaxis class method)": [[77, "nibabel.cifti2.cifti2_axes.ParcelsAxis.from_index_mapping", false]], "from_index_mapping() (nibabel.cifti2.cifti2_axes.scalaraxis class method)": [[77, "nibabel.cifti2.cifti2_axes.ScalarAxis.from_index_mapping", false]], "from_index_mapping() (nibabel.cifti2.cifti2_axes.seriesaxis class method)": [[77, "nibabel.cifti2.cifti2_axes.SeriesAxis.from_index_mapping", false]], "from_mask() (nibabel.cifti2.cifti2_axes.brainmodelaxis class method)": [[77, "nibabel.cifti2.cifti2_axes.BrainModelAxis.from_mask", false]], "from_mask() (nibabel.pointset.grid class method)": [[110, "nibabel.pointset.Grid.from_mask", false]], "from_matvec() (in module nibabel.affines)": [[68, "nibabel.affines.from_matvec", false]], "from_object() (nibabel.nifti1.niftiextension class method)": [[103, "nibabel.nifti1.NiftiExtension.from_object", false]], "from_stream() (nibabel.filebasedimages.serializableimage class method)": [[87, "nibabel.filebasedimages.SerializableImage.from_stream", false]], "from_surface() (nibabel.cifti2.cifti2_axes.brainmodelaxis class method)": [[77, "nibabel.cifti2.cifti2_axes.BrainModelAxis.from_surface", false]], "from_tractogram() (nibabel.streamlines.tractogram.lazytractogram class method)": [[119, "nibabel.streamlines.tractogram.LazyTractogram.from_tractogram", false]], "from_url() (nibabel.filebasedimages.serializableimage class method)": [[87, "nibabel.filebasedimages.SerializableImage.from_url", false]], "fuse (in module nibabel.cmdline.dicomfs)": [[78, "nibabel.cmdline.dicomfs.fuse", false]], "fuse (nibabel.cmdline.dicomfs.dummy_fuse attribute)": [[78, "nibabel.cmdline.dicomfs.dummy_fuse.Fuse", false]], "fuse_python_api (nibabel.cmdline.dicomfs.dummy_fuse attribute)": [[78, "nibabel.cmdline.dicomfs.dummy_fuse.fuse_python_api", false]], "futurewarningmixin (class in nibabel.deprecated)": [[81, "nibabel.deprecated.FutureWarningMixin", false]], "fwhm2sigma() (in module nibabel.processing)": [[111, "nibabel.processing.fwhm2sigma", false]], "ge (nibabel.nicom.utils.vendor attribute)": [[102, "nibabel.nicom.utils.Vendor.GE", false]], "get() (nibabel.nicom.dicomwrappers.wrapper method)": [[102, "nibabel.nicom.dicomwrappers.Wrapper.get", false]], "get() (nibabel.wrapstruct.wrapstruct method)": [[124, "nibabel.wrapstruct.WrapStruct.get", false]], "get_acq_mat_txt() (in module nibabel.nicom.csareader)": [[102, "nibabel.nicom.csareader.get_acq_mat_txt", false]], "get_affine() (nibabel.brikhead.afniheader method)": [[74, "nibabel.brikhead.AFNIHeader.get_affine", false]], "get_affine() (nibabel.freesurfer.mghformat.mghheader method)": [[92, "nibabel.freesurfer.mghformat.MGHHeader.get_affine", false]], "get_affine() (nibabel.minc1.minc1file method)": [[99, "nibabel.minc1.Minc1File.get_affine", false]], "get_affine() (nibabel.parrec.parrecheader method)": [[109, "nibabel.parrec.PARRECHeader.get_affine", false]], "get_affine_from_reference() (in module nibabel.streamlines.utils)": [[119, "nibabel.streamlines.utils.get_affine_from_reference", false]], "get_affine_rasmm_to_trackvis() (in module nibabel.streamlines.trk)": [[119, "nibabel.streamlines.trk.get_affine_rasmm_to_trackvis", false]], "get_affine_trackvis_to_rasmm() (in module nibabel.streamlines.trk)": [[119, "nibabel.streamlines.trk.get_affine_trackvis_to_rasmm", false]], "get_arrays_from_intent() (nibabel.gifti.gifti.giftiimage method)": [[94, "nibabel.gifti.gifti.GiftiImage.get_arrays_from_intent", false]], "get_axis() (nibabel.cifti2.cifti2.cifti2header method)": [[77, "nibabel.cifti2.cifti2.Cifti2Header.get_axis", false]], "get_axis() (nibabel.cifti2.cifti2.cifti2matrix method)": [[77, "nibabel.cifti2.cifti2.Cifti2Matrix.get_axis", false]], "get_b_matrix() (in module nibabel.nicom.csareader)": [[102, "nibabel.nicom.csareader.get_b_matrix", false]], "get_b_value() (in module nibabel.nicom.csareader)": [[102, "nibabel.nicom.csareader.get_b_value", false]], "get_base_affine() (nibabel.analyze.analyzeheader method)": [[69, "nibabel.analyze.AnalyzeHeader.get_base_affine", false]], "get_base_affine() (nibabel.spatialimages.spatialheader method)": [[116, "nibabel.spatialimages.SpatialHeader.get_base_affine", false]], "get_best_affine() (nibabel.analyze.analyzeheader method)": [[69, "nibabel.analyze.AnalyzeHeader.get_best_affine", false]], "get_best_affine() (nibabel.freesurfer.mghformat.mghheader method)": [[92, "nibabel.freesurfer.mghformat.MGHHeader.get_best_affine", false]], "get_best_affine() (nibabel.nifti1.nifti1header method)": [[103, "nibabel.nifti1.Nifti1Header.get_best_affine", false]], "get_best_affine() (nibabel.spatialimages.spatialheader method)": [[116, "nibabel.spatialimages.SpatialHeader.get_best_affine", false]], "get_best_affine() (nibabel.spm99analyze.spm99analyzeheader method)": [[118, "nibabel.spm99analyze.Spm99AnalyzeHeader.get_best_affine", false]], "get_bvals_bvecs() (nibabel.parrec.parrecheader method)": [[109, "nibabel.parrec.PARRECHeader.get_bvals_bvecs", false]], "get_code() (nibabel.nifti1.niftiextension method)": [[103, "nibabel.nifti1.NiftiExtension.get_code", false]], "get_codes() (nibabel.nifti1.nifti1extensions method)": [[103, "nibabel.nifti1.Nifti1Extensions.get_codes", false]], "get_content() (nibabel.nifti1.niftiextension method)": [[103, "nibabel.nifti1.NiftiExtension.get_content", false]], "get_coords() (nibabel.pointset.pointset method)": [[110, "nibabel.pointset.Pointset.get_coords", false]], "get_csa_header() (in module nibabel.nicom.csareader)": [[102, "nibabel.nicom.csareader.get_csa_header", false]], "get_data() (nibabel.dataobj_images.dataobjimage method)": [[80, "nibabel.dataobj_images.DataobjImage.get_data", false]], "get_data() (nibabel.nicom.dicomwrappers.wrapper method)": [[102, "nibabel.nicom.dicomwrappers.Wrapper.get_data", false]], "get_data() (nibabel.streamlines.array_sequence.arraysequence method)": [[119, "nibabel.streamlines.array_sequence.ArraySequence.get_data", false]], "get_data_bytespervox() (nibabel.freesurfer.mghformat.mghheader method)": [[92, "nibabel.freesurfer.mghformat.MGHHeader.get_data_bytespervox", false]], "get_data_diff() (in module nibabel.cmdline.diff)": [[78, "nibabel.cmdline.diff.get_data_diff", false]], "get_data_dtype() (nibabel.analyze.analyzeheader method)": [[69, "nibabel.analyze.AnalyzeHeader.get_data_dtype", false]], "get_data_dtype() (nibabel.analyze.analyzeimage method)": [[69, "nibabel.analyze.AnalyzeImage.get_data_dtype", false]], "get_data_dtype() (nibabel.cifti2.cifti2.cifti2image method)": [[77, "nibabel.cifti2.cifti2.Cifti2Image.get_data_dtype", false]], "get_data_dtype() (nibabel.ecat.ecatheader method)": [[84, "nibabel.ecat.EcatHeader.get_data_dtype", false]], "get_data_dtype() (nibabel.ecat.ecatimage method)": [[84, "nibabel.ecat.EcatImage.get_data_dtype", false]], "get_data_dtype() (nibabel.freesurfer.mghformat.mghheader method)": [[92, "nibabel.freesurfer.mghformat.MGHHeader.get_data_dtype", false]], "get_data_dtype() (nibabel.minc1.minc1file method)": [[99, "nibabel.minc1.Minc1File.get_data_dtype", false]], "get_data_dtype() (nibabel.minc2.minc2file method)": [[100, "nibabel.minc2.Minc2File.get_data_dtype", false]], "get_data_dtype() (nibabel.nifti1.nifti1pair method)": [[103, "nibabel.nifti1.Nifti1Pair.get_data_dtype", false]], "get_data_dtype() (nibabel.spatialimages.hasdtype method)": [[116, "nibabel.spatialimages.HasDtype.get_data_dtype", false]], "get_data_dtype() (nibabel.spatialimages.spatialheader method)": [[116, "nibabel.spatialimages.SpatialHeader.get_data_dtype", false]], "get_data_dtype() (nibabel.spatialimages.spatialimage method)": [[116, "nibabel.spatialimages.SpatialImage.get_data_dtype", false]], "get_data_dtype() (nibabel.spatialimages.spatialprotocol method)": [[116, "nibabel.spatialimages.SpatialProtocol.get_data_dtype", false]], "get_data_hash_diff() (in module nibabel.cmdline.diff)": [[78, "nibabel.cmdline.diff.get_data_hash_diff", false]], "get_data_offset() (nibabel.analyze.analyzeheader method)": [[69, "nibabel.analyze.AnalyzeHeader.get_data_offset", false]], "get_data_offset() (nibabel.brikhead.afniheader method)": [[74, "nibabel.brikhead.AFNIHeader.get_data_offset", false]], "get_data_offset() (nibabel.freesurfer.mghformat.mghheader method)": [[92, "nibabel.freesurfer.mghformat.MGHHeader.get_data_offset", false]], "get_data_offset() (nibabel.parrec.parrecheader method)": [[109, "nibabel.parrec.PARRECHeader.get_data_offset", false]], "get_data_path() (in module nibabel.data)": [[79, "nibabel.data.get_data_path", false]], "get_data_scaling() (nibabel.brikhead.afniheader method)": [[74, "nibabel.brikhead.AFNIHeader.get_data_scaling", false]], "get_data_scaling() (nibabel.parrec.parrecheader method)": [[109, "nibabel.parrec.PARRECHeader.get_data_scaling", false]], "get_data_shape() (nibabel.analyze.analyzeheader method)": [[69, "nibabel.analyze.AnalyzeHeader.get_data_shape", false]], "get_data_shape() (nibabel.cifti2.cifti2.cifti2matrix method)": [[77, "nibabel.cifti2.cifti2.Cifti2Matrix.get_data_shape", false]], "get_data_shape() (nibabel.freesurfer.mghformat.mghheader method)": [[92, "nibabel.freesurfer.mghformat.MGHHeader.get_data_shape", false]], "get_data_shape() (nibabel.minc1.minc1file method)": [[99, "nibabel.minc1.Minc1File.get_data_shape", false]], "get_data_shape() (nibabel.minc2.minc2file method)": [[100, "nibabel.minc2.Minc2File.get_data_shape", false]], "get_data_shape() (nibabel.nifti1.nifti1header method)": [[103, "nibabel.nifti1.Nifti1Header.get_data_shape", false]], "get_data_shape() (nibabel.nifti2.nifti2header method)": [[104, "nibabel.nifti2.Nifti2Header.get_data_shape", false]], "get_data_shape() (nibabel.spatialimages.spatialheader method)": [[116, "nibabel.spatialimages.SpatialHeader.get_data_shape", false]], "get_data_shape() (nibabel.spatialimages.spatialprotocol method)": [[116, "nibabel.spatialimages.SpatialProtocol.get_data_shape", false]], "get_data_size() (nibabel.freesurfer.mghformat.mghheader method)": [[92, "nibabel.freesurfer.mghformat.MGHHeader.get_data_size", false]], "get_def() (nibabel.parrec.parrecheader method)": [[109, "nibabel.parrec.PARRECHeader.get_def", false]], "get_dim_info() (nibabel.nifti1.nifti1header method)": [[103, "nibabel.nifti1.Nifti1Header.get_dim_info", false]], "get_echo_train_length() (nibabel.parrec.parrecheader method)": [[109, "nibabel.parrec.PARRECHeader.get_echo_train_length", false]], "get_element() (nibabel.cifti2.cifti2_axes.brainmodelaxis method)": [[77, "nibabel.cifti2.cifti2_axes.BrainModelAxis.get_element", false]], "get_element() (nibabel.cifti2.cifti2_axes.labelaxis method)": [[77, "nibabel.cifti2.cifti2_axes.LabelAxis.get_element", false]], "get_element() (nibabel.cifti2.cifti2_axes.parcelsaxis method)": [[77, "nibabel.cifti2.cifti2_axes.ParcelsAxis.get_element", false]], "get_element() (nibabel.cifti2.cifti2_axes.scalaraxis method)": [[77, "nibabel.cifti2.cifti2_axes.ScalarAxis.get_element", false]], "get_element() (nibabel.cifti2.cifti2_axes.seriesaxis method)": [[77, "nibabel.cifti2.cifti2_axes.SeriesAxis.get_element", false]], "get_fdata() (nibabel.dataobj_images.dataobjimage method)": [[80, "nibabel.dataobj_images.DataobjImage.get_fdata", false]], "get_filename() (nibabel.data.datasource method)": [[79, "nibabel.data.Datasource.get_filename", false]], "get_filename() (nibabel.filebasedimages.filebasedimage method)": [[87, "nibabel.filebasedimages.FileBasedImage.get_filename", false]], "get_filetype() (nibabel.ecat.ecatheader method)": [[84, "nibabel.ecat.EcatHeader.get_filetype", false]], "get_footer_offset() (nibabel.freesurfer.mghformat.mghheader method)": [[92, "nibabel.freesurfer.mghformat.MGHHeader.get_footer_offset", false]], "get_frame() (nibabel.ecat.ecatimage method)": [[84, "nibabel.ecat.EcatImage.get_frame", false]], "get_frame_affine() (nibabel.ecat.ecatimage method)": [[84, "nibabel.ecat.EcatImage.get_frame_affine", false]], "get_frame_affine() (nibabel.ecat.ecatsubheader method)": [[84, "nibabel.ecat.EcatSubHeader.get_frame_affine", false]], "get_frame_order() (in module nibabel.ecat)": [[84, "nibabel.ecat.get_frame_order", false]], "get_g_vector() (in module nibabel.nicom.csareader)": [[102, "nibabel.nicom.csareader.get_g_vector", false]], "get_headers_diff() (in module nibabel.cmdline.diff)": [[78, "nibabel.cmdline.diff.get_headers_diff", false]], "get_home_dir() (in module nibabel.environment)": [[85, "nibabel.environment.get_home_dir", false]], "get_ice_dims() (in module nibabel.nicom.csareader)": [[102, "nibabel.nicom.csareader.get_ice_dims", false]], "get_index_map() (nibabel.cifti2.cifti2.cifti2header method)": [[77, "nibabel.cifti2.cifti2.Cifti2Header.get_index_map", false]], "get_index_map() (nibabel.cifti2.cifti2.cifti2matrix method)": [[77, "nibabel.cifti2.cifti2.Cifti2Matrix.get_index_map", false]], "get_info() (in module nibabel)": [[66, "nibabel.get_info", false]], "get_intent() (nibabel.nifti1.nifti1header method)": [[103, "nibabel.nifti1.Nifti1Header.get_intent", false]], "get_labels_as_dict() (nibabel.gifti.gifti.giftilabeltable method)": [[94, "nibabel.gifti.gifti.GiftiLabelTable.get_labels_as_dict", false]], "get_mlist() (nibabel.ecat.ecatimage method)": [[84, "nibabel.ecat.EcatImage.get_mlist", false]], "get_n_mosaic() (in module nibabel.nicom.csareader)": [[102, "nibabel.nicom.csareader.get_n_mosaic", false]], "get_n_slices() (nibabel.nifti1.nifti1header method)": [[103, "nibabel.nifti1.Nifti1Header.get_n_slices", false]], "get_nframes() (nibabel.ecat.ecatsubheader method)": [[84, "nibabel.ecat.EcatSubHeader.get_nframes", false]], "get_nipy_system_dir() (in module nibabel.environment)": [[85, "nibabel.environment.get_nipy_system_dir", false]], "get_nipy_user_dir() (in module nibabel.environment)": [[85, "nibabel.environment.get_nipy_user_dir", false]], "get_obj_dtype() (in module nibabel.arrayproxy)": [[70, "nibabel.arrayproxy.get_obj_dtype", false]], "get_object() (nibabel.nifti1.niftiextension method)": [[103, "nibabel.nifti1.NiftiExtension.get_object", false]], "get_opt_parser() (in module nibabel.cmdline.dicomfs)": [[78, "nibabel.cmdline.dicomfs.get_opt_parser", false]], "get_opt_parser() (in module nibabel.cmdline.diff)": [[78, "nibabel.cmdline.diff.get_opt_parser", false]], "get_opt_parser() (in module nibabel.cmdline.ls)": [[78, "nibabel.cmdline.ls.get_opt_parser", false]], "get_opt_parser() (in module nibabel.cmdline.parrec2nii)": [[78, "nibabel.cmdline.parrec2nii.get_opt_parser", false]], "get_origin_affine() (nibabel.spm99analyze.spm99analyzeheader method)": [[118, "nibabel.spm99analyze.Spm99AnalyzeHeader.get_origin_affine", false]], "get_paths() (nibabel.cmdline.dicomfs.dicomfs method)": [[78, "nibabel.cmdline.dicomfs.DICOMFS.get_paths", false]], "get_patient_orient() (nibabel.ecat.ecatheader method)": [[84, "nibabel.ecat.EcatHeader.get_patient_orient", false]], "get_pixel_array() (nibabel.nicom.dicomwrappers.wrapper method)": [[102, "nibabel.nicom.dicomwrappers.Wrapper.get_pixel_array", false]], "get_prepare_fileobj() (nibabel.fileholders.fileholder method)": [[88, "nibabel.fileholders.FileHolder.get_prepare_fileobj", false]], "get_q_vectors() (nibabel.parrec.parrecheader method)": [[109, "nibabel.parrec.PARRECHeader.get_q_vectors", false]], "get_qform() (nibabel.nifti1.nifti1header method)": [[103, "nibabel.nifti1.Nifti1Header.get_qform", false]], "get_qform() (nibabel.nifti1.nifti1pair method)": [[103, "nibabel.nifti1.Nifti1Pair.get_qform", false]], "get_qform_quaternion() (nibabel.nifti1.nifti1header method)": [[103, "nibabel.nifti1.Nifti1Header.get_qform_quaternion", false]], "get_ras2vox() (nibabel.freesurfer.mghformat.mghheader method)": [[92, "nibabel.freesurfer.mghformat.MGHHeader.get_ras2vox", false]], "get_rec_shape() (nibabel.parrec.parrecheader method)": [[109, "nibabel.parrec.PARRECHeader.get_rec_shape", false]], "get_scalar() (in module nibabel.nicom.csareader)": [[102, "nibabel.nicom.csareader.get_scalar", false]], "get_scaled_data() (nibabel.minc1.minc1file method)": [[99, "nibabel.minc1.Minc1File.get_scaled_data", false]], "get_scaled_data() (nibabel.minc2.minc2file method)": [[100, "nibabel.minc2.Minc2File.get_scaled_data", false]], "get_series_framenumbers() (in module nibabel.ecat)": [[84, "nibabel.ecat.get_series_framenumbers", false]], "get_sform() (nibabel.nifti1.nifti1header method)": [[103, "nibabel.nifti1.Nifti1Header.get_sform", false]], "get_sform() (nibabel.nifti1.nifti1pair method)": [[103, "nibabel.nifti1.Nifti1Pair.get_sform", false]], "get_shape() (nibabel.ecat.ecatsubheader method)": [[84, "nibabel.ecat.EcatSubHeader.get_shape", false]], "get_sizeondisk() (nibabel.nifti1.nifti1extensions method)": [[103, "nibabel.nifti1.Nifti1Extensions.get_sizeondisk", false]], "get_sizeondisk() (nibabel.nifti1.niftiextension method)": [[103, "nibabel.nifti1.NiftiExtension.get_sizeondisk", false]], "get_slice_duration() (nibabel.nifti1.nifti1header method)": [[103, "nibabel.nifti1.Nifti1Header.get_slice_duration", false]], "get_slice_normal() (in module nibabel.nicom.csareader)": [[102, "nibabel.nicom.csareader.get_slice_normal", false]], "get_slice_orientation() (nibabel.parrec.parrecheader method)": [[109, "nibabel.parrec.PARRECHeader.get_slice_orientation", false]], "get_slice_times() (nibabel.nifti1.nifti1header method)": [[103, "nibabel.nifti1.Nifti1Header.get_slice_times", false]], "get_slope_inter() (in module nibabel.arraywriters)": [[71, "nibabel.arraywriters.get_slope_inter", false]], "get_slope_inter() (nibabel.analyze.analyzeheader method)": [[69, "nibabel.analyze.AnalyzeHeader.get_slope_inter", false]], "get_slope_inter() (nibabel.brikhead.afniheader method)": [[74, "nibabel.brikhead.AFNIHeader.get_slope_inter", false]], "get_slope_inter() (nibabel.freesurfer.mghformat.mghheader method)": [[92, "nibabel.freesurfer.mghformat.MGHHeader.get_slope_inter", false]], "get_slope_inter() (nibabel.nifti1.nifti1header method)": [[103, "nibabel.nifti1.Nifti1Header.get_slope_inter", false]], "get_slope_inter() (nibabel.spm2analyze.spm2analyzeheader method)": [[117, "nibabel.spm2analyze.Spm2AnalyzeHeader.get_slope_inter", false]], "get_slope_inter() (nibabel.spm99analyze.spmanalyzeheader method)": [[118, "nibabel.spm99analyze.SpmAnalyzeHeader.get_slope_inter", false]], "get_sorted_slice_indices() (nibabel.parrec.parrecheader method)": [[109, "nibabel.parrec.PARRECHeader.get_sorted_slice_indices", false]], "get_space() (nibabel.brikhead.afniheader method)": [[74, "nibabel.brikhead.AFNIHeader.get_space", false]], "get_studies() (in module nibabel.dft)": [[83, "nibabel.dft.get_studies", false]], "get_subheaders() (nibabel.ecat.ecatimage method)": [[84, "nibabel.ecat.EcatImage.get_subheaders", false]], "get_unscaled() (nibabel.arrayproxy.arrayproxy method)": [[70, "nibabel.arrayproxy.ArrayProxy.get_unscaled", false]], "get_unscaled() (nibabel.parrec.parrecarrayproxy method)": [[109, "nibabel.parrec.PARRECArrayProxy.get_unscaled", false]], "get_unscaled_data() (nibabel.nicom.dicomwrappers.mosaicwrapper method)": [[102, "nibabel.nicom.dicomwrappers.MosaicWrapper.get_unscaled_data", false]], "get_unscaled_data() (nibabel.nicom.dicomwrappers.multiframewrapper method)": [[102, "nibabel.nicom.dicomwrappers.MultiframeWrapper.get_unscaled_data", false]], "get_unscaled_data() (nibabel.nicom.dicomwrappers.wrapper method)": [[102, "nibabel.nicom.dicomwrappers.Wrapper.get_unscaled_data", false]], "get_value_label() (nibabel.wrapstruct.labeledwrapstruct method)": [[124, "nibabel.wrapstruct.LabeledWrapStruct.get_value_label", false]], "get_vector() (in module nibabel.nicom.csareader)": [[102, "nibabel.nicom.csareader.get_vector", false]], "get_volume_labels() (nibabel.brikhead.afniheader method)": [[74, "nibabel.brikhead.AFNIHeader.get_volume_labels", false]], "get_volume_labels() (nibabel.parrec.parrecheader method)": [[109, "nibabel.parrec.PARRECHeader.get_volume_labels", false]], "get_vox2ras() (nibabel.freesurfer.mghformat.mghheader method)": [[92, "nibabel.freesurfer.mghformat.MGHHeader.get_vox2ras", false]], "get_vox2ras_tkr() (nibabel.freesurfer.mghformat.mghheader method)": [[92, "nibabel.freesurfer.mghformat.MGHHeader.get_vox2ras_tkr", false]], "get_water_fat_shift() (nibabel.parrec.parrecheader method)": [[109, "nibabel.parrec.PARRECHeader.get_water_fat_shift", false]], "get_xyzt_units() (nibabel.nifti1.nifti1header method)": [[103, "nibabel.nifti1.Nifti1Header.get_xyzt_units", false]], "get_zooms() (nibabel.analyze.analyzeheader method)": [[69, "nibabel.analyze.AnalyzeHeader.get_zooms", false]], "get_zooms() (nibabel.ecat.ecatsubheader method)": [[84, "nibabel.ecat.EcatSubHeader.get_zooms", false]], "get_zooms() (nibabel.freesurfer.mghformat.mghheader method)": [[92, "nibabel.freesurfer.mghformat.MGHHeader.get_zooms", false]], "get_zooms() (nibabel.minc1.minc1file method)": [[99, "nibabel.minc1.Minc1File.get_zooms", false]], "get_zooms() (nibabel.spatialimages.spatialheader method)": [[116, "nibabel.spatialimages.SpatialHeader.get_zooms", false]], "get_zooms() (nibabel.spatialimages.spatialprotocol method)": [[116, "nibabel.spatialimages.SpatialProtocol.get_zooms", false]], "getattr() (nibabel.cmdline.dicomfs.dicomfs method)": [[78, "nibabel.cmdline.dicomfs.DICOMFS.getattr", false]], "gifticoordsystem (class in nibabel.gifti.gifti)": [[94, "nibabel.gifti.gifti.GiftiCoordSystem", false]], "giftidataarray (class in nibabel.gifti.gifti)": [[94, "nibabel.gifti.gifti.GiftiDataArray", false]], "giftiimage (class in nibabel.gifti.gifti)": [[94, "nibabel.gifti.gifti.GiftiImage", false]], "giftiimageparser (class in nibabel.gifti.parse_gifti_fast)": [[94, "nibabel.gifti.parse_gifti_fast.GiftiImageParser", false]], "giftilabel (class in nibabel.gifti.gifti)": [[94, "nibabel.gifti.gifti.GiftiLabel", false]], "giftilabeltable (class in nibabel.gifti.gifti)": [[94, "nibabel.gifti.gifti.GiftiLabelTable", false]], "giftimetadata (class in nibabel.gifti.gifti)": [[94, "nibabel.gifti.gifti.GiftiMetaData", false]], "giftinvpairs (class in nibabel.gifti.gifti)": [[94, "nibabel.gifti.gifti.GiftiNVPairs", false]], "giftiparseerror (class in nibabel.gifti.parse_gifti_fast)": [[94, "nibabel.gifti.parse_gifti_fast.GiftiParseError", false]], "grid (class in nibabel.pointset)": [[110, "nibabel.pointset.Grid", false]], "gridindices (class in nibabel.pointset)": [[110, "nibabel.pointset.GridIndices", false]], "gridshape (nibabel.pointset.gridindices attribute)": [[110, "nibabel.pointset.GridIndices.gridshape", false]], "guessed_endian() (nibabel.analyze.analyzeheader class method)": [[69, "nibabel.analyze.AnalyzeHeader.guessed_endian", false]], "guessed_endian() (nibabel.ecat.ecatheader class method)": [[84, "nibabel.ecat.EcatHeader.guessed_endian", false]], "guessed_endian() (nibabel.freesurfer.mghformat.mghheader class method)": [[92, "nibabel.freesurfer.mghformat.MGHHeader.guessed_endian", false]], "guessed_endian() (nibabel.wrapstruct.wrapstruct class method)": [[124, "nibabel.wrapstruct.WrapStruct.guessed_endian", false]], "guessed_image_type() (in module nibabel.loadsave)": [[98, "nibabel.loadsave.guessed_image_type", false]], "gz_def (nibabel.openers.opener attribute)": [[106, "nibabel.openers.Opener.gz_def", false]], "handler_names (nibabel.xmlutils.xmlparser attribute)": [[125, "nibabel.xmlutils.XmlParser.HANDLER_NAMES", false]], "has_affine (nibabel.spm99analyze.spm99analyzeimage attribute)": [[118, "nibabel.spm99analyze.Spm99AnalyzeImage.has_affine", false]], "has_data_intercept (nibabel.analyze.analyzeheader attribute)": [[69, "nibabel.analyze.AnalyzeHeader.has_data_intercept", false]], "has_data_intercept (nibabel.nifti1.nifti1header attribute)": [[103, "nibabel.nifti1.Nifti1Header.has_data_intercept", false]], "has_data_intercept (nibabel.spm99analyze.spmanalyzeheader attribute)": [[118, "nibabel.spm99analyze.SpmAnalyzeHeader.has_data_intercept", false]], "has_data_slope (nibabel.analyze.analyzeheader attribute)": [[69, "nibabel.analyze.AnalyzeHeader.has_data_slope", false]], "has_data_slope (nibabel.nifti1.nifti1header attribute)": [[103, "nibabel.nifti1.Nifti1Header.has_data_slope", false]], "has_data_slope (nibabel.spm99analyze.spmanalyzeheader attribute)": [[118, "nibabel.spm99analyze.SpmAnalyzeHeader.has_data_slope", false]], "has_nan (nibabel.arraywriters.arraywriter property)": [[71, "nibabel.arraywriters.ArrayWriter.has_nan", false]], "hasdtype (class in nibabel.spatialimages)": [[116, "nibabel.spatialimages.HasDtype", false]], "have_binary128() (in module nibabel.casting)": [[76, "nibabel.casting.have_binary128", false]], "hdf5bunch (class in nibabel.minc2)": [[100, "nibabel.minc2.Hdf5Bunch", false]], "header (nibabel.filebasedimages.filebasedimage property)": [[87, "nibabel.filebasedimages.FileBasedImage.header", false]], "header (nibabel.streamlines.tractogram_file.tractogramfile property)": [[119, "nibabel.streamlines.tractogram_file.TractogramFile.header", false]], "header_class (nibabel.analyze.analyzeimage attribute)": [[69, "nibabel.analyze.AnalyzeImage.header_class", false]], "header_class (nibabel.brikhead.afniimage attribute)": [[74, "nibabel.brikhead.AFNIImage.header_class", false]], "header_class (nibabel.cifti2.cifti2.cifti2image attribute)": [[77, "nibabel.cifti2.cifti2.Cifti2Image.header_class", false]], "header_class (nibabel.ecat.ecatimage attribute)": [[84, "nibabel.ecat.EcatImage.header_class", false]], "header_class (nibabel.filebasedimages.filebasedimage attribute)": [[87, "nibabel.filebasedimages.FileBasedImage.header_class", false]], "header_class (nibabel.freesurfer.mghformat.mghimage attribute)": [[92, "nibabel.freesurfer.mghformat.MGHImage.header_class", false]], "header_class (nibabel.minc1.minc1image attribute)": [[99, "nibabel.minc1.Minc1Image.header_class", false]], "header_class (nibabel.minc2.minc2image attribute)": [[100, "nibabel.minc2.Minc2Image.header_class", false]], "header_class (nibabel.nifti1.nifti1image attribute)": [[103, "nibabel.nifti1.Nifti1Image.header_class", false]], "header_class (nibabel.nifti1.nifti1pair attribute)": [[103, "nibabel.nifti1.Nifti1Pair.header_class", false]], "header_class (nibabel.nifti2.nifti2image attribute)": [[104, "nibabel.nifti2.Nifti2Image.header_class", false]], "header_class (nibabel.nifti2.nifti2pair attribute)": [[104, "nibabel.nifti2.Nifti2Pair.header_class", false]], "header_class (nibabel.parrec.parrecimage attribute)": [[109, "nibabel.parrec.PARRECImage.header_class", false]], "header_class (nibabel.spatialimages.spatialimage attribute)": [[116, "nibabel.spatialimages.SpatialImage.header_class", false]], "header_class (nibabel.spm2analyze.spm2analyzeimage attribute)": [[117, "nibabel.spm2analyze.Spm2AnalyzeImage.header_class", false]], "header_class (nibabel.spm99analyze.spm99analyzeimage attribute)": [[118, "nibabel.spm99analyze.Spm99AnalyzeImage.header_class", false]], "header_size (nibabel.streamlines.trk.trkfile attribute)": [[119, "nibabel.streamlines.trk.TrkFile.HEADER_SIZE", false]], "headerdataerror (class in nibabel.spatialimages)": [[116, "nibabel.spatialimages.HeaderDataError", false]], "headererror (class in nibabel.streamlines.tractogram_file)": [[119, "nibabel.streamlines.tractogram_file.HeaderError", false]], "headertypeerror (class in nibabel.spatialimages)": [[116, "nibabel.spatialimages.HeaderTypeError", false]], "headerwarning (class in nibabel.streamlines.tractogram_file)": [[119, "nibabel.streamlines.tractogram_file.HeaderWarning", false]], "homogeneous (nibabel.pointset.pointset attribute)": [[110, "nibabel.pointset.Pointset.homogeneous", false]], "image_orient_patient (nibabel.nicom.dicomwrappers.multiframewrapper property)": [[102, "nibabel.nicom.dicomwrappers.MultiframeWrapper.image_orient_patient", false]], "image_orient_patient (nibabel.nicom.dicomwrappers.wrapper property)": [[102, "nibabel.nicom.dicomwrappers.Wrapper.image_orient_patient", false]], "image_position (nibabel.nicom.dicomwrappers.mosaicwrapper property)": [[102, "nibabel.nicom.dicomwrappers.MosaicWrapper.image_position", false]], "image_position (nibabel.nicom.dicomwrappers.multiframewrapper property)": [[102, "nibabel.nicom.dicomwrappers.MultiframeWrapper.image_position", false]], "image_position (nibabel.nicom.dicomwrappers.wrapper property)": [[102, "nibabel.nicom.dicomwrappers.Wrapper.image_position", false]], "image_shape (nibabel.nicom.dicomwrappers.mosaicwrapper property)": [[102, "nibabel.nicom.dicomwrappers.MosaicWrapper.image_shape", false]], "image_shape (nibabel.nicom.dicomwrappers.multiframewrapper property)": [[102, "nibabel.nicom.dicomwrappers.MultiframeWrapper.image_shape", false]], "image_shape (nibabel.nicom.dicomwrappers.wrapper property)": [[102, "nibabel.nicom.dicomwrappers.Wrapper.image_shape", false]], "imagearrayproxy (nibabel.analyze.analyzeimage attribute)": [[69, "nibabel.analyze.AnalyzeImage.ImageArrayProxy", false]], "imagearrayproxy (nibabel.brikhead.afniimage attribute)": [[74, "nibabel.brikhead.AFNIImage.ImageArrayProxy", false]], "imagearrayproxy (nibabel.ecat.ecatimage attribute)": [[84, "nibabel.ecat.EcatImage.ImageArrayProxy", false]], "imagearrayproxy (nibabel.freesurfer.mghformat.mghimage attribute)": [[92, "nibabel.freesurfer.mghformat.MGHImage.ImageArrayProxy", false]], "imagearrayproxy (nibabel.minc1.minc1image attribute)": [[99, "nibabel.minc1.Minc1Image.ImageArrayProxy", false]], "imagearrayproxy (nibabel.parrec.parrecimage attribute)": [[109, "nibabel.parrec.PARRECImage.ImageArrayProxy", false]], "imagedataerror (class in nibabel.spatialimages)": [[116, "nibabel.spatialimages.ImageDataError", false]], "imagefileerror (class in nibabel.filebasedimages)": [[87, "nibabel.filebasedimages.ImageFileError", false]], "imageopener (class in nibabel.openers)": [[106, "nibabel.openers.ImageOpener", false]], "imageslicer (nibabel.spatialimages.spatialimage attribute)": [[116, "nibabel.spatialimages.SpatialImage.ImageSlicer", false]], "img (nibabel.spatialimages.spatialfirstslicer attribute)": [[116, "nibabel.spatialimages.SpatialFirstSlicer.img", false]], "in_memory (nibabel.dataobj_images.dataobjimage property)": [[80, "nibabel.dataobj_images.DataobjImage.in_memory", false]], "ingivendirectory() (in module nibabel.tmpdirs)": [[120, "nibabel.tmpdirs.InGivenDirectory", false]], "insert() (nibabel.cifti2.cifti2.cifti2matrix method)": [[77, "nibabel.cifti2.cifti2.Cifti2Matrix.insert", false]], "insert() (nibabel.cifti2.cifti2.cifti2matrixindicesmap method)": [[77, "nibabel.cifti2.cifti2.Cifti2MatrixIndicesMap.insert", false]], "insert() (nibabel.cifti2.cifti2.cifti2vertexindices method)": [[77, "nibabel.cifti2.cifti2.Cifti2VertexIndices.insert", false]], "insert() (nibabel.cifti2.cifti2.cifti2vertices method)": [[77, "nibabel.cifti2.cifti2.Cifti2Vertices.insert", false]], "insert() (nibabel.cifti2.cifti2.cifti2voxelindicesijk method)": [[77, "nibabel.cifti2.cifti2.Cifti2VoxelIndicesIJK.insert", false]], "instance_number (nibabel.nicom.dicomwrappers.wrapper property)": [[102, "nibabel.nicom.dicomwrappers.Wrapper.instance_number", false]], "instance_to_filename() (nibabel.filebasedimages.filebasedimage class method)": [[87, "nibabel.filebasedimages.FileBasedImage.instance_to_filename", false]], "instancestackerror (class in nibabel.dft)": [[83, "nibabel.dft.InstanceStackError", false]], "int_abs() (in module nibabel.casting)": [[76, "nibabel.casting.int_abs", false]], "int_scinter_ftype() (in module nibabel.volumeutils)": [[123, "nibabel.volumeutils.int_scinter_ftype", false]], "int_to_float() (in module nibabel.casting)": [[76, "nibabel.casting.int_to_float", false]], "intemporarydirectory() (in module nibabel.tmpdirs)": [[120, "nibabel.tmpdirs.InTemporaryDirectory", false]], "inter (nibabel.arrayproxy.arrayproxy property)": [[70, "nibabel.arrayproxy.ArrayProxy.inter", false]], "inter (nibabel.arraywriters.slopeinterarraywriter property)": [[71, "nibabel.arraywriters.SlopeInterArrayWriter.inter", false]], "inv_ornt_aff() (in module nibabel.orientations)": [[108, "nibabel.orientations.inv_ornt_aff", false]], "inverse() (in module nibabel.quaternions)": [[113, "nibabel.quaternions.inverse", false]], "io_orientation() (in module nibabel.orientations)": [[108, "nibabel.orientations.io_orientation", false]], "is_array_sequence (nibabel.streamlines.array_sequence.arraysequence property)": [[119, "nibabel.streamlines.array_sequence.ArraySequence.is_array_sequence", false]], "is_array_sequence() (in module nibabel.streamlines.array_sequence)": [[119, "nibabel.streamlines.array_sequence.is_array_sequence", false]], "is_bad_version() (nibabel.deprecator.deprecator method)": [[82, "nibabel.deprecator.Deprecator.is_bad_version", false]], "is_correct_format() (nibabel.streamlines.tck.tckfile class method)": [[119, "nibabel.streamlines.tck.TckFile.is_correct_format", false]], "is_correct_format() (nibabel.streamlines.tractogram_file.tractogramfile class method)": [[119, "nibabel.streamlines.tractogram_file.TractogramFile.is_correct_format", false]], "is_correct_format() (nibabel.streamlines.trk.trkfile class method)": [[119, "nibabel.streamlines.trk.TrkFile.is_correct_format", false]], "is_csa (nibabel.nicom.dicomwrappers.siemenswrapper attribute)": [[102, "nibabel.nicom.dicomwrappers.SiemensWrapper.is_csa", false]], "is_csa (nibabel.nicom.dicomwrappers.wrapper attribute)": [[102, "nibabel.nicom.dicomwrappers.Wrapper.is_csa", false]], "is_data_dict() (in module nibabel.streamlines.tractogram)": [[119, "nibabel.streamlines.tractogram.is_data_dict", false]], "is_fancy() (in module nibabel.fileslice)": [[90, "nibabel.fileslice.is_fancy", false]], "is_lazy_dict() (in module nibabel.streamlines.tractogram)": [[119, "nibabel.streamlines.tractogram.is_lazy_dict", false]], "is_mosaic (nibabel.nicom.dicomwrappers.mosaicwrapper attribute)": [[102, "nibabel.nicom.dicomwrappers.MosaicWrapper.is_mosaic", false]], "is_mosaic (nibabel.nicom.dicomwrappers.wrapper attribute)": [[102, "nibabel.nicom.dicomwrappers.Wrapper.is_mosaic", false]], "is_mosaic() (in module nibabel.nicom.csareader)": [[102, "nibabel.nicom.csareader.is_mosaic", false]], "is_multiframe (nibabel.nicom.dicomwrappers.multiframewrapper attribute)": [[102, "nibabel.nicom.dicomwrappers.MultiframeWrapper.is_multiframe", false]], "is_multiframe (nibabel.nicom.dicomwrappers.wrapper attribute)": [[102, "nibabel.nicom.dicomwrappers.Wrapper.is_multiframe", false]], "is_ndarray_of_int_or_bool() (in module nibabel.streamlines.array_sequence)": [[119, "nibabel.streamlines.array_sequence.is_ndarray_of_int_or_bool", false]], "is_proxy (nibabel.arrayproxy.arrayproxy property)": [[70, "nibabel.arrayproxy.ArrayProxy.is_proxy", false]], "is_proxy (nibabel.ecat.ecatimagearrayproxy property)": [[84, "nibabel.ecat.EcatImageArrayProxy.is_proxy", false]], "is_proxy (nibabel.minc1.mincimagearrayproxy property)": [[99, "nibabel.minc1.MincImageArrayProxy.is_proxy", false]], "is_proxy (nibabel.parrec.parrecarrayproxy property)": [[109, "nibabel.parrec.PARRECArrayProxy.is_proxy", false]], "is_proxy() (in module nibabel.arrayproxy)": [[70, "nibabel.arrayproxy.is_proxy", false]], "is_same_series() (nibabel.nicom.dicomwrappers.wrapper method)": [[102, "nibabel.nicom.dicomwrappers.Wrapper.is_same_series", false]], "is_single (nibabel.nifti1.nifti1header attribute)": [[103, "nibabel.nifti1.Nifti1Header.is_single", false]], "is_single (nibabel.nifti1.nifti1pairheader attribute)": [[103, "nibabel.nifti1.Nifti1PairHeader.is_single", false]], "is_single (nibabel.nifti2.nifti2pairheader attribute)": [[104, "nibabel.nifti2.Nifti2PairHeader.is_single", false]], "is_sliced_view (nibabel.streamlines.array_sequence.arraysequence property)": [[119, "nibabel.streamlines.array_sequence.ArraySequence.is_sliced_view", false]], "is_supported() (in module nibabel.streamlines)": [[119, "nibabel.streamlines.is_supported", false]], "is_tripwire() (in module nibabel.tripwire)": [[121, "nibabel.tripwire.is_tripwire", false]], "isunit() (in module nibabel.quaternions)": [[113, "nibabel.quaternions.isunit", false]], "items() (nibabel.wrapstruct.wrapstruct method)": [[124, "nibabel.wrapstruct.WrapStruct.items", false]], "iter_structures() (nibabel.cifti2.cifti2_axes.brainmodelaxis method)": [[77, "nibabel.cifti2.cifti2_axes.BrainModelAxis.iter_structures", false]], "json() (nibabel.nifti1.niftiextension method)": [[103, "nibabel.nifti1.NiftiExtension.json", false]], "keep() (nibabel.nicom.dicomwrappers.filterdwiiso method)": [[102, "nibabel.nicom.dicomwrappers.FilterDwiIso.keep", false]], "keep() (nibabel.nicom.dicomwrappers.filtermultistack method)": [[102, "nibabel.nicom.dicomwrappers.FilterMultiStack.keep", false]], "keep() (nibabel.nicom.dicomwrappers.framefilter method)": [[102, "nibabel.nicom.dicomwrappers.FrameFilter.keep", false]], "keys() (nibabel.volumeutils.recoder method)": [[123, "nibabel.volumeutils.Recoder.keys", false]], "keys() (nibabel.wrapstruct.wrapstruct method)": [[124, "nibabel.wrapstruct.WrapStruct.keys", false]], "label_table (nibabel.cifti2.cifti2.cifti2namedmap property)": [[77, "nibabel.cifti2.cifti2.Cifti2NamedMap.label_table", false]], "labelaxis (class in nibabel.cifti2.cifti2_axes)": [[77, "nibabel.cifti2.cifti2_axes.LabelAxis", false]], "labeledwrapstruct (class in nibabel.wrapstruct)": [[124, "nibabel.wrapstruct.LabeledWrapStruct", false]], "labeltable (nibabel.gifti.gifti.giftiimage property)": [[94, "nibabel.gifti.gifti.GiftiImage.labeltable", false]], "lazydict (class in nibabel.streamlines.tractogram)": [[119, "nibabel.streamlines.tractogram.LazyDict", false]], "lazytractogram (class in nibabel.streamlines.tractogram)": [[119, "nibabel.streamlines.tractogram.LazyTractogram", false]], "limitednifti2header (class in nibabel.cifti2.cifti2)": [[77, "nibabel.cifti2.cifti2.LimitedNifti2Header", false]], "link_to() (nibabel.viewers.orthoslicer3d method)": [[122, "nibabel.viewers.OrthoSlicer3D.link_to", false]], "list_files() (nibabel.data.datasource method)": [[79, "nibabel.data.Datasource.list_files", false]], "load() (in module nibabel.loadsave)": [[98, "nibabel.loadsave.load", false]], "load() (in module nibabel.nifti1)": [[103, "nibabel.nifti1.load", false]], "load() (in module nibabel.nifti2)": [[104, "nibabel.nifti2.load", false]], "load() (in module nibabel.streamlines)": [[119, "nibabel.streamlines.load", false]], "load() (nibabel.dataobj_images.dataobjimage class method)": [[80, "nibabel.dataobj_images.DataobjImage.load", false]], "load() (nibabel.ecat.ecatimage class method)": [[84, "nibabel.ecat.EcatImage.load", false]], "load() (nibabel.filebasedimages.filebasedimage class method)": [[87, "nibabel.filebasedimages.FileBasedImage.load", false]], "load() (nibabel.parrec.parrecimage class method)": [[109, "nibabel.parrec.PARRECImage.load", false]], "load() (nibabel.streamlines.array_sequence.arraysequence class method)": [[119, "nibabel.streamlines.array_sequence.ArraySequence.load", false]], "load() (nibabel.streamlines.tck.tckfile class method)": [[119, "nibabel.streamlines.tck.TckFile.load", false]], "load() (nibabel.streamlines.tractogram_file.tractogramfile class method)": [[119, "nibabel.streamlines.tractogram_file.TractogramFile.load", false]], "load() (nibabel.streamlines.trk.trkfile class method)": [[119, "nibabel.streamlines.trk.TrkFile.load", false]], "log_raise() (nibabel.batteryrunners.report method)": [[72, "nibabel.batteryrunners.Report.log_raise", false]], "loggingoutputsuppressor (class in nibabel.imageglobals)": [[96, "nibabel.imageglobals.LoggingOutputSuppressor", false]], "longdouble_lte_float64() (in module nibabel.casting)": [[76, "nibabel.casting.longdouble_lte_float64", false]], "longdouble_precision_improved() (in module nibabel.casting)": [[76, "nibabel.casting.longdouble_precision_improved", false]], "lossless_slice() (in module nibabel.cmdline.roi)": [[78, "nibabel.cmdline.roi.lossless_slice", false]], "magic_number (nibabel.streamlines.header.field attribute)": [[119, "nibabel.streamlines.header.Field.MAGIC_NUMBER", false]], "magic_number (nibabel.streamlines.tck.tckfile attribute)": [[119, "nibabel.streamlines.tck.TckFile.MAGIC_NUMBER", false]], "magic_number (nibabel.streamlines.trk.trkfile attribute)": [[119, "nibabel.streamlines.trk.TrkFile.MAGIC_NUMBER", false]], "main() (in module nibabel.cmdline.conform)": [[78, "nibabel.cmdline.conform.main", false]], "main() (in module nibabel.cmdline.convert)": [[78, "nibabel.cmdline.convert.main", false]], "main() (in module nibabel.cmdline.dicomfs)": [[78, "nibabel.cmdline.dicomfs.main", false]], "main() (in module nibabel.cmdline.diff)": [[78, "nibabel.cmdline.diff.main", false]], "main() (in module nibabel.cmdline.ls)": [[78, "nibabel.cmdline.ls.main", false]], "main() (in module nibabel.cmdline.nifti_dx)": [[78, "nibabel.cmdline.nifti_dx.main", false]], "main() (in module nibabel.cmdline.parrec2nii)": [[78, "nibabel.cmdline.parrec2nii.main", false]], "main() (in module nibabel.cmdline.roi)": [[78, "nibabel.cmdline.roi.main", false]], "main() (in module nibabel.cmdline.stats)": [[78, "nibabel.cmdline.stats.main", false]], "main() (in module nibabel.cmdline.tck2trk)": [[78, "nibabel.cmdline.tck2trk.main", false]], "main() (in module nibabel.cmdline.trk2tck)": [[78, "nibabel.cmdline.trk2tck.main", false]], "make_array_writer() (in module nibabel.arraywriters)": [[71, "nibabel.arraywriters.make_array_writer", false]], "make_datasource() (in module nibabel.data)": [[79, "nibabel.data.make_datasource", false]], "make_dt_codes() (in module nibabel.volumeutils)": [[123, "nibabel.volumeutils.make_dt_codes", false]], "make_file_map() (nibabel.filebasedimages.filebasedimage class method)": [[87, "nibabel.filebasedimages.FileBasedImage.make_file_map", false]], "makeable (nibabel.analyze.analyzeimage attribute)": [[69, "nibabel.analyze.AnalyzeImage.makeable", false]], "makeable (nibabel.brikhead.afniimage attribute)": [[74, "nibabel.brikhead.AFNIImage.makeable", false]], "makeable (nibabel.cifti2.cifti2.cifti2image attribute)": [[77, "nibabel.cifti2.cifti2.Cifti2Image.makeable", false]], "makeable (nibabel.filebasedimages.filebasedimage attribute)": [[87, "nibabel.filebasedimages.FileBasedImage.makeable", false]], "makeable (nibabel.freesurfer.mghformat.mghimage attribute)": [[92, "nibabel.freesurfer.mghformat.MGHImage.makeable", false]], "makeable (nibabel.minc1.minc1image attribute)": [[99, "nibabel.minc1.Minc1Image.makeable", false]], "makeable (nibabel.parrec.parrecimage attribute)": [[109, "nibabel.parrec.PARRECImage.makeable", false]], "makeable (nibabel.spm99analyze.spm99analyzeimage attribute)": [[118, "nibabel.spm99analyze.Spm99AnalyzeImage.makeable", false]], "mapped_indices (nibabel.cifti2.cifti2.cifti2header property)": [[77, "nibabel.cifti2.cifti2.Cifti2Header.mapped_indices", false]], "mapped_indices (nibabel.cifti2.cifti2.cifti2matrix property)": [[77, "nibabel.cifti2.cifti2.Cifti2Matrix.mapped_indices", false]], "mask_volume() (in module nibabel.imagestats)": [[97, "nibabel.imagestats.mask_volume", false]], "mat2euler() (in module nibabel.eulerangles)": [[86, "nibabel.eulerangles.mat2euler", false]], "mat2quat() (in module nibabel.quaternions)": [[113, "nibabel.quaternions.mat2quat", false]], "match_path() (nibabel.cmdline.dicomfs.dicomfs method)": [[78, "nibabel.cmdline.dicomfs.DICOMFS.match_path", false]], "may_contain_header() (nibabel.analyze.analyzeheader class method)": [[69, "nibabel.analyze.AnalyzeHeader.may_contain_header", false]], "may_contain_header() (nibabel.cifti2.cifti2.cifti2header class method)": [[77, "nibabel.cifti2.cifti2.Cifti2Header.may_contain_header", false]], "may_contain_header() (nibabel.minc1.minc1header class method)": [[99, "nibabel.minc1.Minc1Header.may_contain_header", false]], "may_contain_header() (nibabel.minc2.minc2header class method)": [[100, "nibabel.minc2.Minc2Header.may_contain_header", false]], "may_contain_header() (nibabel.nifti1.nifti1header class method)": [[103, "nibabel.nifti1.Nifti1Header.may_contain_header", false]], "may_contain_header() (nibabel.nifti2.nifti2header class method)": [[104, "nibabel.nifti2.Nifti2Header.may_contain_header", false]], "may_contain_header() (nibabel.spm2analyze.spm2analyzeheader class method)": [[117, "nibabel.spm2analyze.Spm2AnalyzeHeader.may_contain_header", false]], "message (nibabel.batteryrunners.report property)": [[72, "nibabel.batteryrunners.Report.message", false]], "meta (nibabel.gifti.gifti.giftiimage property)": [[94, "nibabel.gifti.gifti.GiftiImage.meta", false]], "metadata (nibabel.cifti2.cifti2.cifti2matrix property)": [[77, "nibabel.cifti2.cifti2.Cifti2Matrix.metadata", false]], "metadata (nibabel.cifti2.cifti2.cifti2namedmap property)": [[77, "nibabel.cifti2.cifti2.Cifti2NamedMap.metadata", false]], "metadata (nibabel.gifti.gifti.giftidataarray property)": [[94, "nibabel.gifti.gifti.GiftiDataArray.metadata", false]], "metadata (nibabel.gifti.gifti.giftimetadata property)": [[94, "nibabel.gifti.gifti.GiftiMetaData.metadata", false]], "method (nibabel.streamlines.header.field attribute)": [[119, "nibabel.streamlines.header.Field.METHOD", false]], "mgherror (class in nibabel.freesurfer.mghformat)": [[92, "nibabel.freesurfer.mghformat.MGHError", false]], "mghheader (class in nibabel.freesurfer.mghformat)": [[92, "nibabel.freesurfer.mghformat.MGHHeader", false]], "mghimage (class in nibabel.freesurfer.mghformat)": [[92, "nibabel.freesurfer.mghformat.MGHImage", false]], "minc1file (class in nibabel.minc1)": [[99, "nibabel.minc1.Minc1File", false]], "minc1header (class in nibabel.minc1)": [[99, "nibabel.minc1.Minc1Header", false]], "minc1image (class in nibabel.minc1)": [[99, "nibabel.minc1.Minc1Image", false]], "minc2file (class in nibabel.minc2)": [[100, "nibabel.minc2.Minc2File", false]], "minc2header (class in nibabel.minc2)": [[100, "nibabel.minc2.Minc2Header", false]], "minc2image (class in nibabel.minc2)": [[100, "nibabel.minc2.Minc2Image", false]], "mincerror (class in nibabel.minc1)": [[99, "nibabel.minc1.MincError", false]], "mincheader (class in nibabel.minc1)": [[99, "nibabel.minc1.MincHeader", false]], "mincimagearrayproxy (class in nibabel.minc1)": [[99, "nibabel.minc1.MincImageArrayProxy", false]], "mode (nibabel.openers.opener property)": [[106, "nibabel.openers.Opener.mode", false]], "module": [[66, "module-nibabel", false], [67, "module-nibabel._compression", false], [68, "module-nibabel.affines", false], [69, "module-nibabel.analyze", false], [70, "module-nibabel.arrayproxy", false], [71, "module-nibabel.arraywriters", false], [72, "module-nibabel.batteryrunners", false], [73, "module-nibabel.benchmarks", false], [73, "module-nibabel.benchmarks.bench_array_to_file", false], [73, "module-nibabel.benchmarks.bench_arrayproxy_slicing", false], [73, "module-nibabel.benchmarks.bench_fileslice", false], [73, "module-nibabel.benchmarks.bench_finite_range", false], [73, "module-nibabel.benchmarks.bench_load_save", false], [73, "module-nibabel.benchmarks.butils", false], [74, "module-nibabel.brikhead", false], [75, "module-nibabel.caret", false], [76, "module-nibabel.casting", false], [77, "module-nibabel.cifti2", false], [77, "module-nibabel.cifti2.cifti2", false], [77, "module-nibabel.cifti2.cifti2_axes", false], [77, "module-nibabel.cifti2.parse_cifti2", false], [78, "module-nibabel.cmdline", false], [78, "module-nibabel.cmdline.conform", false], [78, "module-nibabel.cmdline.convert", false], [78, "module-nibabel.cmdline.dicomfs", false], [78, "module-nibabel.cmdline.diff", false], [78, "module-nibabel.cmdline.ls", false], [78, "module-nibabel.cmdline.nifti_dx", false], [78, "module-nibabel.cmdline.parrec2nii", false], [78, "module-nibabel.cmdline.roi", false], [78, "module-nibabel.cmdline.stats", false], [78, "module-nibabel.cmdline.tck2trk", false], [78, "module-nibabel.cmdline.trk2tck", false], [78, "module-nibabel.cmdline.utils", false], [79, "module-nibabel.data", false], [80, "module-nibabel.dataobj_images", false], [81, "module-nibabel.deprecated", false], [82, "module-nibabel.deprecator", false], [83, "module-nibabel.dft", false], [84, "module-nibabel.ecat", false], [85, "module-nibabel.environment", false], [86, "module-nibabel.eulerangles", false], [87, "module-nibabel.filebasedimages", false], [88, "module-nibabel.fileholders", false], [89, "module-nibabel.filename_parser", false], [90, "module-nibabel.fileslice", false], [91, "module-nibabel.fileutils", false], [92, "module-nibabel.freesurfer", false], [92, "module-nibabel.freesurfer.io", false], [92, "module-nibabel.freesurfer.mghformat", false], [93, "module-nibabel.funcs", false], [94, "module-nibabel.gifti", false], [94, "module-nibabel.gifti.gifti", false], [94, "module-nibabel.gifti.parse_gifti_fast", false], [94, "module-nibabel.gifti.util", false], [95, "module-nibabel.imageclasses", false], [96, "module-nibabel.imageglobals", false], [97, "module-nibabel.imagestats", false], [98, "module-nibabel.loadsave", false], [99, "module-nibabel.minc1", false], [100, "module-nibabel.minc2", false], [101, "module-nibabel.mriutils", false], [102, "module-nibabel.nicom", false], [102, "module-nibabel.nicom.ascconv", false], [102, "module-nibabel.nicom.csareader", false], [102, "module-nibabel.nicom.dicomreaders", false], [102, "module-nibabel.nicom.dicomwrappers", false], [102, "module-nibabel.nicom.dwiparams", false], [102, "module-nibabel.nicom.structreader", false], [102, "module-nibabel.nicom.utils", false], [103, "module-nibabel.nifti1", false], [104, "module-nibabel.nifti2", false], [105, "module-nibabel.onetime", false], [106, "module-nibabel.openers", false], [107, "module-nibabel.optpkg", false], [108, "module-nibabel.orientations", false], [109, "module-nibabel.parrec", false], [110, "module-nibabel.pointset", false], [111, "module-nibabel.processing", false], [112, "module-nibabel.pydicom_compat", false], [113, "module-nibabel.quaternions", false], [114, "module-nibabel.rstutils", false], [115, "module-nibabel.spaces", false], [116, "module-nibabel.spatialimages", false], [117, "module-nibabel.spm2analyze", false], [118, "module-nibabel.spm99analyze", false], [119, "module-nibabel.streamlines", false], [119, "module-nibabel.streamlines.array_sequence", false], [119, "module-nibabel.streamlines.header", false], [119, "module-nibabel.streamlines.tck", false], [119, "module-nibabel.streamlines.tractogram", false], [119, "module-nibabel.streamlines.tractogram_file", false], [119, "module-nibabel.streamlines.trk", false], [119, "module-nibabel.streamlines.utils", false], [120, "module-nibabel.tmpdirs", false], [121, "module-nibabel.tripwire", false], [122, "module-nibabel.viewers", false], [123, "module-nibabel.volumeutils", false], [124, "module-nibabel.wrapstruct", false], [125, "module-nibabel.xmlutils", false]], "moduleproxy (class in nibabel.deprecated)": [[81, "nibabel.deprecated.ModuleProxy", false]], "mosaic_to_nii() (in module nibabel.nicom.dicomreaders)": [[102, "nibabel.nicom.dicomreaders.mosaic_to_nii", false]], "mosaicwrapper (class in nibabel.nicom.dicomwrappers)": [[102, "nibabel.nicom.dicomwrappers.MosaicWrapper", false]], "mrierror (class in nibabel.mriutils)": [[101, "nibabel.mriutils.MRIError", false]], "mult() (in module nibabel.quaternions)": [[113, "nibabel.quaternions.mult", false]], "multiframewrapper (class in nibabel.nicom.dicomwrappers)": [[102, "nibabel.nicom.dicomwrappers.MultiframeWrapper", false]], "n_coords (nibabel.pointset.pointset property)": [[110, "nibabel.pointset.Pointset.n_coords", false]], "n_volumes (nibabel.viewers.orthoslicer3d property)": [[122, "nibabel.viewers.OrthoSlicer3D.n_volumes", false]], "name (nibabel.cifti2.cifti2_axes.brainmodelaxis property)": [[77, "nibabel.cifti2.cifti2_axes.BrainModelAxis.name", false]], "name (nibabel.gifti.gifti.giftinvpairs property)": [[94, "nibabel.gifti.gifti.GiftiNVPairs.name", false]], "name (nibabel.openers.opener property)": [[106, "nibabel.openers.Opener.name", false]], "named_maps (nibabel.cifti2.cifti2.cifti2matrixindicesmap property)": [[77, "nibabel.cifti2.cifti2.Cifti2MatrixIndicesMap.named_maps", false]], "nb_points (nibabel.streamlines.header.field attribute)": [[119, "nibabel.streamlines.header.Field.NB_POINTS", false]], "nb_properties_per_streamline (nibabel.streamlines.header.field attribute)": [[119, "nibabel.streamlines.header.Field.NB_PROPERTIES_PER_STREAMLINE", false]], "nb_scalars_per_point (nibabel.streamlines.header.field attribute)": [[119, "nibabel.streamlines.header.Field.NB_SCALARS_PER_POINT", false]], "nb_streamlines (nibabel.streamlines.header.field attribute)": [[119, "nibabel.streamlines.header.Field.NB_STREAMLINES", false]], "ndim (nibabel.arrayproxy.arraylike property)": [[70, "nibabel.arrayproxy.ArrayLike.ndim", false]], "ndim (nibabel.arrayproxy.arrayproxy property)": [[70, "nibabel.arrayproxy.ArrayProxy.ndim", false]], "ndim (nibabel.dataobj_images.dataobjimage property)": [[80, "nibabel.dataobj_images.DataobjImage.ndim", false]], "ndim (nibabel.ecat.ecatimagearrayproxy property)": [[84, "nibabel.ecat.EcatImageArrayProxy.ndim", false]], "ndim (nibabel.minc1.mincimagearrayproxy property)": [[99, "nibabel.minc1.MincImageArrayProxy.ndim", false]], "ndim (nibabel.parrec.parrecarrayproxy property)": [[109, "nibabel.parrec.PARRECArrayProxy.ndim", false]], "ndim (nibabel.pointset.coordinatearray attribute)": [[110, "nibabel.pointset.CoordinateArray.ndim", false]], "ndim (nibabel.pointset.gridindices attribute)": [[110, "nibabel.pointset.GridIndices.ndim", false]], "nearest_pos_semi_def() (in module nibabel.nicom.dwiparams)": [[102, "nibabel.nicom.dwiparams.nearest_pos_semi_def", false]], "nearly_equivalent() (in module nibabel.quaternions)": [[113, "nibabel.quaternions.nearly_equivalent", false]], "nibabel": [[66, "module-nibabel", false]], "nibabel._compression": [[67, "module-nibabel._compression", false]], "nibabel.affines": [[68, "module-nibabel.affines", false]], "nibabel.analyze": [[69, "module-nibabel.analyze", false]], "nibabel.arrayproxy": [[70, "module-nibabel.arrayproxy", false]], "nibabel.arraywriters": [[71, "module-nibabel.arraywriters", false]], "nibabel.batteryrunners": [[72, "module-nibabel.batteryrunners", false]], "nibabel.benchmarks": [[73, "module-nibabel.benchmarks", false]], "nibabel.benchmarks.bench_array_to_file": [[73, "module-nibabel.benchmarks.bench_array_to_file", false]], "nibabel.benchmarks.bench_arrayproxy_slicing": [[73, "module-nibabel.benchmarks.bench_arrayproxy_slicing", false]], "nibabel.benchmarks.bench_fileslice": [[73, "module-nibabel.benchmarks.bench_fileslice", false]], "nibabel.benchmarks.bench_finite_range": [[73, "module-nibabel.benchmarks.bench_finite_range", false]], "nibabel.benchmarks.bench_load_save": [[73, "module-nibabel.benchmarks.bench_load_save", false]], "nibabel.benchmarks.butils": [[73, "module-nibabel.benchmarks.butils", false]], "nibabel.brikhead": [[74, "module-nibabel.brikhead", false]], "nibabel.caret": [[75, "module-nibabel.caret", false]], "nibabel.casting": [[76, "module-nibabel.casting", false]], "nibabel.cifti2": [[77, "module-nibabel.cifti2", false]], "nibabel.cifti2.cifti2": [[77, "module-nibabel.cifti2.cifti2", false]], "nibabel.cifti2.cifti2_axes": [[77, "module-nibabel.cifti2.cifti2_axes", false]], "nibabel.cifti2.parse_cifti2": [[77, "module-nibabel.cifti2.parse_cifti2", false]], "nibabel.cmdline": [[78, "module-nibabel.cmdline", false]], "nibabel.cmdline.conform": [[78, "module-nibabel.cmdline.conform", false]], "nibabel.cmdline.convert": [[78, "module-nibabel.cmdline.convert", false]], "nibabel.cmdline.dicomfs": [[78, "module-nibabel.cmdline.dicomfs", false]], "nibabel.cmdline.diff": [[78, "module-nibabel.cmdline.diff", false]], "nibabel.cmdline.ls": [[78, "module-nibabel.cmdline.ls", false]], "nibabel.cmdline.nifti_dx": [[78, "module-nibabel.cmdline.nifti_dx", false]], "nibabel.cmdline.parrec2nii": [[78, "module-nibabel.cmdline.parrec2nii", false]], "nibabel.cmdline.roi": [[78, "module-nibabel.cmdline.roi", false]], "nibabel.cmdline.stats": [[78, "module-nibabel.cmdline.stats", false]], "nibabel.cmdline.tck2trk": [[78, "module-nibabel.cmdline.tck2trk", false]], "nibabel.cmdline.trk2tck": [[78, "module-nibabel.cmdline.trk2tck", false]], "nibabel.cmdline.utils": [[78, "module-nibabel.cmdline.utils", false]], "nibabel.data": [[79, "module-nibabel.data", false]], "nibabel.dataobj_images": [[80, "module-nibabel.dataobj_images", false]], "nibabel.deprecated": [[81, "module-nibabel.deprecated", false]], "nibabel.deprecator": [[82, "module-nibabel.deprecator", false]], "nibabel.dft": [[83, "module-nibabel.dft", false]], "nibabel.ecat": [[84, "module-nibabel.ecat", false]], "nibabel.environment": [[85, "module-nibabel.environment", false]], "nibabel.eulerangles": [[86, "module-nibabel.eulerangles", false]], "nibabel.filebasedimages": [[87, "module-nibabel.filebasedimages", false]], "nibabel.fileholders": [[88, "module-nibabel.fileholders", false]], "nibabel.filename_parser": [[89, "module-nibabel.filename_parser", false]], "nibabel.fileslice": [[90, "module-nibabel.fileslice", false]], "nibabel.fileutils": [[91, "module-nibabel.fileutils", false]], "nibabel.freesurfer": [[92, "module-nibabel.freesurfer", false]], "nibabel.freesurfer.io": [[92, "module-nibabel.freesurfer.io", false]], "nibabel.freesurfer.mghformat": [[92, "module-nibabel.freesurfer.mghformat", false]], "nibabel.funcs": [[93, "module-nibabel.funcs", false]], "nibabel.gifti": [[94, "module-nibabel.gifti", false]], "nibabel.gifti.gifti": [[94, "module-nibabel.gifti.gifti", false]], "nibabel.gifti.parse_gifti_fast": [[94, "module-nibabel.gifti.parse_gifti_fast", false]], "nibabel.gifti.util": [[94, "module-nibabel.gifti.util", false]], "nibabel.imageclasses": [[95, "module-nibabel.imageclasses", false]], "nibabel.imageglobals": [[96, "module-nibabel.imageglobals", false]], "nibabel.imagestats": [[97, "module-nibabel.imagestats", false]], "nibabel.loadsave": [[98, "module-nibabel.loadsave", false]], "nibabel.minc1": [[99, "module-nibabel.minc1", false]], "nibabel.minc2": [[100, "module-nibabel.minc2", false]], "nibabel.mriutils": [[101, "module-nibabel.mriutils", false]], "nibabel.nicom": [[102, "module-nibabel.nicom", false]], "nibabel.nicom.ascconv": [[102, "module-nibabel.nicom.ascconv", false]], "nibabel.nicom.csareader": [[102, "module-nibabel.nicom.csareader", false]], "nibabel.nicom.dicomreaders": [[102, "module-nibabel.nicom.dicomreaders", false]], "nibabel.nicom.dicomwrappers": [[102, "module-nibabel.nicom.dicomwrappers", false]], "nibabel.nicom.dwiparams": [[102, "module-nibabel.nicom.dwiparams", false]], "nibabel.nicom.structreader": [[102, "module-nibabel.nicom.structreader", false]], "nibabel.nicom.utils": [[102, "module-nibabel.nicom.utils", false]], "nibabel.nifti1": [[103, "module-nibabel.nifti1", false]], "nibabel.nifti2": [[104, "module-nibabel.nifti2", false]], "nibabel.onetime": [[105, "module-nibabel.onetime", false]], "nibabel.openers": [[106, "module-nibabel.openers", false]], "nibabel.optpkg": [[107, "module-nibabel.optpkg", false]], "nibabel.orientations": [[108, "module-nibabel.orientations", false]], "nibabel.parrec": [[109, "module-nibabel.parrec", false]], "nibabel.pointset": [[110, "module-nibabel.pointset", false]], "nibabel.processing": [[111, "module-nibabel.processing", false]], "nibabel.pydicom_compat": [[112, "module-nibabel.pydicom_compat", false]], "nibabel.quaternions": [[113, "module-nibabel.quaternions", false]], "nibabel.rstutils": [[114, "module-nibabel.rstutils", false]], "nibabel.spaces": [[115, "module-nibabel.spaces", false]], "nibabel.spatialimages": [[116, "module-nibabel.spatialimages", false]], "nibabel.spm2analyze": [[117, "module-nibabel.spm2analyze", false]], "nibabel.spm99analyze": [[118, "module-nibabel.spm99analyze", false]], "nibabel.streamlines": [[119, "module-nibabel.streamlines", false]], "nibabel.streamlines.array_sequence": [[119, "module-nibabel.streamlines.array_sequence", false]], "nibabel.streamlines.header": [[119, "module-nibabel.streamlines.header", false]], "nibabel.streamlines.tck": [[119, "module-nibabel.streamlines.tck", false]], "nibabel.streamlines.tractogram": [[119, "module-nibabel.streamlines.tractogram", false]], "nibabel.streamlines.tractogram_file": [[119, "module-nibabel.streamlines.tractogram_file", false]], "nibabel.streamlines.trk": [[119, "module-nibabel.streamlines.trk", false]], "nibabel.streamlines.utils": [[119, "module-nibabel.streamlines.utils", false]], "nibabel.tmpdirs": [[120, "module-nibabel.tmpdirs", false]], "nibabel.tripwire": [[121, "module-nibabel.tripwire", false]], "nibabel.viewers": [[122, "module-nibabel.viewers", false]], "nibabel.volumeutils": [[123, "module-nibabel.volumeutils", false]], "nibabel.wrapstruct": [[124, "module-nibabel.wrapstruct", false]], "nibabel.xmlutils": [[125, "module-nibabel.xmlutils", false]], "nifti1dicomextension (class in nibabel.nifti1)": [[103, "nibabel.nifti1.Nifti1DicomExtension", false]], "nifti1extension (class in nibabel.nifti1)": [[103, "nibabel.nifti1.Nifti1Extension", false]], "nifti1extensions (class in nibabel.nifti1)": [[103, "nibabel.nifti1.Nifti1Extensions", false]], "nifti1header (class in nibabel.nifti1)": [[103, "nibabel.nifti1.Nifti1Header", false]], "nifti1image (class in nibabel.nifti1)": [[103, "nibabel.nifti1.Nifti1Image", false]], "nifti1pair (class in nibabel.nifti1)": [[103, "nibabel.nifti1.Nifti1Pair", false]], "nifti1pairheader (class in nibabel.nifti1)": [[103, "nibabel.nifti1.Nifti1PairHeader", false]], "nifti2header (class in nibabel.nifti2)": [[104, "nibabel.nifti2.Nifti2Header", false]], "nifti2image (class in nibabel.nifti2)": [[104, "nibabel.nifti2.Nifti2Image", false]], "nifti2pair (class in nibabel.nifti2)": [[104, "nibabel.nifti2.Nifti2Pair", false]], "nifti2pairheader (class in nibabel.nifti2)": [[104, "nibabel.nifti2.Nifti2PairHeader", false]], "nifti_header (nibabel.cifti2.cifti2.cifti2image property)": [[77, "nibabel.cifti2.cifti2.Cifti2Image.nifti_header", false]], "niftiextension (class in nibabel.nifti1)": [[103, "nibabel.nifti1.NiftiExtension", false]], "none_or_close() (in module nibabel.nicom.dicomwrappers)": [[102, "nibabel.nicom.dicomwrappers.none_or_close", false]], "norm() (in module nibabel.quaternions)": [[113, "nibabel.quaternions.norm", false]], "novalue (class in nibabel.nicom.ascconv)": [[102, "nibabel.nicom.ascconv.NoValue", false]], "nt_str() (in module nibabel.nicom.csareader)": [[102, "nibabel.nicom.csareader.nt_str", false]], "num_dim (nibabel.gifti.gifti.giftidataarray property)": [[94, "nibabel.gifti.gifti.GiftiDataArray.num_dim", false]], "number_of_mapped_indices (nibabel.cifti2.cifti2.cifti2header property)": [[77, "nibabel.cifti2.cifti2.Cifti2Header.number_of_mapped_indices", false]], "numda (nibabel.gifti.gifti.giftiimage property)": [[94, "nibabel.gifti.gifti.GiftiImage.numDA", false]], "obj_from_atoms() (in module nibabel.nicom.ascconv)": [[102, "nibabel.nicom.ascconv.obj_from_atoms", false]], "obliquity() (in module nibabel.affines)": [[68, "nibabel.affines.obliquity", false]], "offset (nibabel.arrayproxy.arrayproxy property)": [[70, "nibabel.arrayproxy.ArrayProxy.offset", false]], "ok_floats() (in module nibabel.casting)": [[76, "nibabel.casting.ok_floats", false]], "on_powerpc() (in module nibabel.casting)": [[76, "nibabel.casting.on_powerpc", false]], "one_line() (in module nibabel.parrec)": [[109, "nibabel.parrec.one_line", false]], "open() (nibabel.cmdline.dicomfs.dicomfs method)": [[78, "nibabel.cmdline.dicomfs.DICOMFS.open", false]], "opener (class in nibabel.openers)": [[106, "nibabel.openers.Opener", false]], "optimize_read_slicers() (in module nibabel.fileslice)": [[90, "nibabel.fileslice.optimize_read_slicers", false]], "optimize_slicer() (in module nibabel.fileslice)": [[90, "nibabel.fileslice.optimize_slicer", false]], "optional_package() (in module nibabel.optpkg)": [[107, "nibabel.optpkg.optional_package", false]], "orientationerror (class in nibabel.orientations)": [[108, "nibabel.orientations.OrientationError", false]], "origin (nibabel.streamlines.header.field attribute)": [[119, "nibabel.streamlines.header.Field.ORIGIN", false]], "ornt2axcodes() (in module nibabel.orientations)": [[108, "nibabel.orientations.ornt2axcodes", false]], "ornt_transform() (in module nibabel.orientations)": [[108, "nibabel.orientations.ornt_transform", false]], "orthoslicer3d (class in nibabel.viewers)": [[122, "nibabel.viewers.OrthoSlicer3D", false]], "orthoview() (nibabel.spatialimages.spatialimage method)": [[116, "nibabel.spatialimages.SpatialImage.orthoview", false]], "out_dtype (nibabel.arraywriters.arraywriter property)": [[71, "nibabel.arraywriters.ArrayWriter.out_dtype", false]], "pair_magic (nibabel.nifti1.nifti1header attribute)": [[103, "nibabel.nifti1.Nifti1Header.pair_magic", false]], "pair_magic (nibabel.nifti2.nifti2header attribute)": [[104, "nibabel.nifti2.Nifti2Header.pair_magic", false]], "pair_vox_offset (nibabel.nifti1.nifti1header attribute)": [[103, "nibabel.nifti1.Nifti1Header.pair_vox_offset", false]], "pair_vox_offset (nibabel.nifti2.nifti2header attribute)": [[104, "nibabel.nifti2.Nifti2Header.pair_vox_offset", false]], "parcels (nibabel.cifti2.cifti2.cifti2matrixindicesmap property)": [[77, "nibabel.cifti2.cifti2.Cifti2MatrixIndicesMap.parcels", false]], "parcelsaxis (class in nibabel.cifti2.cifti2_axes)": [[77, "nibabel.cifti2.cifti2_axes.ParcelsAxis", false]], "parrecarrayproxy (class in nibabel.parrec)": [[109, "nibabel.parrec.PARRECArrayProxy", false]], "parrecerror (class in nibabel.parrec)": [[109, "nibabel.parrec.PARRECError", false]], "parrecheader (class in nibabel.parrec)": [[109, "nibabel.parrec.PARRECHeader", false]], "parrecimage (class in nibabel.parrec)": [[109, "nibabel.parrec.PARRECImage", false]], "parse() (nibabel.xmlutils.xmlparser method)": [[125, "nibabel.xmlutils.XmlParser.parse", false]], "parse_afni_header() (in module nibabel.brikhead)": [[74, "nibabel.brikhead.parse_AFNI_header", false]], "parse_args() (in module nibabel.cmdline.tck2trk)": [[78, "nibabel.cmdline.tck2trk.parse_args", false]], "parse_args() (in module nibabel.cmdline.trk2tck)": [[78, "nibabel.cmdline.trk2tck.parse_args", false]], "parse_ascconv() (in module nibabel.nicom.ascconv)": [[102, "nibabel.nicom.ascconv.parse_ascconv", false]], "parse_filename() (in module nibabel.filename_parser)": [[89, "nibabel.filename_parser.parse_filename", false]], "parse_par_header() (in module nibabel.parrec)": [[109, "nibabel.parrec.parse_PAR_header", false]], "parse_slice() (in module nibabel.cmdline.roi)": [[78, "nibabel.cmdline.roi.parse_slice", false]], "parser (nibabel.gifti.gifti.giftiimage attribute)": [[94, "nibabel.gifti.gifti.GiftiImage.parser", false]], "path_maybe_image() (nibabel.filebasedimages.filebasedimage class method)": [[87, "nibabel.filebasedimages.FileBasedImage.path_maybe_image", false]], "peek_next() (in module nibabel.streamlines.utils)": [[119, "nibabel.streamlines.utils.peek_next", false]], "pending_data (nibabel.cifti2.parse_cifti2.cifti2parser property)": [[77, "nibabel.cifti2.parse_cifti2.Cifti2Parser.pending_data", false]], "pending_data (nibabel.gifti.parse_gifti_fast.giftiimageparser property)": [[94, "nibabel.gifti.parse_gifti_fast.GiftiImageParser.pending_data", false]], "perarraydict (class in nibabel.streamlines.tractogram)": [[119, "nibabel.streamlines.tractogram.PerArrayDict", false]], "perarraysequencedict (class in nibabel.streamlines.tractogram)": [[119, "nibabel.streamlines.tractogram.PerArraySequenceDict", false]], "philips (nibabel.nicom.utils.vendor attribute)": [[102, "nibabel.nicom.utils.Vendor.PHILIPS", false]], "pointset (class in nibabel.pointset)": [[110, "nibabel.pointset.Pointset", false]], "pop_cifti2_vertices() (nibabel.cifti2.cifti2.cifti2parcel method)": [[77, "nibabel.cifti2.cifti2.Cifti2Parcel.pop_cifti2_vertices", false]], "position (nibabel.viewers.orthoslicer3d property)": [[122, "nibabel.viewers.OrthoSlicer3D.position", false]], "predict_shape() (in module nibabel.fileslice)": [[90, "nibabel.fileslice.predict_shape", false]], "pretty_mapping() (in module nibabel.volumeutils)": [[123, "nibabel.volumeutils.pretty_mapping", false]], "print_git_title() (in module nibabel.benchmarks.butils)": [[73, "nibabel.benchmarks.butils.print_git_title", false]], "print_summary() (nibabel.gifti.gifti.gifticoordsystem method)": [[94, "nibabel.gifti.gifti.GiftiCoordSystem.print_summary", false]], "print_summary() (nibabel.gifti.gifti.giftidataarray method)": [[94, "nibabel.gifti.gifti.GiftiDataArray.print_summary", false]], "print_summary() (nibabel.gifti.gifti.giftiimage method)": [[94, "nibabel.gifti.gifti.GiftiImage.print_summary", false]], "print_summary() (nibabel.gifti.gifti.giftilabeltable method)": [[94, "nibabel.gifti.gifti.GiftiLabelTable.print_summary", false]], "print_summary() (nibabel.gifti.gifti.giftimetadata method)": [[94, "nibabel.gifti.gifti.GiftiMetaData.print_summary", false]], "proc_file() (in module nibabel.cmdline.ls)": [[78, "nibabel.cmdline.ls.proc_file", false]], "proc_file() (in module nibabel.cmdline.parrec2nii)": [[78, "nibabel.cmdline.parrec2nii.proc_file", false]], "q2bg() (in module nibabel.nicom.dwiparams)": [[102, "nibabel.nicom.dwiparams.q2bg", false]], "q_vector (nibabel.nicom.dicomwrappers.siemenswrapper property)": [[102, "nibabel.nicom.dicomwrappers.SiemensWrapper.q_vector", false]], "q_vector (nibabel.nicom.dicomwrappers.wrapper attribute)": [[102, "nibabel.nicom.dicomwrappers.Wrapper.q_vector", false]], "quat2angle_axis() (in module nibabel.quaternions)": [[113, "nibabel.quaternions.quat2angle_axis", false]], "quat2euler() (in module nibabel.eulerangles)": [[86, "nibabel.eulerangles.quat2euler", false]], "quat2mat() (in module nibabel.quaternions)": [[113, "nibabel.quaternions.quat2mat", false]], "quaternion_threshold (nibabel.nifti1.nifti1header attribute)": [[103, "nibabel.nifti1.Nifti1Header.quaternion_threshold", false]], "quaternion_threshold (nibabel.nifti2.nifti2header attribute)": [[104, "nibabel.nifti2.Nifti2Header.quaternion_threshold", false]], "raw_data_from_fileobj() (nibabel.analyze.analyzeheader method)": [[69, "nibabel.analyze.AnalyzeHeader.raw_data_from_fileobj", false]], "raw_data_from_fileobj() (nibabel.ecat.ecatsubheader method)": [[84, "nibabel.ecat.EcatSubHeader.raw_data_from_fileobj", false]], "read() (in module nibabel.nicom.csareader)": [[102, "nibabel.nicom.csareader.read", false]], "read() (nibabel.cmdline.dicomfs.dicomfs method)": [[78, "nibabel.cmdline.dicomfs.DICOMFS.read", false]], "read() (nibabel.nicom.structreader.unpacker method)": [[102, "nibabel.nicom.structreader.Unpacker.read", false]], "read() (nibabel.openers.fileish method)": [[106, "nibabel.openers.Fileish.read", false]], "read() (nibabel.openers.opener method)": [[106, "nibabel.openers.Opener.read", false]], "read_annot() (in module nibabel.freesurfer.io)": [[92, "nibabel.freesurfer.io.read_annot", false]], "read_data_block() (in module nibabel.gifti.parse_gifti_fast)": [[94, "nibabel.gifti.parse_gifti_fast.read_data_block", false]], "read_geometry() (in module nibabel.freesurfer.io)": [[92, "nibabel.freesurfer.io.read_geometry", false]], "read_img_data() (in module nibabel.loadsave)": [[98, "nibabel.loadsave.read_img_data", false]], "read_label() (in module nibabel.freesurfer.io)": [[92, "nibabel.freesurfer.io.read_label", false]], "read_mlist() (in module nibabel.ecat)": [[84, "nibabel.ecat.read_mlist", false]], "read_morph_data() (in module nibabel.freesurfer.io)": [[92, "nibabel.freesurfer.io.read_morph_data", false]], "read_mosaic_dir() (in module nibabel.nicom.dicomreaders)": [[102, "nibabel.nicom.dicomreaders.read_mosaic_dir", false]], "read_mosaic_dwi_dir() (in module nibabel.nicom.dicomreaders)": [[102, "nibabel.nicom.dicomreaders.read_mosaic_dwi_dir", false]], "read_segments() (in module nibabel.fileslice)": [[90, "nibabel.fileslice.read_segments", false]], "read_subheaders() (in module nibabel.ecat)": [[84, "nibabel.ecat.read_subheaders", false]], "read_zt_byte_strings() (in module nibabel.fileutils)": [[91, "nibabel.fileutils.read_zt_byte_strings", false]], "readdir() (nibabel.cmdline.dicomfs.dicomfs method)": [[78, "nibabel.cmdline.dicomfs.DICOMFS.readdir", false]], "readinto() (nibabel.openers.opener method)": [[106, "nibabel.openers.Opener.readinto", false]], "rec2dict() (in module nibabel.volumeutils)": [[123, "nibabel.volumeutils.rec2dict", false]], "recoder (class in nibabel.volumeutils)": [[123, "nibabel.volumeutils.Recoder", false]], "release() (nibabel.cmdline.dicomfs.dicomfs method)": [[78, "nibabel.cmdline.dicomfs.DICOMFS.release", false]], "remove_gifti_data_array() (nibabel.gifti.gifti.giftiimage method)": [[94, "nibabel.gifti.gifti.GiftiImage.remove_gifti_data_array", false]], "remove_gifti_data_array_by_intent() (nibabel.gifti.gifti.giftiimage method)": [[94, "nibabel.gifti.gifti.GiftiImage.remove_gifti_data_array_by_intent", false]], "report (class in nibabel.batteryrunners)": [[72, "nibabel.batteryrunners.Report", false]], "resample_from_to() (in module nibabel.processing)": [[111, "nibabel.processing.resample_from_to", false]], "resample_to_output() (in module nibabel.processing)": [[111, "nibabel.processing.resample_to_output", false]], "rescale_affine() (in module nibabel.affines)": [[68, "nibabel.affines.rescale_affine", false]], "reset() (nibabel.arraywriters.slopearraywriter method)": [[71, "nibabel.arraywriters.SlopeArrayWriter.reset", false]], "reset() (nibabel.arraywriters.slopeinterarraywriter method)": [[71, "nibabel.arraywriters.SlopeInterArrayWriter.reset", false]], "reset() (nibabel.onetime.resetmixin method)": [[105, "nibabel.onetime.ResetMixin.reset", false]], "resetmixin (class in nibabel.onetime)": [[105, "nibabel.onetime.ResetMixin", false]], "reshape() (nibabel.arrayproxy.arrayproxy method)": [[70, "nibabel.arrayproxy.ArrayProxy.reshape", false]], "reshape_dataobj() (in module nibabel.arrayproxy)": [[70, "nibabel.arrayproxy.reshape_dataobj", false]], "rgba (nibabel.cifti2.cifti2.cifti2label property)": [[77, "nibabel.cifti2.cifti2.Cifti2Label.rgba", false]], "rgba (nibabel.gifti.gifti.giftilabel property)": [[94, "nibabel.gifti.gifti.GiftiLabel.rgba", false]], "rotate_vector() (in module nibabel.quaternions)": [[113, "nibabel.quaternions.rotate_vector", false]], "rotation_matrix (nibabel.nicom.dicomwrappers.wrapper property)": [[102, "nibabel.nicom.dicomwrappers.Wrapper.rotation_matrix", false]], "rst_table() (in module nibabel.rstutils)": [[114, "nibabel.rstutils.rst_table", false]], "run_slices() (in module nibabel.benchmarks.bench_fileslice)": [[73, "nibabel.benchmarks.bench_fileslice.run_slices", false]], "rw (nibabel.analyze.analyzeimage attribute)": [[69, "nibabel.analyze.AnalyzeImage.rw", false]], "rw (nibabel.brikhead.afniimage attribute)": [[74, "nibabel.brikhead.AFNIImage.rw", false]], "rw (nibabel.cifti2.cifti2.cifti2image attribute)": [[77, "nibabel.cifti2.cifti2.Cifti2Image.rw", false]], "rw (nibabel.filebasedimages.filebasedimage attribute)": [[87, "nibabel.filebasedimages.FileBasedImage.rw", false]], "rw (nibabel.freesurfer.mghformat.mghimage attribute)": [[92, "nibabel.freesurfer.mghformat.MGHImage.rw", false]], "rw (nibabel.minc1.minc1image attribute)": [[99, "nibabel.minc1.Minc1Image.rw", false]], "rw (nibabel.nifti1.nifti1pair attribute)": [[103, "nibabel.nifti1.Nifti1Pair.rw", false]], "rw (nibabel.parrec.parrecimage attribute)": [[109, "nibabel.parrec.PARRECImage.rw", false]], "rw (nibabel.spm99analyze.spm99analyzeimage attribute)": [[118, "nibabel.spm99analyze.Spm99AnalyzeImage.rw", false]], "safe_get() (in module nibabel.cmdline.utils)": [[78, "nibabel.cmdline.utils.safe_get", false]], "same_file_as() (nibabel.fileholders.fileholder method)": [[88, "nibabel.fileholders.FileHolder.same_file_as", false]], "sanitize() (in module nibabel.cmdline.roi)": [[78, "nibabel.cmdline.roi.sanitize", false]], "save() (in module nibabel.loadsave)": [[98, "nibabel.loadsave.save", false]], "save() (in module nibabel.nifti1)": [[103, "nibabel.nifti1.save", false]], "save() (in module nibabel.nifti2)": [[104, "nibabel.nifti2.save", false]], "save() (in module nibabel.streamlines)": [[119, "nibabel.streamlines.save", false]], "save() (nibabel.streamlines.array_sequence.arraysequence method)": [[119, "nibabel.streamlines.array_sequence.ArraySequence.save", false]], "save() (nibabel.streamlines.tck.tckfile method)": [[119, "nibabel.streamlines.tck.TckFile.save", false]], "save() (nibabel.streamlines.tractogram_file.tractogramfile method)": [[119, "nibabel.streamlines.tractogram_file.TractogramFile.save", false]], "save() (nibabel.streamlines.trk.trkfile method)": [[119, "nibabel.streamlines.trk.TrkFile.save", false]], "scalaraxis (class in nibabel.cifti2.cifti2_axes)": [[77, "nibabel.cifti2.cifti2_axes.ScalarAxis", false]], "scale_factors (nibabel.nicom.dicomwrappers.multiframewrapper property)": [[102, "nibabel.nicom.dicomwrappers.MultiframeWrapper.scale_factors", false]], "scale_factors (nibabel.nicom.dicomwrappers.wrapper property)": [[102, "nibabel.nicom.dicomwrappers.Wrapper.scale_factors", false]], "scaling (nibabel.brikhead.afniarrayproxy property)": [[74, "nibabel.brikhead.AFNIArrayProxy.scaling", false]], "scaling_needed() (nibabel.arraywriters.arraywriter method)": [[71, "nibabel.arraywriters.ArrayWriter.scaling_needed", false]], "scaling_needed() (nibabel.arraywriters.slopearraywriter method)": [[71, "nibabel.arraywriters.SlopeArrayWriter.scaling_needed", false]], "scalingerror (class in nibabel.arraywriters)": [[71, "nibabel.arraywriters.ScalingError", false]], "seek() (nibabel.openers.opener method)": [[106, "nibabel.openers.Opener.seek", false]], "seek_tell() (in module nibabel.volumeutils)": [[123, "nibabel.volumeutils.seek_tell", false]], "serializableimage (class in nibabel.filebasedimages)": [[87, "nibabel.filebasedimages.SerializableImage", false]], "series_signature (nibabel.nicom.dicomwrappers.multiframewrapper property)": [[102, "nibabel.nicom.dicomwrappers.MultiframeWrapper.series_signature", false]], "series_signature (nibabel.nicom.dicomwrappers.siemenswrapper property)": [[102, "nibabel.nicom.dicomwrappers.SiemensWrapper.series_signature", false]], "series_signature (nibabel.nicom.dicomwrappers.wrapper property)": [[102, "nibabel.nicom.dicomwrappers.Wrapper.series_signature", false]], "seriesaxis (class in nibabel.cifti2.cifti2_axes)": [[77, "nibabel.cifti2.cifti2_axes.SeriesAxis", false]], "set_data_dtype() (nibabel.analyze.analyzeheader method)": [[69, "nibabel.analyze.AnalyzeHeader.set_data_dtype", false]], "set_data_dtype() (nibabel.analyze.analyzeimage method)": [[69, "nibabel.analyze.AnalyzeImage.set_data_dtype", false]], "set_data_dtype() (nibabel.cifti2.cifti2.cifti2image method)": [[77, "nibabel.cifti2.cifti2.Cifti2Image.set_data_dtype", false]], "set_data_dtype() (nibabel.freesurfer.mghformat.mghheader method)": [[92, "nibabel.freesurfer.mghformat.MGHHeader.set_data_dtype", false]], "set_data_dtype() (nibabel.nifti1.nifti1header method)": [[103, "nibabel.nifti1.Nifti1Header.set_data_dtype", false]], "set_data_dtype() (nibabel.nifti1.nifti1pair method)": [[103, "nibabel.nifti1.Nifti1Pair.set_data_dtype", false]], "set_data_dtype() (nibabel.spatialimages.hasdtype method)": [[116, "nibabel.spatialimages.HasDtype.set_data_dtype", false]], "set_data_dtype() (nibabel.spatialimages.spatialheader method)": [[116, "nibabel.spatialimages.SpatialHeader.set_data_dtype", false]], "set_data_dtype() (nibabel.spatialimages.spatialimage method)": [[116, "nibabel.spatialimages.SpatialImage.set_data_dtype", false]], "set_data_offset() (nibabel.analyze.analyzeheader method)": [[69, "nibabel.analyze.AnalyzeHeader.set_data_offset", false]], "set_data_offset() (nibabel.parrec.parrecheader method)": [[109, "nibabel.parrec.PARRECHeader.set_data_offset", false]], "set_data_shape() (nibabel.analyze.analyzeheader method)": [[69, "nibabel.analyze.AnalyzeHeader.set_data_shape", false]], "set_data_shape() (nibabel.freesurfer.mghformat.mghheader method)": [[92, "nibabel.freesurfer.mghformat.MGHHeader.set_data_shape", false]], "set_data_shape() (nibabel.nifti1.nifti1header method)": [[103, "nibabel.nifti1.Nifti1Header.set_data_shape", false]], "set_data_shape() (nibabel.nifti2.nifti2header method)": [[104, "nibabel.nifti2.Nifti2Header.set_data_shape", false]], "set_data_shape() (nibabel.spatialimages.spatialheader method)": [[116, "nibabel.spatialimages.SpatialHeader.set_data_shape", false]], "set_dim_info() (nibabel.nifti1.nifti1header method)": [[103, "nibabel.nifti1.Nifti1Header.set_dim_info", false]], "set_filename() (nibabel.filebasedimages.filebasedimage method)": [[87, "nibabel.filebasedimages.FileBasedImage.set_filename", false]], "set_intent() (nibabel.nifti1.nifti1header method)": [[103, "nibabel.nifti1.Nifti1Header.set_intent", false]], "set_origin_from_affine() (nibabel.spm99analyze.spm99analyzeheader method)": [[118, "nibabel.spm99analyze.Spm99AnalyzeHeader.set_origin_from_affine", false]], "set_position() (nibabel.viewers.orthoslicer3d method)": [[122, "nibabel.viewers.OrthoSlicer3D.set_position", false]], "set_qform() (nibabel.nifti1.nifti1header method)": [[103, "nibabel.nifti1.Nifti1Header.set_qform", false]], "set_qform() (nibabel.nifti1.nifti1pair method)": [[103, "nibabel.nifti1.Nifti1Pair.set_qform", false]], "set_sform() (nibabel.nifti1.nifti1header method)": [[103, "nibabel.nifti1.Nifti1Header.set_sform", false]], "set_sform() (nibabel.nifti1.nifti1pair method)": [[103, "nibabel.nifti1.Nifti1Pair.set_sform", false]], "set_slice_duration() (nibabel.nifti1.nifti1header method)": [[103, "nibabel.nifti1.Nifti1Header.set_slice_duration", false]], "set_slice_times() (nibabel.nifti1.nifti1header method)": [[103, "nibabel.nifti1.Nifti1Header.set_slice_times", false]], "set_slope_inter() (nibabel.analyze.analyzeheader method)": [[69, "nibabel.analyze.AnalyzeHeader.set_slope_inter", false]], "set_slope_inter() (nibabel.nifti1.nifti1header method)": [[103, "nibabel.nifti1.Nifti1Header.set_slope_inter", false]], "set_slope_inter() (nibabel.spm99analyze.spmanalyzeheader method)": [[118, "nibabel.spm99analyze.SpmAnalyzeHeader.set_slope_inter", false]], "set_volume_idx() (nibabel.viewers.orthoslicer3d method)": [[122, "nibabel.viewers.OrthoSlicer3D.set_volume_idx", false]], "set_xyzt_units() (nibabel.nifti1.nifti1header method)": [[103, "nibabel.nifti1.Nifti1Header.set_xyzt_units", false]], "set_zooms() (nibabel.analyze.analyzeheader method)": [[69, "nibabel.analyze.AnalyzeHeader.set_zooms", false]], "set_zooms() (nibabel.freesurfer.mghformat.mghheader method)": [[92, "nibabel.freesurfer.mghformat.MGHHeader.set_zooms", false]], "set_zooms() (nibabel.spatialimages.spatialheader method)": [[116, "nibabel.spatialimages.SpatialHeader.set_zooms", false]], "shape (nibabel.arrayproxy.arraylike attribute)": [[70, "nibabel.arrayproxy.ArrayLike.shape", false]], "shape (nibabel.arrayproxy.arrayproxy property)": [[70, "nibabel.arrayproxy.ArrayProxy.shape", false]], "shape (nibabel.dataobj_images.dataobjimage property)": [[80, "nibabel.dataobj_images.DataobjImage.shape", false]], "shape (nibabel.ecat.ecatimage property)": [[84, "nibabel.ecat.EcatImage.shape", false]], "shape (nibabel.ecat.ecatimagearrayproxy property)": [[84, "nibabel.ecat.EcatImageArrayProxy.shape", false]], "shape (nibabel.minc1.mincimagearrayproxy property)": [[99, "nibabel.minc1.MincImageArrayProxy.shape", false]], "shape (nibabel.parrec.parrecarrayproxy property)": [[109, "nibabel.parrec.PARRECArrayProxy.shape", false]], "shape (nibabel.pointset.coordinatearray attribute)": [[110, "nibabel.pointset.CoordinateArray.shape", false]], "shape (nibabel.pointset.gridindices attribute)": [[110, "nibabel.pointset.GridIndices.shape", false]], "shape_zoom_affine() (in module nibabel.volumeutils)": [[123, "nibabel.volumeutils.shape_zoom_affine", false]], "shared_range() (in module nibabel.casting)": [[76, "nibabel.casting.shared_range", false]], "show() (nibabel.viewers.orthoslicer3d method)": [[122, "nibabel.viewers.OrthoSlicer3D.show", false]], "shrink_data() (nibabel.streamlines.array_sequence.arraysequence method)": [[119, "nibabel.streamlines.array_sequence.ArraySequence.shrink_data", false]], "siemens (nibabel.nicom.utils.vendor attribute)": [[102, "nibabel.nicom.utils.Vendor.SIEMENS", false]], "siemenswrapper (class in nibabel.nicom.dicomwrappers)": [[102, "nibabel.nicom.dicomwrappers.SiemensWrapper", false]], "sigma2fwhm() (in module nibabel.processing)": [[111, "nibabel.processing.sigma2fwhm", false]], "single_magic (nibabel.nifti1.nifti1header attribute)": [[103, "nibabel.nifti1.Nifti1Header.single_magic", false]], "single_magic (nibabel.nifti2.nifti2header attribute)": [[104, "nibabel.nifti2.Nifti2Header.single_magic", false]], "single_vox_offset (nibabel.nifti1.nifti1header attribute)": [[103, "nibabel.nifti1.Nifti1Header.single_vox_offset", false]], "single_vox_offset (nibabel.nifti2.nifti2header attribute)": [[104, "nibabel.nifti2.Nifti2Header.single_vox_offset", false]], "size (nibabel.cifti2.cifti2_axes.axis property)": [[77, "nibabel.cifti2.cifti2_axes.Axis.size", false]], "size (nibabel.cifti2.cifti2_axes.seriesaxis attribute)": [[77, "nibabel.cifti2.cifti2_axes.SeriesAxis.size", false]], "sizeof_hdr (nibabel.analyze.analyzeheader attribute)": [[69, "nibabel.analyze.AnalyzeHeader.sizeof_hdr", false]], "sizeof_hdr (nibabel.nifti2.nifti2header attribute)": [[104, "nibabel.nifti2.Nifti2Header.sizeof_hdr", false]], "slice2len() (in module nibabel.fileslice)": [[90, "nibabel.fileslice.slice2len", false]], "slice2outax() (in module nibabel.fileslice)": [[90, "nibabel.fileslice.slice2outax", false]], "slice2volume() (in module nibabel.spaces)": [[115, "nibabel.spaces.slice2volume", false]], "slice_affine() (nibabel.spatialimages.spatialfirstslicer method)": [[116, "nibabel.spatialimages.SpatialFirstSlicer.slice_affine", false]], "slice_indicator (nibabel.nicom.dicomwrappers.wrapper property)": [[102, "nibabel.nicom.dicomwrappers.Wrapper.slice_indicator", false]], "slice_normal (nibabel.nicom.dicomwrappers.siemenswrapper property)": [[102, "nibabel.nicom.dicomwrappers.SiemensWrapper.slice_normal", false]], "slice_normal (nibabel.nicom.dicomwrappers.wrapper property)": [[102, "nibabel.nicom.dicomwrappers.Wrapper.slice_normal", false]], "sliceabledatadict (class in nibabel.streamlines.tractogram)": [[119, "nibabel.streamlines.tractogram.SliceableDataDict", false]], "slicer (nibabel.spatialimages.spatialimage property)": [[116, "nibabel.spatialimages.SpatialImage.slicer", false]], "slicers2segments() (in module nibabel.fileslice)": [[90, "nibabel.fileslice.slicers2segments", false]], "slices_to_series() (in module nibabel.nicom.dicomreaders)": [[102, "nibabel.nicom.dicomreaders.slices_to_series", false]], "slope (nibabel.arrayproxy.arrayproxy property)": [[70, "nibabel.arrayproxy.ArrayProxy.slope", false]], "slope (nibabel.arraywriters.slopearraywriter property)": [[71, "nibabel.arraywriters.SlopeArrayWriter.slope", false]], "slopearraywriter (class in nibabel.arraywriters)": [[71, "nibabel.arraywriters.SlopeArrayWriter", false]], "slopeinterarraywriter (class in nibabel.arraywriters)": [[71, "nibabel.arraywriters.SlopeInterArrayWriter", false]], "smooth_image() (in module nibabel.processing)": [[111, "nibabel.processing.smooth_image", false]], "spatial_axes_first() (in module nibabel.imageclasses)": [[95, "nibabel.imageclasses.spatial_axes_first", false]], "spatialfirstslicer (class in nibabel.spatialimages)": [[116, "nibabel.spatialimages.SpatialFirstSlicer", false]], "spatialheader (class in nibabel.spatialimages)": [[116, "nibabel.spatialimages.SpatialHeader", false]], "spatialimage (class in nibabel.spatialimages)": [[116, "nibabel.spatialimages.SpatialImage", false]], "spatialprotocol (class in nibabel.spatialimages)": [[116, "nibabel.spatialimages.SpatialProtocol", false]], "splitext_addext() (in module nibabel.filename_parser)": [[89, "nibabel.filename_parser.splitext_addext", false]], "spm2analyzeheader (class in nibabel.spm2analyze)": [[117, "nibabel.spm2analyze.Spm2AnalyzeHeader", false]], "spm2analyzeimage (class in nibabel.spm2analyze)": [[117, "nibabel.spm2analyze.Spm2AnalyzeImage", false]], "spm99analyzeheader (class in nibabel.spm99analyze)": [[118, "nibabel.spm99analyze.Spm99AnalyzeHeader", false]], "spm99analyzeimage (class in nibabel.spm99analyze)": [[118, "nibabel.spm99analyze.Spm99AnalyzeImage", false]], "spmanalyzeheader (class in nibabel.spm99analyze)": [[118, "nibabel.spm99analyze.SpmAnalyzeHeader", false]], "squeeze_image() (in module nibabel.funcs)": [[93, "nibabel.funcs.squeeze_image", false]], "startelementhandler() (nibabel.cifti2.parse_cifti2.cifti2parser method)": [[77, "nibabel.cifti2.parse_cifti2.Cifti2Parser.StartElementHandler", false]], "startelementhandler() (nibabel.gifti.parse_gifti_fast.giftiimageparser method)": [[94, "nibabel.gifti.parse_gifti_fast.GiftiImageParser.StartElementHandler", false]], "startelementhandler() (nibabel.xmlutils.xmlparser method)": [[125, "nibabel.xmlutils.XmlParser.StartElementHandler", false]], "step_size (nibabel.streamlines.header.field attribute)": [[119, "nibabel.streamlines.header.Field.STEP_SIZE", false]], "streamlines (nibabel.streamlines.tractogram.lazytractogram property)": [[119, "nibabel.streamlines.tractogram.LazyTractogram.streamlines", false]], "streamlines (nibabel.streamlines.tractogram.tractogram property)": [[119, "nibabel.streamlines.tractogram.Tractogram.streamlines", false]], "streamlines (nibabel.streamlines.tractogram_file.tractogramfile property)": [[119, "nibabel.streamlines.tractogram_file.TractogramFile.streamlines", false]], "strided_scalar() (in module nibabel.fileslice)": [[90, "nibabel.fileslice.strided_scalar", false]], "structarr (nibabel.wrapstruct.wrapstruct property)": [[124, "nibabel.wrapstruct.WrapStruct.structarr", false]], "subheader_class (nibabel.ecat.ecatimage attribute)": [[84, "nibabel.ecat.EcatImage.subheader_class", false]], "supported_np_types() (in module nibabel.spatialimages)": [[116, "nibabel.spatialimages.supported_np_types", false]], "supports_data_per_point (nibabel.streamlines.tck.tckfile attribute)": [[119, "nibabel.streamlines.tck.TckFile.SUPPORTS_DATA_PER_POINT", false]], "supports_data_per_point (nibabel.streamlines.trk.trkfile attribute)": [[119, "nibabel.streamlines.trk.TrkFile.SUPPORTS_DATA_PER_POINT", false]], "supports_data_per_streamline (nibabel.streamlines.tck.tckfile attribute)": [[119, "nibabel.streamlines.tck.TckFile.SUPPORTS_DATA_PER_STREAMLINE", false]], "supports_data_per_streamline (nibabel.streamlines.trk.trkfile attribute)": [[119, "nibabel.streamlines.trk.TrkFile.SUPPORTS_DATA_PER_STREAMLINE", false]], "surface_mask (nibabel.cifti2.cifti2_axes.brainmodelaxis property)": [[77, "nibabel.cifti2.cifti2_axes.BrainModelAxis.surface_mask", false]], "surfaces (nibabel.cifti2.cifti2.cifti2matrixindicesmap property)": [[77, "nibabel.cifti2.cifti2.Cifti2MatrixIndicesMap.surfaces", false]], "table2string() (in module nibabel.cmdline.utils)": [[78, "nibabel.cmdline.utils.table2string", false]], "tckfile (class in nibabel.streamlines.tck)": [[119, "nibabel.streamlines.tck.TckFile", false]], "tell() (nibabel.openers.opener method)": [[106, "nibabel.openers.Opener.tell", false]], "template_dtype (nibabel.analyze.analyzeheader attribute)": [[69, "nibabel.analyze.AnalyzeHeader.template_dtype", false]], "template_dtype (nibabel.ecat.ecatheader attribute)": [[84, "nibabel.ecat.EcatHeader.template_dtype", false]], "template_dtype (nibabel.freesurfer.mghformat.mghheader attribute)": [[92, "nibabel.freesurfer.mghformat.MGHHeader.template_dtype", false]], "template_dtype (nibabel.nifti1.nifti1header attribute)": [[103, "nibabel.nifti1.Nifti1Header.template_dtype", false]], "template_dtype (nibabel.nifti2.nifti2header attribute)": [[104, "nibabel.nifti2.Nifti2Header.template_dtype", false]], "template_dtype (nibabel.spm2analyze.spm2analyzeheader attribute)": [[117, "nibabel.spm2analyze.Spm2AnalyzeHeader.template_dtype", false]], "template_dtype (nibabel.spm99analyze.spmanalyzeheader attribute)": [[118, "nibabel.spm99analyze.SpmAnalyzeHeader.template_dtype", false]], "template_dtype (nibabel.wrapstruct.wrapstruct attribute)": [[124, "nibabel.wrapstruct.WrapStruct.template_dtype", false]], "temporarydirectory (class in nibabel.tmpdirs)": [[120, "nibabel.tmpdirs.TemporaryDirectory", false]], "test() (in module nibabel)": [[66, "nibabel.test", false]], "text (nibabel.nifti1.niftiextension property)": [[103, "nibabel.nifti1.NiftiExtension.text", false]], "threshold_heuristic() (in module nibabel.fileslice)": [[90, "nibabel.fileslice.threshold_heuristic", false]], "time (nibabel.cifti2.cifti2_axes.seriesaxis property)": [[77, "nibabel.cifti2.cifti2_axes.SeriesAxis.time", false]], "to_bytes() (nibabel.filebasedimages.serializableimage method)": [[87, "nibabel.filebasedimages.SerializableImage.to_bytes", false]], "to_bytes() (nibabel.gifti.gifti.giftiimage method)": [[94, "nibabel.gifti.gifti.GiftiImage.to_bytes", false]], "to_cifti_brain_structure_name() (nibabel.cifti2.cifti2_axes.brainmodelaxis static method)": [[77, "nibabel.cifti2.cifti2_axes.BrainModelAxis.to_cifti_brain_structure_name", false]], "to_file_map() (nibabel.analyze.analyzeimage method)": [[69, "nibabel.analyze.AnalyzeImage.to_file_map", false]], "to_file_map() (nibabel.cifti2.cifti2.cifti2image method)": [[77, "nibabel.cifti2.cifti2.Cifti2Image.to_file_map", false]], "to_file_map() (nibabel.ecat.ecatimage method)": [[84, "nibabel.ecat.EcatImage.to_file_map", false]], "to_file_map() (nibabel.filebasedimages.filebasedimage method)": [[87, "nibabel.filebasedimages.FileBasedImage.to_file_map", false]], "to_file_map() (nibabel.freesurfer.mghformat.mghimage method)": [[92, "nibabel.freesurfer.mghformat.MGHImage.to_file_map", false]], "to_file_map() (nibabel.gifti.gifti.giftiimage method)": [[94, "nibabel.gifti.gifti.GiftiImage.to_file_map", false]], "to_file_map() (nibabel.nifti1.nifti1pair method)": [[103, "nibabel.nifti1.Nifti1Pair.to_file_map", false]], "to_file_map() (nibabel.spm99analyze.spm99analyzeimage method)": [[118, "nibabel.spm99analyze.Spm99AnalyzeImage.to_file_map", false]], "to_filename() (nibabel.filebasedimages.filebasedimage method)": [[87, "nibabel.filebasedimages.FileBasedImage.to_filename", false]], "to_fileobj() (nibabel.arraywriters.arraywriter method)": [[71, "nibabel.arraywriters.ArrayWriter.to_fileobj", false]], "to_fileobj() (nibabel.arraywriters.slopearraywriter method)": [[71, "nibabel.arraywriters.SlopeArrayWriter.to_fileobj", false]], "to_fileobj() (nibabel.arraywriters.slopeinterarraywriter method)": [[71, "nibabel.arraywriters.SlopeInterArrayWriter.to_fileobj", false]], "to_header() (in module nibabel.cifti2.cifti2_axes)": [[77, "nibabel.cifti2.cifti2_axes.to_header", false]], "to_mapping() (nibabel.cifti2.cifti2_axes.brainmodelaxis method)": [[77, "nibabel.cifti2.cifti2_axes.BrainModelAxis.to_mapping", false]], "to_mapping() (nibabel.cifti2.cifti2_axes.labelaxis method)": [[77, "nibabel.cifti2.cifti2_axes.LabelAxis.to_mapping", false]], "to_mapping() (nibabel.cifti2.cifti2_axes.parcelsaxis method)": [[77, "nibabel.cifti2.cifti2_axes.ParcelsAxis.to_mapping", false]], "to_mapping() (nibabel.cifti2.cifti2_axes.scalaraxis method)": [[77, "nibabel.cifti2.cifti2_axes.ScalarAxis.to_mapping", false]], "to_mapping() (nibabel.cifti2.cifti2_axes.seriesaxis method)": [[77, "nibabel.cifti2.cifti2_axes.SeriesAxis.to_mapping", false]], "to_mask() (nibabel.pointset.grid method)": [[110, "nibabel.pointset.Grid.to_mask", false]], "to_matvec() (in module nibabel.affines)": [[68, "nibabel.affines.to_matvec", false]], "to_stream() (nibabel.filebasedimages.serializableimage method)": [[87, "nibabel.filebasedimages.SerializableImage.to_stream", false]], "to_world() (nibabel.streamlines.tractogram.lazytractogram method)": [[119, "nibabel.streamlines.tractogram.LazyTractogram.to_world", false]], "to_world() (nibabel.streamlines.tractogram.tractogram method)": [[119, "nibabel.streamlines.tractogram.Tractogram.to_world", false]], "to_xml() (nibabel.gifti.gifti.giftiimage method)": [[94, "nibabel.gifti.gifti.GiftiImage.to_xml", false]], "to_xml() (nibabel.xmlutils.xmlserializable method)": [[125, "nibabel.xmlutils.XmlSerializable.to_xml", false]], "total_nb_rows (nibabel.streamlines.array_sequence.arraysequence property)": [[119, "nibabel.streamlines.array_sequence.ArraySequence.total_nb_rows", false]], "tractogram (class in nibabel.streamlines.tractogram)": [[119, "nibabel.streamlines.tractogram.Tractogram", false]], "tractogram (nibabel.streamlines.tractogram_file.tractogramfile property)": [[119, "nibabel.streamlines.tractogram_file.TractogramFile.tractogram", false]], "tractogramfile (class in nibabel.streamlines.tractogram_file)": [[119, "nibabel.streamlines.tractogram_file.TractogramFile", false]], "tractogramitem (class in nibabel.streamlines.tractogram)": [[119, "nibabel.streamlines.tractogram.TractogramItem", false]], "tripwire (class in nibabel.tripwire)": [[121, "nibabel.tripwire.TripWire", false]], "tripwireerror (class in nibabel.tripwire)": [[121, "nibabel.tripwire.TripWireError", false]], "trkfile (class in nibabel.streamlines.trk)": [[119, "nibabel.streamlines.trk.TrkFile", false]], "type_info() (in module nibabel.casting)": [[76, "nibabel.casting.type_info", false]], "types_filenames() (in module nibabel.filename_parser)": [[89, "nibabel.filename_parser.types_filenames", false]], "typesfilenameserror (class in nibabel.filename_parser)": [[89, "nibabel.filename_parser.TypesFilenamesError", false]], "ulp() (in module nibabel.casting)": [[76, "nibabel.casting.ulp", false]], "uncache() (nibabel.dataobj_images.dataobjimage method)": [[80, "nibabel.dataobj_images.DataobjImage.uncache", false]], "unit (nibabel.cifti2.cifti2_axes.seriesaxis property)": [[77, "nibabel.cifti2.cifti2_axes.SeriesAxis.unit", false]], "unpack() (nibabel.nicom.structreader.unpacker method)": [[102, "nibabel.nicom.structreader.Unpacker.unpack", false]], "unpacker (class in nibabel.nicom.structreader)": [[102, "nibabel.nicom.structreader.Unpacker", false]], "update_cache() (in module nibabel.dft)": [[83, "nibabel.dft.update_cache", false]], "update_header() (nibabel.nifti1.nifti1image method)": [[103, "nibabel.nifti1.Nifti1Image.update_header", false]], "update_header() (nibabel.nifti1.nifti1pair method)": [[103, "nibabel.nifti1.Nifti1Pair.update_header", false]], "update_header() (nibabel.spatialimages.spatialimage method)": [[116, "nibabel.spatialimages.SpatialImage.update_header", false]], "update_headers() (nibabel.cifti2.cifti2.cifti2image method)": [[77, "nibabel.cifti2.cifti2.Cifti2Image.update_headers", false]], "valid_exts (nibabel.analyze.analyzeimage attribute)": [[69, "nibabel.analyze.AnalyzeImage.valid_exts", false]], "valid_exts (nibabel.brikhead.afniimage attribute)": [[74, "nibabel.brikhead.AFNIImage.valid_exts", false]], "valid_exts (nibabel.cifti2.cifti2.cifti2image attribute)": [[77, "nibabel.cifti2.cifti2.Cifti2Image.valid_exts", false]], "valid_exts (nibabel.ecat.ecatimage attribute)": [[84, "nibabel.ecat.EcatImage.valid_exts", false]], "valid_exts (nibabel.filebasedimages.filebasedimage attribute)": [[87, "nibabel.filebasedimages.FileBasedImage.valid_exts", false]], "valid_exts (nibabel.freesurfer.mghformat.mghimage attribute)": [[92, "nibabel.freesurfer.mghformat.MGHImage.valid_exts", false]], "valid_exts (nibabel.gifti.gifti.giftiimage attribute)": [[94, "nibabel.gifti.gifti.GiftiImage.valid_exts", false]], "valid_exts (nibabel.minc1.minc1image attribute)": [[99, "nibabel.minc1.Minc1Image.valid_exts", false]], "valid_exts (nibabel.nifti1.nifti1image attribute)": [[103, "nibabel.nifti1.Nifti1Image.valid_exts", false]], "valid_exts (nibabel.parrec.parrecimage attribute)": [[109, "nibabel.parrec.PARRECImage.valid_exts", false]], "value (nibabel.gifti.gifti.giftinvpairs property)": [[94, "nibabel.gifti.gifti.GiftiNVPairs.value", false]], "value_set() (nibabel.volumeutils.recoder method)": [[123, "nibabel.volumeutils.Recoder.value_set", false]], "values() (nibabel.wrapstruct.wrapstruct method)": [[124, "nibabel.wrapstruct.WrapStruct.values", false]], "vendor (class in nibabel.nicom.utils)": [[102, "nibabel.nicom.utils.Vendor", false]], "vendor (nibabel.nicom.dicomwrappers.multiframewrapper property)": [[102, "nibabel.nicom.dicomwrappers.MultiframeWrapper.vendor", false]], "vendor (nibabel.nicom.dicomwrappers.siemenswrapper property)": [[102, "nibabel.nicom.dicomwrappers.SiemensWrapper.vendor", false]], "vendor (nibabel.nicom.dicomwrappers.wrapper property)": [[102, "nibabel.nicom.dicomwrappers.Wrapper.vendor", false]], "vendor_from_private() (in module nibabel.nicom.utils)": [[102, "nibabel.nicom.utils.vendor_from_private", false]], "verbose() (in module nibabel.cmdline.parrec2nii)": [[78, "nibabel.cmdline.parrec2nii.verbose", false]], "verbose() (in module nibabel.cmdline.utils)": [[78, "nibabel.cmdline.utils.verbose", false]], "versioneddatasource (class in nibabel.data)": [[79, "nibabel.data.VersionedDatasource", false]], "vertex_indices (nibabel.cifti2.cifti2.cifti2brainmodel property)": [[77, "nibabel.cifti2.cifti2.Cifti2BrainModel.vertex_indices", false]], "visibledeprecationwarning (class in nibabel.deprecated)": [[81, "nibabel.deprecated.VisibleDeprecationWarning", false]], "vol_is_full() (in module nibabel.parrec)": [[109, "nibabel.parrec.vol_is_full", false]], "vol_numbers() (in module nibabel.parrec)": [[109, "nibabel.parrec.vol_numbers", false]], "volume (nibabel.cifti2.cifti2.cifti2matrixindicesmap property)": [[77, "nibabel.cifti2.cifti2.Cifti2MatrixIndicesMap.volume", false]], "volume_mask (nibabel.cifti2.cifti2_axes.brainmodelaxis property)": [[77, "nibabel.cifti2.cifti2_axes.BrainModelAxis.volume_mask", false]], "volume_shape (nibabel.cifti2.cifti2_axes.brainmodelaxis property)": [[77, "nibabel.cifti2.cifti2_axes.BrainModelAxis.volume_shape", false]], "volume_shape (nibabel.cifti2.cifti2_axes.parcelsaxis property)": [[77, "nibabel.cifti2.cifti2_axes.ParcelsAxis.volume_shape", false]], "volumeerror (class in nibabel.dft)": [[83, "nibabel.dft.VolumeError", false]], "vox2out_vox() (in module nibabel.spaces)": [[115, "nibabel.spaces.vox2out_vox", false]], "voxel_indices_ijk (nibabel.cifti2.cifti2.cifti2brainmodel property)": [[77, "nibabel.cifti2.cifti2.Cifti2BrainModel.voxel_indices_ijk", false]], "voxel_indices_ijk (nibabel.cifti2.cifti2.cifti2parcel property)": [[77, "nibabel.cifti2.cifti2.Cifti2Parcel.voxel_indices_ijk", false]], "voxel_order (nibabel.streamlines.header.field attribute)": [[119, "nibabel.streamlines.header.Field.VOXEL_ORDER", false]], "voxel_sizes (nibabel.nicom.dicomwrappers.multiframewrapper property)": [[102, "nibabel.nicom.dicomwrappers.MultiframeWrapper.voxel_sizes", false]], "voxel_sizes (nibabel.nicom.dicomwrappers.wrapper property)": [[102, "nibabel.nicom.dicomwrappers.Wrapper.voxel_sizes", false]], "voxel_sizes (nibabel.streamlines.header.field attribute)": [[119, "nibabel.streamlines.header.Field.VOXEL_SIZES", false]], "voxel_sizes() (in module nibabel.affines)": [[68, "nibabel.affines.voxel_sizes", false]], "voxel_to_rasmm (nibabel.streamlines.header.field attribute)": [[119, "nibabel.streamlines.header.Field.VOXEL_TO_RASMM", false]], "warn_message (nibabel.deprecated.futurewarningmixin attribute)": [[81, "nibabel.deprecated.FutureWarningMixin.warn_message", false]], "working_type() (in module nibabel.volumeutils)": [[123, "nibabel.volumeutils.working_type", false]], "wrapper (class in nibabel.nicom.dicomwrappers)": [[102, "nibabel.nicom.dicomwrappers.Wrapper", false]], "wrapper_from_data() (in module nibabel.nicom.dicomwrappers)": [[102, "nibabel.nicom.dicomwrappers.wrapper_from_data", false]], "wrapper_from_file() (in module nibabel.nicom.dicomwrappers)": [[102, "nibabel.nicom.dicomwrappers.wrapper_from_file", false]], "wrappererror (class in nibabel.nicom.dicomwrappers)": [[102, "nibabel.nicom.dicomwrappers.WrapperError", false]], "wrapperprecisionerror (class in nibabel.nicom.dicomwrappers)": [[102, "nibabel.nicom.dicomwrappers.WrapperPrecisionError", false]], "wrapstruct (class in nibabel.wrapstruct)": [[124, "nibabel.wrapstruct.WrapStruct", false]], "wrapstructerror (class in nibabel.wrapstruct)": [[124, "nibabel.wrapstruct.WrapStructError", false]], "write() (nibabel.openers.fileish method)": [[106, "nibabel.openers.Fileish.write", false]], "write() (nibabel.openers.opener method)": [[106, "nibabel.openers.Opener.write", false]], "write_annot() (in module nibabel.freesurfer.io)": [[92, "nibabel.freesurfer.io.write_annot", false]], "write_geometry() (in module nibabel.freesurfer.io)": [[92, "nibabel.freesurfer.io.write_geometry", false]], "write_morph_data() (in module nibabel.freesurfer.io)": [[92, "nibabel.freesurfer.io.write_morph_data", false]], "write_raise() (nibabel.batteryrunners.report method)": [[72, "nibabel.batteryrunners.Report.write_raise", false]], "write_to() (nibabel.filebasedimages.filebasedheader method)": [[87, "nibabel.filebasedimages.FileBasedHeader.write_to", false]], "write_to() (nibabel.nifti1.nifti1extensions method)": [[103, "nibabel.nifti1.Nifti1Extensions.write_to", false]], "write_to() (nibabel.nifti1.nifti1header method)": [[103, "nibabel.nifti1.Nifti1Header.write_to", false]], "write_to() (nibabel.nifti1.niftiextension method)": [[103, "nibabel.nifti1.NiftiExtension.write_to", false]], "write_to() (nibabel.wrapstruct.wrapstruct method)": [[124, "nibabel.wrapstruct.WrapStruct.write_to", false]], "write_zeros() (in module nibabel.volumeutils)": [[123, "nibabel.volumeutils.write_zeros", false]], "writeftr_to() (nibabel.freesurfer.mghformat.mghheader method)": [[92, "nibabel.freesurfer.mghformat.MGHHeader.writeftr_to", false]], "writehdr_to() (nibabel.freesurfer.mghformat.mghheader method)": [[92, "nibabel.freesurfer.mghformat.MGHHeader.writehdr_to", false]], "writererror (class in nibabel.arraywriters)": [[71, "nibabel.arraywriters.WriterError", false]], "xmlbasedheader (class in nibabel.xmlutils)": [[125, "nibabel.xmlutils.XmlBasedHeader", false]], "xmlparser (class in nibabel.xmlutils)": [[125, "nibabel.xmlutils.XmlParser", false]], "xmlserializable (class in nibabel.xmlutils)": [[125, "nibabel.xmlutils.XmlSerializable", false]], "zstd_def (nibabel.openers.opener attribute)": [[106, "nibabel.openers.Opener.zstd_def", false]]}, "objects": {"": [[66, 0, 0, "-", "nibabel"]], "nibabel": [[67, 0, 0, "-", "_compression"], [68, 0, 0, "-", "affines"], [69, 0, 0, "-", "analyze"], [70, 0, 0, "-", "arrayproxy"], [71, 0, 0, "-", "arraywriters"], [72, 0, 0, "-", "batteryrunners"], [66, 3, 1, "", "bench"], [73, 0, 0, "-", "benchmarks"], [74, 0, 0, "-", "brikhead"], [75, 0, 0, "-", "caret"], [76, 0, 0, "-", "casting"], [77, 0, 0, "-", "cifti2"], [78, 0, 0, "-", "cmdline"], [79, 0, 0, "-", "data"], [80, 0, 0, "-", "dataobj_images"], [81, 0, 0, "-", "deprecated"], [82, 0, 0, "-", "deprecator"], [83, 0, 0, "-", "dft"], [84, 0, 0, "-", "ecat"], [85, 0, 0, "-", "environment"], [86, 0, 0, "-", "eulerangles"], [87, 0, 0, "-", "filebasedimages"], [88, 0, 0, "-", "fileholders"], [89, 0, 0, "-", "filename_parser"], [90, 0, 0, "-", "fileslice"], [91, 0, 0, "-", "fileutils"], [92, 0, 0, "-", "freesurfer"], [93, 0, 0, "-", "funcs"], [66, 3, 1, "", "get_info"], [94, 0, 0, "-", "gifti"], [95, 0, 0, "-", "imageclasses"], [96, 0, 0, "-", "imageglobals"], [97, 0, 0, "-", "imagestats"], [98, 0, 0, "-", "loadsave"], [99, 0, 0, "-", "minc1"], [100, 0, 0, "-", "minc2"], [101, 0, 0, "-", "mriutils"], [102, 0, 0, "-", "nicom"], [103, 0, 0, "-", "nifti1"], [104, 0, 0, "-", "nifti2"], [105, 0, 0, "-", "onetime"], [106, 0, 0, "-", "openers"], [107, 0, 0, "-", "optpkg"], [108, 0, 0, "-", "orientations"], [109, 0, 0, "-", "parrec"], [110, 0, 0, "-", "pointset"], [111, 0, 0, "-", "processing"], [112, 0, 0, "-", "pydicom_compat"], [113, 0, 0, "-", "quaternions"], [114, 0, 0, "-", "rstutils"], [115, 0, 0, "-", "spaces"], [116, 0, 0, "-", "spatialimages"], [117, 0, 0, "-", "spm2analyze"], [118, 0, 0, "-", "spm99analyze"], [119, 0, 0, "-", "streamlines"], [66, 3, 1, "", "test"], [120, 0, 0, "-", "tmpdirs"], [121, 0, 0, "-", "tripwire"], [122, 0, 0, "-", "viewers"], [123, 0, 0, "-", "volumeutils"], [124, 0, 0, "-", "wrapstruct"], [125, 0, 0, "-", "xmlutils"]], "nibabel.affines": [[68, 1, 1, "", "AffineError"], [68, 3, 1, "", "append_diag"], [68, 3, 1, "", "apply_affine"], [68, 3, 1, "", "dot_reduce"], [68, 3, 1, "", "from_matvec"], [68, 3, 1, "", "obliquity"], [68, 3, 1, "", "rescale_affine"], [68, 3, 1, "", "to_matvec"], [68, 3, 1, "", "voxel_sizes"]], "nibabel.affines.AffineError": [[68, 2, 1, "", "__init__"]], "nibabel.analyze": [[69, 1, 1, "", "AnalyzeHeader"], [69, 1, 1, "", "AnalyzeImage"]], "nibabel.analyze.AnalyzeHeader": [[69, 2, 1, "", "__init__"], [69, 2, 1, "", "as_analyze_map"], [69, 2, 1, "", "data_from_fileobj"], [69, 2, 1, "", "data_to_fileobj"], [69, 2, 1, "", "default_structarr"], [69, 4, 1, "", "default_x_flip"], [69, 2, 1, "", "from_header"], [69, 2, 1, "", "get_base_affine"], [69, 2, 1, "", "get_best_affine"], [69, 2, 1, "", "get_data_dtype"], [69, 2, 1, "", "get_data_offset"], [69, 2, 1, "", "get_data_shape"], [69, 2, 1, "", "get_slope_inter"], [69, 2, 1, "", "get_zooms"], [69, 2, 1, "", "guessed_endian"], [69, 4, 1, "", "has_data_intercept"], [69, 4, 1, "", "has_data_slope"], [69, 2, 1, "", "may_contain_header"], [69, 2, 1, "", "raw_data_from_fileobj"], [69, 2, 1, "", "set_data_dtype"], [69, 2, 1, "", "set_data_offset"], [69, 2, 1, "", "set_data_shape"], [69, 2, 1, "", "set_slope_inter"], [69, 2, 1, "", "set_zooms"], [69, 4, 1, "", "sizeof_hdr"], [69, 4, 1, "", "template_dtype"]], "nibabel.analyze.AnalyzeImage": [[69, 4, 1, "", "ImageArrayProxy"], [69, 2, 1, "", "__init__"], [69, 4, 1, "", "files_types"], [69, 2, 1, "", "from_file_map"], [69, 2, 1, "", "get_data_dtype"], [69, 4, 1, "", "header_class"], [69, 4, 1, "", "makeable"], [69, 4, 1, "", "rw"], [69, 2, 1, "", "set_data_dtype"], [69, 2, 1, "", "to_file_map"], [69, 4, 1, "", "valid_exts"]], "nibabel.arrayproxy": [[70, 1, 1, "", "ArrayLike"], [70, 1, 1, "", "ArrayProxy"], [70, 3, 1, "", "get_obj_dtype"], [70, 3, 1, "", "is_proxy"], [70, 3, 1, "", "reshape_dataobj"]], "nibabel.arrayproxy.ArrayLike": [[70, 2, 1, "", "__init__"], [70, 5, 1, "", "ndim"], [70, 4, 1, "", "shape"]], "nibabel.arrayproxy.ArrayProxy": [[70, 2, 1, "", "__init__"], [70, 2, 1, "", "copy"], [70, 5, 1, "", "dtype"], [70, 2, 1, "", "get_unscaled"], [70, 5, 1, "", "inter"], [70, 5, 1, "", "is_proxy"], [70, 5, 1, "", "ndim"], [70, 5, 1, "", "offset"], [70, 2, 1, "", "reshape"], [70, 5, 1, "", "shape"], [70, 5, 1, "", "slope"]], "nibabel.arraywriters": [[71, 1, 1, "", "ArrayWriter"], [71, 1, 1, "", "ScalingError"], [71, 1, 1, "", "SlopeArrayWriter"], [71, 1, 1, "", "SlopeInterArrayWriter"], [71, 1, 1, "", "WriterError"], [71, 3, 1, "", "get_slope_inter"], [71, 3, 1, "", "make_array_writer"]], "nibabel.arraywriters.ArrayWriter": [[71, 2, 1, "", "__init__"], [71, 5, 1, "", "array"], [71, 2, 1, "", "finite_range"], [71, 5, 1, "", "has_nan"], [71, 5, 1, "", "out_dtype"], [71, 2, 1, "", "scaling_needed"], [71, 2, 1, "", "to_fileobj"]], "nibabel.arraywriters.ScalingError": [[71, 2, 1, "", "__init__"]], "nibabel.arraywriters.SlopeArrayWriter": [[71, 2, 1, "", "__init__"], [71, 2, 1, "", "calc_scale"], [71, 2, 1, "", "reset"], [71, 2, 1, "", "scaling_needed"], [71, 5, 1, "", "slope"], [71, 2, 1, "", "to_fileobj"]], "nibabel.arraywriters.SlopeInterArrayWriter": [[71, 2, 1, "", "__init__"], [71, 5, 1, "", "inter"], [71, 2, 1, "", "reset"], [71, 2, 1, "", "to_fileobj"]], "nibabel.arraywriters.WriterError": [[71, 2, 1, "", "__init__"]], "nibabel.batteryrunners": [[72, 1, 1, "", "BatteryRunner"], [72, 1, 1, "", "Report"]], "nibabel.batteryrunners.BatteryRunner": [[72, 2, 1, "", "__init__"], [72, 2, 1, "", "check_fix"], [72, 2, 1, "", "check_only"]], "nibabel.batteryrunners.Report": [[72, 2, 1, "", "__init__"], [72, 2, 1, "", "log_raise"], [72, 5, 1, "", "message"], [72, 2, 1, "", "write_raise"]], "nibabel.benchmarks": [[73, 0, 0, "-", "bench_array_to_file"], [73, 0, 0, "-", "bench_arrayproxy_slicing"], [73, 0, 0, "-", "bench_fileslice"], [73, 0, 0, "-", "bench_finite_range"], [73, 0, 0, "-", "bench_load_save"], [73, 0, 0, "-", "butils"]], "nibabel.benchmarks.bench_array_to_file": [[73, 3, 1, "", "bench_array_to_file"]], "nibabel.benchmarks.bench_arrayproxy_slicing": [[73, 3, 1, "", "bench_arrayproxy_slicing"]], "nibabel.benchmarks.bench_fileslice": [[73, 3, 1, "", "bench_fileslice"], [73, 3, 1, "", "run_slices"]], "nibabel.benchmarks.bench_finite_range": [[73, 3, 1, "", "bench_finite_range"]], "nibabel.benchmarks.bench_load_save": [[73, 3, 1, "", "bench_load_save"]], "nibabel.benchmarks.butils": [[73, 3, 1, "", "print_git_title"]], "nibabel.brikhead": [[74, 1, 1, "", "AFNIArrayProxy"], [74, 1, 1, "", "AFNIHeader"], [74, 1, 1, "", "AFNIHeaderError"], [74, 1, 1, "", "AFNIImage"], [74, 1, 1, "", "AFNIImageError"], [74, 3, 1, "", "parse_AFNI_header"]], "nibabel.brikhead.AFNIArrayProxy": [[74, 2, 1, "", "__init__"], [74, 5, 1, "", "scaling"]], "nibabel.brikhead.AFNIHeader": [[74, 2, 1, "", "__init__"], [74, 2, 1, "", "copy"], [74, 2, 1, "", "from_fileobj"], [74, 2, 1, "", "from_header"], [74, 2, 1, "", "get_affine"], [74, 2, 1, "", "get_data_offset"], [74, 2, 1, "", "get_data_scaling"], [74, 2, 1, "", "get_slope_inter"], [74, 2, 1, "", "get_space"], [74, 2, 1, "", "get_volume_labels"]], "nibabel.brikhead.AFNIHeaderError": [[74, 2, 1, "", "__init__"]], "nibabel.brikhead.AFNIImage": [[74, 4, 1, "", "ImageArrayProxy"], [74, 2, 1, "", "__init__"], [74, 4, 1, "", "files_types"], [74, 2, 1, "", "filespec_to_file_map"], [74, 2, 1, "", "from_file_map"], [74, 4, 1, "", "header_class"], [74, 4, 1, "", "makeable"], [74, 4, 1, "", "rw"], [74, 4, 1, "", "valid_exts"]], "nibabel.brikhead.AFNIImageError": [[74, 2, 1, "", "__init__"]], "nibabel.caret": [[75, 1, 1, "", "CaretMetaData"]], "nibabel.caret.CaretMetaData": [[75, 2, 1, "", "__init__"]], "nibabel.casting": [[76, 1, 1, "", "CastingError"], [76, 1, 1, "", "FloatingError"], [76, 3, 1, "", "able_int_type"], [76, 3, 1, "", "as_int"], [76, 3, 1, "", "best_float"], [76, 3, 1, "", "ceil_exact"], [76, 3, 1, "", "float_to_int"], [76, 3, 1, "", "floor_exact"], [76, 3, 1, "", "floor_log2"], [76, 3, 1, "", "have_binary128"], [76, 3, 1, "", "int_abs"], [76, 3, 1, "", "int_to_float"], [76, 3, 1, "", "longdouble_lte_float64"], [76, 3, 1, "", "longdouble_precision_improved"], [76, 3, 1, "", "ok_floats"], [76, 3, 1, "", "on_powerpc"], [76, 3, 1, "", "shared_range"], [76, 3, 1, "", "type_info"], [76, 3, 1, "", "ulp"]], "nibabel.casting.CastingError": [[76, 2, 1, "", "__init__"]], "nibabel.casting.FloatingError": [[76, 2, 1, "", "__init__"]], "nibabel.cifti2": [[77, 0, 0, "-", "cifti2"], [77, 0, 0, "-", "cifti2_axes"], [77, 0, 0, "-", "parse_cifti2"]], "nibabel.cifti2.cifti2": [[77, 1, 1, "", "Cifti2BrainModel"], [77, 1, 1, "", "Cifti2Header"], [77, 1, 1, "", "Cifti2HeaderError"], [77, 1, 1, "", "Cifti2Image"], [77, 1, 1, "", "Cifti2Label"], [77, 1, 1, "", "Cifti2LabelTable"], [77, 1, 1, "", "Cifti2Matrix"], [77, 1, 1, "", "Cifti2MatrixIndicesMap"], [77, 1, 1, "", "Cifti2MetaData"], [77, 1, 1, "", "Cifti2NamedMap"], [77, 1, 1, "", "Cifti2Parcel"], [77, 1, 1, "", "Cifti2Surface"], [77, 1, 1, "", "Cifti2TransformationMatrixVoxelIndicesIJKtoXYZ"], [77, 1, 1, "", "Cifti2VertexIndices"], [77, 1, 1, "", "Cifti2Vertices"], [77, 1, 1, "", "Cifti2Volume"], [77, 1, 1, "", "Cifti2VoxelIndicesIJK"], [77, 1, 1, "", "LimitedNifti2Header"]], "nibabel.cifti2.cifti2.Cifti2BrainModel": [[77, 2, 1, "", "__init__"], [77, 5, 1, "", "vertex_indices"], [77, 5, 1, "", "voxel_indices_ijk"]], "nibabel.cifti2.cifti2.Cifti2Header": [[77, 2, 1, "", "__init__"], [77, 2, 1, "", "from_axes"], [77, 2, 1, "", "get_axis"], [77, 2, 1, "", "get_index_map"], [77, 5, 1, "", "mapped_indices"], [77, 2, 1, "", "may_contain_header"], [77, 5, 1, "", "number_of_mapped_indices"]], "nibabel.cifti2.cifti2.Cifti2HeaderError": [[77, 2, 1, "", "__init__"]], "nibabel.cifti2.cifti2.Cifti2Image": [[77, 2, 1, "", "__init__"], [77, 4, 1, "", "files_types"], [77, 2, 1, "", "from_file_map"], [77, 2, 1, "", "from_image"], [77, 2, 1, "", "get_data_dtype"], [77, 4, 1, "", "header_class"], [77, 4, 1, "", "makeable"], [77, 5, 1, "", "nifti_header"], [77, 4, 1, "", "rw"], [77, 2, 1, "", "set_data_dtype"], [77, 2, 1, "", "to_file_map"], [77, 2, 1, "", "update_headers"], [77, 4, 1, "", "valid_exts"]], "nibabel.cifti2.cifti2.Cifti2Label": [[77, 2, 1, "", "__init__"], [77, 5, 1, "", "rgba"]], "nibabel.cifti2.cifti2.Cifti2LabelTable": [[77, 2, 1, "", "__init__"], [77, 2, 1, "", "append"]], "nibabel.cifti2.cifti2.Cifti2Matrix": [[77, 2, 1, "", "__init__"], [77, 2, 1, "", "get_axis"], [77, 2, 1, "", "get_data_shape"], [77, 2, 1, "", "get_index_map"], [77, 2, 1, "", "insert"], [77, 5, 1, "", "mapped_indices"], [77, 5, 1, "", "metadata"]], "nibabel.cifti2.cifti2.Cifti2MatrixIndicesMap": [[77, 2, 1, "", "__init__"], [77, 5, 1, "", "brain_models"], [77, 2, 1, "", "insert"], [77, 5, 1, "", "named_maps"], [77, 5, 1, "", "parcels"], [77, 5, 1, "", "surfaces"], [77, 5, 1, "", "volume"]], "nibabel.cifti2.cifti2.Cifti2MetaData": [[77, 2, 1, "", "__init__"], [77, 5, 1, "", "data"], [77, 2, 1, "", "difference_update"]], "nibabel.cifti2.cifti2.Cifti2NamedMap": [[77, 2, 1, "", "__init__"], [77, 5, 1, "", "label_table"], [77, 5, 1, "", "metadata"]], "nibabel.cifti2.cifti2.Cifti2Parcel": [[77, 2, 1, "", "__init__"], [77, 2, 1, "", "append_cifti_vertices"], [77, 2, 1, "", "pop_cifti2_vertices"], [77, 5, 1, "", "voxel_indices_ijk"]], "nibabel.cifti2.cifti2.Cifti2Surface": [[77, 2, 1, "", "__init__"]], "nibabel.cifti2.cifti2.Cifti2TransformationMatrixVoxelIndicesIJKtoXYZ": [[77, 2, 1, "", "__init__"]], "nibabel.cifti2.cifti2.Cifti2VertexIndices": [[77, 2, 1, "", "__init__"], [77, 2, 1, "", "insert"]], "nibabel.cifti2.cifti2.Cifti2Vertices": [[77, 2, 1, "", "__init__"], [77, 2, 1, "", "insert"]], "nibabel.cifti2.cifti2.Cifti2Volume": [[77, 2, 1, "", "__init__"]], "nibabel.cifti2.cifti2.Cifti2VoxelIndicesIJK": [[77, 2, 1, "", "__init__"], [77, 2, 1, "", "insert"]], "nibabel.cifti2.cifti2.LimitedNifti2Header": [[77, 2, 1, "", "__init__"]], "nibabel.cifti2.cifti2_axes": [[77, 1, 1, "", "Axis"], [77, 1, 1, "", "BrainModelAxis"], [77, 1, 1, "", "LabelAxis"], [77, 1, 1, "", "ParcelsAxis"], [77, 1, 1, "", "ScalarAxis"], [77, 1, 1, "", "SeriesAxis"], [77, 3, 1, "", "from_index_mapping"], [77, 3, 1, "", "to_header"]], "nibabel.cifti2.cifti2_axes.Axis": [[77, 2, 1, "", "__init__"], [77, 5, 1, "", "size"]], "nibabel.cifti2.cifti2_axes.BrainModelAxis": [[77, 2, 1, "", "__init__"], [77, 5, 1, "", "affine"], [77, 2, 1, "", "from_index_mapping"], [77, 2, 1, "", "from_mask"], [77, 2, 1, "", "from_surface"], [77, 2, 1, "", "get_element"], [77, 2, 1, "", "iter_structures"], [77, 5, 1, "", "name"], [77, 5, 1, "", "surface_mask"], [77, 2, 1, "", "to_cifti_brain_structure_name"], [77, 2, 1, "", "to_mapping"], [77, 5, 1, "", "volume_mask"], [77, 5, 1, "", "volume_shape"]], "nibabel.cifti2.cifti2_axes.LabelAxis": [[77, 2, 1, "", "__init__"], [77, 2, 1, "", "from_index_mapping"], [77, 2, 1, "", "get_element"], [77, 2, 1, "", "to_mapping"]], "nibabel.cifti2.cifti2_axes.ParcelsAxis": [[77, 2, 1, "", "__init__"], [77, 5, 1, "", "affine"], [77, 2, 1, "", "from_brain_models"], [77, 2, 1, "", "from_index_mapping"], [77, 2, 1, "", "get_element"], [77, 2, 1, "", "to_mapping"], [77, 5, 1, "", "volume_shape"]], "nibabel.cifti2.cifti2_axes.ScalarAxis": [[77, 2, 1, "", "__init__"], [77, 2, 1, "", "from_index_mapping"], [77, 2, 1, "", "get_element"], [77, 2, 1, "", "to_mapping"]], "nibabel.cifti2.cifti2_axes.SeriesAxis": [[77, 2, 1, "", "__init__"], [77, 2, 1, "", "from_index_mapping"], [77, 2, 1, "", "get_element"], [77, 4, 1, "", "size"], [77, 5, 1, "", "time"], [77, 2, 1, "", "to_mapping"], [77, 5, 1, "", "unit"]], "nibabel.cifti2.parse_cifti2": [[77, 1, 1, "", "Cifti2Extension"], [77, 1, 1, "", "Cifti2Parser"]], "nibabel.cifti2.parse_cifti2.Cifti2Extension": [[77, 2, 1, "", "__init__"], [77, 4, 1, "", "code"]], "nibabel.cifti2.parse_cifti2.Cifti2Parser": [[77, 2, 1, "", "CharacterDataHandler"], [77, 2, 1, "", "EndElementHandler"], [77, 2, 1, "", "StartElementHandler"], [77, 2, 1, "", "__init__"], [77, 2, 1, "", "flush_chardata"], [77, 5, 1, "", "pending_data"]], "nibabel.cmdline": [[78, 0, 0, "-", "conform"], [78, 0, 0, "-", "convert"], [78, 0, 0, "-", "dicomfs"], [78, 0, 0, "-", "diff"], [78, 0, 0, "-", "ls"], [78, 0, 0, "-", "nifti_dx"], [78, 0, 0, "-", "parrec2nii"], [78, 0, 0, "-", "roi"], [78, 0, 0, "-", "stats"], [78, 0, 0, "-", "tck2trk"], [78, 0, 0, "-", "trk2tck"], [78, 0, 0, "-", "utils"]], "nibabel.cmdline.conform": [[78, 3, 1, "", "main"]], "nibabel.cmdline.convert": [[78, 3, 1, "", "main"]], "nibabel.cmdline.dicomfs": [[78, 1, 1, "", "DICOMFS"], [78, 1, 1, "", "FileHandle"], [78, 1, 1, "", "dummy_fuse"], [78, 4, 1, "", "fuse"], [78, 3, 1, "", "get_opt_parser"], [78, 3, 1, "", "main"]], "nibabel.cmdline.dicomfs.DICOMFS": [[78, 2, 1, "", "__init__"], [78, 2, 1, "", "get_paths"], [78, 2, 1, "", "getattr"], [78, 2, 1, "", "match_path"], [78, 2, 1, "", "open"], [78, 2, 1, "", "read"], [78, 2, 1, "", "readdir"], [78, 2, 1, "", "release"]], "nibabel.cmdline.dicomfs.FileHandle": [[78, 2, 1, "", "__init__"]], "nibabel.cmdline.dicomfs.dummy_fuse": [[78, 4, 1, "", "Fuse"], [78, 2, 1, "", "__init__"], [78, 4, 1, "", "fuse_python_api"]], "nibabel.cmdline.diff": [[78, 3, 1, "", "are_values_different"], [78, 3, 1, "", "diff"], [78, 3, 1, "", "display_diff"], [78, 3, 1, "", "get_data_diff"], [78, 3, 1, "", "get_data_hash_diff"], [78, 3, 1, "", "get_headers_diff"], [78, 3, 1, "", "get_opt_parser"], [78, 3, 1, "", "main"]], "nibabel.cmdline.ls": [[78, 3, 1, "", "get_opt_parser"], [78, 3, 1, "", "main"], [78, 3, 1, "", "proc_file"]], "nibabel.cmdline.nifti_dx": [[78, 3, 1, "", "main"]], "nibabel.cmdline.parrec2nii": [[78, 3, 1, "", "error"], [78, 3, 1, "", "get_opt_parser"], [78, 3, 1, "", "main"], [78, 3, 1, "", "proc_file"], [78, 3, 1, "", "verbose"]], "nibabel.cmdline.roi": [[78, 3, 1, "", "lossless_slice"], [78, 3, 1, "", "main"], [78, 3, 1, "", "parse_slice"], [78, 3, 1, "", "sanitize"]], "nibabel.cmdline.stats": [[78, 3, 1, "", "main"]], "nibabel.cmdline.tck2trk": [[78, 3, 1, "", "main"], [78, 3, 1, "", "parse_args"]], "nibabel.cmdline.trk2tck": [[78, 3, 1, "", "main"], [78, 3, 1, "", "parse_args"]], "nibabel.cmdline.utils": [[78, 3, 1, "", "ap"], [78, 3, 1, "", "safe_get"], [78, 3, 1, "", "table2string"], [78, 3, 1, "", "verbose"]], "nibabel.data": [[79, 1, 1, "", "Bomber"], [79, 1, 1, "", "BomberError"], [79, 1, 1, "", "DataError"], [79, 1, 1, "", "Datasource"], [79, 1, 1, "", "VersionedDatasource"], [79, 3, 1, "", "datasource_or_bomber"], [79, 3, 1, "", "find_data_dir"], [79, 3, 1, "", "get_data_path"], [79, 3, 1, "", "make_datasource"]], "nibabel.data.Bomber": [[79, 2, 1, "", "__init__"]], "nibabel.data.BomberError": [[79, 2, 1, "", "__init__"]], "nibabel.data.DataError": [[79, 2, 1, "", "__init__"]], "nibabel.data.Datasource": [[79, 2, 1, "", "__init__"], [79, 2, 1, "", "get_filename"], [79, 2, 1, "", "list_files"]], "nibabel.data.VersionedDatasource": [[79, 2, 1, "", "__init__"]], "nibabel.dataobj_images": [[80, 1, 1, "", "DataobjImage"]], "nibabel.dataobj_images.DataobjImage": [[80, 2, 1, "", "__init__"], [80, 5, 1, "", "dataobj"], [80, 2, 1, "", "from_file_map"], [80, 2, 1, "", "from_filename"], [80, 2, 1, "", "get_data"], [80, 2, 1, "", "get_fdata"], [80, 5, 1, "", "in_memory"], [80, 2, 1, "", "load"], [80, 5, 1, "", "ndim"], [80, 5, 1, "", "shape"], [80, 2, 1, "", "uncache"]], "nibabel.deprecated": [[81, 1, 1, "", "FutureWarningMixin"], [81, 1, 1, "", "ModuleProxy"], [81, 1, 1, "", "VisibleDeprecationWarning"], [81, 3, 1, "", "alert_future_error"]], "nibabel.deprecated.FutureWarningMixin": [[81, 2, 1, "", "__init__"], [81, 4, 1, "", "warn_message"]], "nibabel.deprecated.ModuleProxy": [[81, 2, 1, "", "__init__"]], "nibabel.deprecated.VisibleDeprecationWarning": [[81, 2, 1, "", "__init__"]], "nibabel.deprecator": [[82, 1, 1, "", "Deprecator"], [82, 1, 1, "", "ExpiredDeprecationError"]], "nibabel.deprecator.Deprecator": [[82, 2, 1, "", "__init__"], [82, 2, 1, "", "is_bad_version"]], "nibabel.deprecator.ExpiredDeprecationError": [[82, 2, 1, "", "__init__"]], "nibabel.dft": [[83, 1, 1, "", "CachingError"], [83, 1, 1, "", "DFTError"], [83, 1, 1, "", "InstanceStackError"], [83, 1, 1, "", "VolumeError"], [83, 3, 1, "", "clear_cache"], [83, 3, 1, "", "get_studies"], [83, 3, 1, "", "update_cache"]], "nibabel.dft.CachingError": [[83, 2, 1, "", "__init__"]], "nibabel.dft.DFTError": [[83, 2, 1, "", "__init__"]], "nibabel.dft.InstanceStackError": [[83, 2, 1, "", "__init__"]], "nibabel.dft.VolumeError": [[83, 2, 1, "", "__init__"]], "nibabel.ecat": [[84, 1, 1, "", "EcatHeader"], [84, 1, 1, "", "EcatImage"], [84, 1, 1, "", "EcatImageArrayProxy"], [84, 1, 1, "", "EcatSubHeader"], [84, 3, 1, "", "get_frame_order"], [84, 3, 1, "", "get_series_framenumbers"], [84, 3, 1, "", "read_mlist"], [84, 3, 1, "", "read_subheaders"]], "nibabel.ecat.EcatHeader": [[84, 2, 1, "", "__init__"], [84, 2, 1, "", "default_structarr"], [84, 2, 1, "", "get_data_dtype"], [84, 2, 1, "", "get_filetype"], [84, 2, 1, "", "get_patient_orient"], [84, 2, 1, "", "guessed_endian"], [84, 4, 1, "", "template_dtype"]], "nibabel.ecat.EcatImage": [[84, 4, 1, "", "ImageArrayProxy"], [84, 2, 1, "", "__init__"], [84, 5, 1, "", "affine"], [84, 4, 1, "", "files_types"], [84, 2, 1, "", "from_file_map"], [84, 2, 1, "", "from_image"], [84, 2, 1, "", "get_data_dtype"], [84, 2, 1, "", "get_frame"], [84, 2, 1, "", "get_frame_affine"], [84, 2, 1, "", "get_mlist"], [84, 2, 1, "", "get_subheaders"], [84, 4, 1, "", "header_class"], [84, 2, 1, "", "load"], [84, 5, 1, "", "shape"], [84, 4, 1, "", "subheader_class"], [84, 2, 1, "", "to_file_map"], [84, 4, 1, "", "valid_exts"]], "nibabel.ecat.EcatImageArrayProxy": [[84, 2, 1, "", "__init__"], [84, 5, 1, "", "is_proxy"], [84, 5, 1, "", "ndim"], [84, 5, 1, "", "shape"]], "nibabel.ecat.EcatSubHeader": [[84, 2, 1, "", "__init__"], [84, 2, 1, "", "data_from_fileobj"], [84, 2, 1, "", "get_frame_affine"], [84, 2, 1, "", "get_nframes"], [84, 2, 1, "", "get_shape"], [84, 2, 1, "", "get_zooms"], [84, 2, 1, "", "raw_data_from_fileobj"]], "nibabel.environment": [[85, 3, 1, "", "get_home_dir"], [85, 3, 1, "", "get_nipy_system_dir"], [85, 3, 1, "", "get_nipy_user_dir"]], "nibabel.eulerangles": [[86, 3, 1, "", "angle_axis2euler"], [86, 3, 1, "", "euler2angle_axis"], [86, 3, 1, "", "euler2mat"], [86, 3, 1, "", "euler2quat"], [86, 3, 1, "", "mat2euler"], [86, 3, 1, "", "quat2euler"]], "nibabel.filebasedimages": [[87, 1, 1, "", "FileBasedHeader"], [87, 1, 1, "", "FileBasedImage"], [87, 1, 1, "", "ImageFileError"], [87, 1, 1, "", "SerializableImage"]], "nibabel.filebasedimages.FileBasedHeader": [[87, 2, 1, "", "__init__"], [87, 2, 1, "", "copy"], [87, 2, 1, "", "from_fileobj"], [87, 2, 1, "", "from_header"], [87, 2, 1, "", "write_to"]], "nibabel.filebasedimages.FileBasedImage": [[87, 2, 1, "", "__init__"], [87, 4, 1, "", "files_types"], [87, 2, 1, "", "filespec_to_file_map"], [87, 2, 1, "", "from_file_map"], [87, 2, 1, "", "from_filename"], [87, 2, 1, "", "from_image"], [87, 2, 1, "", "get_filename"], [87, 5, 1, "", "header"], [87, 4, 1, "", "header_class"], [87, 2, 1, "", "instance_to_filename"], [87, 2, 1, "", "load"], [87, 2, 1, "", "make_file_map"], [87, 4, 1, "", "makeable"], [87, 2, 1, "", "path_maybe_image"], [87, 4, 1, "", "rw"], [87, 2, 1, "", "set_filename"], [87, 2, 1, "", "to_file_map"], [87, 2, 1, "", "to_filename"], [87, 4, 1, "", "valid_exts"]], "nibabel.filebasedimages.ImageFileError": [[87, 2, 1, "", "__init__"]], "nibabel.filebasedimages.SerializableImage": [[87, 2, 1, "", "__init__"], [87, 2, 1, "", "from_bytes"], [87, 2, 1, "", "from_stream"], [87, 2, 1, "", "from_url"], [87, 2, 1, "", "to_bytes"], [87, 2, 1, "", "to_stream"]], "nibabel.fileholders": [[88, 1, 1, "", "FileHolder"], [88, 1, 1, "", "FileHolderError"], [88, 3, 1, "", "copy_file_map"]], "nibabel.fileholders.FileHolder": [[88, 2, 1, "", "__init__"], [88, 5, 1, "", "file_like"], [88, 2, 1, "", "get_prepare_fileobj"], [88, 2, 1, "", "same_file_as"]], "nibabel.fileholders.FileHolderError": [[88, 2, 1, "", "__init__"]], "nibabel.filename_parser": [[89, 1, 1, "", "TypesFilenamesError"], [89, 3, 1, "", "parse_filename"], [89, 3, 1, "", "splitext_addext"], [89, 3, 1, "", "types_filenames"]], "nibabel.filename_parser.TypesFilenamesError": [[89, 2, 1, "", "__init__"]], "nibabel.fileslice": [[90, 3, 1, "", "calc_slicedefs"], [90, 3, 1, "", "canonical_slicers"], [90, 3, 1, "", "fileslice"], [90, 3, 1, "", "fill_slicer"], [90, 3, 1, "", "is_fancy"], [90, 3, 1, "", "optimize_read_slicers"], [90, 3, 1, "", "optimize_slicer"], [90, 3, 1, "", "predict_shape"], [90, 3, 1, "", "read_segments"], [90, 3, 1, "", "slice2len"], [90, 3, 1, "", "slice2outax"], [90, 3, 1, "", "slicers2segments"], [90, 3, 1, "", "strided_scalar"], [90, 3, 1, "", "threshold_heuristic"]], "nibabel.fileutils": [[91, 3, 1, "", "read_zt_byte_strings"]], "nibabel.freesurfer": [[92, 0, 0, "-", "io"], [92, 0, 0, "-", "mghformat"]], "nibabel.freesurfer.io": [[92, 3, 1, "", "read_annot"], [92, 3, 1, "", "read_geometry"], [92, 3, 1, "", "read_label"], [92, 3, 1, "", "read_morph_data"], [92, 3, 1, "", "write_annot"], [92, 3, 1, "", "write_geometry"], [92, 3, 1, "", "write_morph_data"]], "nibabel.freesurfer.mghformat": [[92, 1, 1, "", "MGHError"], [92, 1, 1, "", "MGHHeader"], [92, 1, 1, "", "MGHImage"]], "nibabel.freesurfer.mghformat.MGHError": [[92, 2, 1, "", "__init__"]], "nibabel.freesurfer.mghformat.MGHHeader": [[92, 2, 1, "", "__init__"], [92, 2, 1, "", "as_byteswapped"], [92, 2, 1, "", "chk_version"], [92, 2, 1, "", "copy"], [92, 2, 1, "", "data_from_fileobj"], [92, 2, 1, "", "default_structarr"], [92, 2, 1, "", "diagnose_binaryblock"], [92, 2, 1, "", "from_fileobj"], [92, 2, 1, "", "from_header"], [92, 2, 1, "", "get_affine"], [92, 2, 1, "", "get_best_affine"], [92, 2, 1, "", "get_data_bytespervox"], [92, 2, 1, "", "get_data_dtype"], [92, 2, 1, "", "get_data_offset"], [92, 2, 1, "", "get_data_shape"], [92, 2, 1, "", "get_data_size"], [92, 2, 1, "", "get_footer_offset"], [92, 2, 1, "", "get_ras2vox"], [92, 2, 1, "", "get_slope_inter"], [92, 2, 1, "", "get_vox2ras"], [92, 2, 1, "", "get_vox2ras_tkr"], [92, 2, 1, "", "get_zooms"], [92, 2, 1, "", "guessed_endian"], [92, 2, 1, "", "set_data_dtype"], [92, 2, 1, "", "set_data_shape"], [92, 2, 1, "", "set_zooms"], [92, 4, 1, "", "template_dtype"], [92, 2, 1, "", "writeftr_to"], [92, 2, 1, "", "writehdr_to"]], "nibabel.freesurfer.mghformat.MGHImage": [[92, 4, 1, "", "ImageArrayProxy"], [92, 2, 1, "", "__init__"], [92, 4, 1, "", "files_types"], [92, 2, 1, "", "filespec_to_file_map"], [92, 2, 1, "", "from_file_map"], [92, 4, 1, "", "header_class"], [92, 4, 1, "", "makeable"], [92, 4, 1, "", "rw"], [92, 2, 1, "", "to_file_map"], [92, 4, 1, "", "valid_exts"]], "nibabel.funcs": [[93, 3, 1, "", "as_closest_canonical"], [93, 3, 1, "", "concat_images"], [93, 3, 1, "", "four_to_three"], [93, 3, 1, "", "squeeze_image"]], "nibabel.gifti": [[94, 0, 0, "-", "gifti"], [94, 0, 0, "-", "parse_gifti_fast"], [94, 0, 0, "-", "util"]], "nibabel.gifti.gifti": [[94, 1, 1, "", "GiftiCoordSystem"], [94, 1, 1, "", "GiftiDataArray"], [94, 1, 1, "", "GiftiImage"], [94, 1, 1, "", "GiftiLabel"], [94, 1, 1, "", "GiftiLabelTable"], [94, 1, 1, "", "GiftiMetaData"], [94, 1, 1, "", "GiftiNVPairs"]], "nibabel.gifti.gifti.GiftiCoordSystem": [[94, 2, 1, "", "__init__"], [94, 2, 1, "", "print_summary"]], "nibabel.gifti.gifti.GiftiDataArray": [[94, 2, 1, "", "__init__"], [94, 5, 1, "", "metadata"], [94, 5, 1, "", "num_dim"], [94, 2, 1, "", "print_summary"]], "nibabel.gifti.gifti.GiftiImage": [[94, 2, 1, "", "__init__"], [94, 2, 1, "", "add_gifti_data_array"], [94, 2, 1, "", "agg_data"], [94, 4, 1, "", "files_types"], [94, 2, 1, "", "from_file_map"], [94, 2, 1, "", "from_filename"], [94, 2, 1, "", "get_arrays_from_intent"], [94, 5, 1, "", "labeltable"], [94, 5, 1, "", "meta"], [94, 5, 1, "", "numDA"], [94, 4, 1, "", "parser"], [94, 2, 1, "", "print_summary"], [94, 2, 1, "", "remove_gifti_data_array"], [94, 2, 1, "", "remove_gifti_data_array_by_intent"], [94, 2, 1, "", "to_bytes"], [94, 2, 1, "", "to_file_map"], [94, 2, 1, "", "to_xml"], [94, 4, 1, "", "valid_exts"]], "nibabel.gifti.gifti.GiftiLabel": [[94, 2, 1, "", "__init__"], [94, 5, 1, "", "rgba"]], "nibabel.gifti.gifti.GiftiLabelTable": [[94, 2, 1, "", "__init__"], [94, 2, 1, "", "get_labels_as_dict"], [94, 2, 1, "", "print_summary"]], "nibabel.gifti.gifti.GiftiMetaData": [[94, 2, 1, "", "__init__"], [94, 5, 1, "", "data"], [94, 2, 1, "", "from_dict"], [94, 5, 1, "", "metadata"], [94, 2, 1, "", "print_summary"]], "nibabel.gifti.gifti.GiftiNVPairs": [[94, 2, 1, "", "__init__"], [94, 5, 1, "", "name"], [94, 5, 1, "", "value"]], "nibabel.gifti.parse_gifti_fast": [[94, 1, 1, "", "GiftiImageParser"], [94, 1, 1, "", "GiftiParseError"], [94, 3, 1, "", "read_data_block"]], "nibabel.gifti.parse_gifti_fast.GiftiImageParser": [[94, 2, 1, "", "CharacterDataHandler"], [94, 2, 1, "", "EndElementHandler"], [94, 2, 1, "", "StartElementHandler"], [94, 2, 1, "", "__init__"], [94, 2, 1, "", "flush_chardata"], [94, 5, 1, "", "pending_data"]], "nibabel.gifti.parse_gifti_fast.GiftiParseError": [[94, 2, 1, "", "__init__"]], "nibabel.imageclasses": [[95, 3, 1, "", "spatial_axes_first"]], "nibabel.imageglobals": [[96, 1, 1, "", "ErrorLevel"], [96, 1, 1, "", "LoggingOutputSuppressor"]], "nibabel.imageglobals.ErrorLevel": [[96, 2, 1, "", "__init__"]], "nibabel.imageglobals.LoggingOutputSuppressor": [[96, 2, 1, "", "__init__"]], "nibabel.imagestats": [[97, 3, 1, "", "count_nonzero_voxels"], [97, 3, 1, "", "mask_volume"]], "nibabel.loadsave": [[98, 3, 1, "", "guessed_image_type"], [98, 3, 1, "", "load"], [98, 3, 1, "", "read_img_data"], [98, 3, 1, "", "save"]], "nibabel.minc1": [[99, 1, 1, "", "Minc1File"], [99, 1, 1, "", "Minc1Header"], [99, 1, 1, "", "Minc1Image"], [99, 1, 1, "", "MincError"], [99, 1, 1, "", "MincHeader"], [99, 1, 1, "", "MincImageArrayProxy"]], "nibabel.minc1.Minc1File": [[99, 2, 1, "", "__init__"], [99, 2, 1, "", "get_affine"], [99, 2, 1, "", "get_data_dtype"], [99, 2, 1, "", "get_data_shape"], [99, 2, 1, "", "get_scaled_data"], [99, 2, 1, "", "get_zooms"]], "nibabel.minc1.Minc1Header": [[99, 2, 1, "", "__init__"], [99, 2, 1, "", "may_contain_header"]], "nibabel.minc1.Minc1Image": [[99, 4, 1, "", "ImageArrayProxy"], [99, 2, 1, "", "__init__"], [99, 4, 1, "", "files_types"], [99, 2, 1, "", "from_file_map"], [99, 4, 1, "", "header_class"], [99, 4, 1, "", "makeable"], [99, 4, 1, "", "rw"], [99, 4, 1, "", "valid_exts"]], "nibabel.minc1.MincError": [[99, 2, 1, "", "__init__"]], "nibabel.minc1.MincHeader": [[99, 2, 1, "", "__init__"], [99, 2, 1, "", "data_from_fileobj"], [99, 4, 1, "", "data_layout"], [99, 2, 1, "", "data_to_fileobj"]], "nibabel.minc1.MincImageArrayProxy": [[99, 2, 1, "", "__init__"], [99, 5, 1, "", "is_proxy"], [99, 5, 1, "", "ndim"], [99, 5, 1, "", "shape"]], "nibabel.minc2": [[100, 1, 1, "", "Hdf5Bunch"], [100, 1, 1, "", "Minc2File"], [100, 1, 1, "", "Minc2Header"], [100, 1, 1, "", "Minc2Image"]], "nibabel.minc2.Hdf5Bunch": [[100, 2, 1, "", "__init__"]], "nibabel.minc2.Minc2File": [[100, 2, 1, "", "__init__"], [100, 2, 1, "", "get_data_dtype"], [100, 2, 1, "", "get_data_shape"], [100, 2, 1, "", "get_scaled_data"]], "nibabel.minc2.Minc2Header": [[100, 2, 1, "", "__init__"], [100, 2, 1, "", "may_contain_header"]], "nibabel.minc2.Minc2Image": [[100, 2, 1, "", "__init__"], [100, 2, 1, "", "from_file_map"], [100, 4, 1, "", "header_class"]], "nibabel.mriutils": [[101, 1, 1, "", "MRIError"], [101, 3, 1, "", "calculate_dwell_time"]], "nibabel.mriutils.MRIError": [[101, 2, 1, "", "__init__"]], "nibabel.nicom": [[102, 0, 0, "-", "ascconv"], [102, 0, 0, "-", "csareader"], [102, 0, 0, "-", "dicomreaders"], [102, 0, 0, "-", "dicomwrappers"], [102, 0, 0, "-", "dwiparams"], [102, 0, 0, "-", "structreader"], [102, 0, 0, "-", "utils"]], "nibabel.nicom.ascconv": [[102, 1, 1, "", "AscconvParseError"], [102, 1, 1, "", "Atom"], [102, 1, 1, "", "NoValue"], [102, 3, 1, "", "assign2atoms"], [102, 3, 1, "", "obj_from_atoms"], [102, 3, 1, "", "parse_ascconv"]], "nibabel.nicom.ascconv.AscconvParseError": [[102, 2, 1, "", "__init__"]], "nibabel.nicom.ascconv.Atom": [[102, 2, 1, "", "__init__"]], "nibabel.nicom.ascconv.NoValue": [[102, 2, 1, "", "__init__"]], "nibabel.nicom.csareader": [[102, 1, 1, "", "CSAError"], [102, 1, 1, "", "CSAReadError"], [102, 3, 1, "", "get_acq_mat_txt"], [102, 3, 1, "", "get_b_matrix"], [102, 3, 1, "", "get_b_value"], [102, 3, 1, "", "get_csa_header"], [102, 3, 1, "", "get_g_vector"], [102, 3, 1, "", "get_ice_dims"], [102, 3, 1, "", "get_n_mosaic"], [102, 3, 1, "", "get_scalar"], [102, 3, 1, "", "get_slice_normal"], [102, 3, 1, "", "get_vector"], [102, 3, 1, "", "is_mosaic"], [102, 3, 1, "", "nt_str"], [102, 3, 1, "", "read"]], "nibabel.nicom.csareader.CSAError": [[102, 2, 1, "", "__init__"]], "nibabel.nicom.csareader.CSAReadError": [[102, 2, 1, "", "__init__"]], "nibabel.nicom.dicomreaders": [[102, 1, 1, "", "DicomReadError"], [102, 3, 1, "", "mosaic_to_nii"], [102, 3, 1, "", "read_mosaic_dir"], [102, 3, 1, "", "read_mosaic_dwi_dir"], [102, 3, 1, "", "slices_to_series"]], "nibabel.nicom.dicomreaders.DicomReadError": [[102, 2, 1, "", "__init__"]], "nibabel.nicom.dicomwrappers": [[102, 1, 1, "", "FilterDwiIso"], [102, 1, 1, "", "FilterMultiStack"], [102, 1, 1, "", "FrameFilter"], [102, 1, 1, "", "MosaicWrapper"], [102, 1, 1, "", "MultiframeWrapper"], [102, 1, 1, "", "SiemensWrapper"], [102, 1, 1, "", "Wrapper"], [102, 1, 1, "", "WrapperError"], [102, 1, 1, "", "WrapperPrecisionError"], [102, 3, 1, "", "none_or_close"], [102, 3, 1, "", "wrapper_from_data"], [102, 3, 1, "", "wrapper_from_file"]], "nibabel.nicom.dicomwrappers.FilterDwiIso": [[102, 2, 1, "", "__init__"], [102, 2, 1, "", "applies"], [102, 2, 1, "", "keep"]], "nibabel.nicom.dicomwrappers.FilterMultiStack": [[102, 2, 1, "", "__init__"], [102, 2, 1, "", "applies"], [102, 2, 1, "", "keep"]], "nibabel.nicom.dicomwrappers.FrameFilter": [[102, 2, 1, "", "__init__"], [102, 2, 1, "", "applies"], [102, 2, 1, "", "keep"]], "nibabel.nicom.dicomwrappers.MosaicWrapper": [[102, 2, 1, "", "__init__"], [102, 2, 1, "", "get_unscaled_data"], [102, 5, 1, "", "image_position"], [102, 5, 1, "", "image_shape"], [102, 4, 1, "", "is_mosaic"]], "nibabel.nicom.dicomwrappers.MultiframeWrapper": [[102, 2, 1, "", "__init__"], [102, 5, 1, "", "frame_order"], [102, 2, 1, "", "get_unscaled_data"], [102, 5, 1, "", "image_orient_patient"], [102, 5, 1, "", "image_position"], [102, 5, 1, "", "image_shape"], [102, 4, 1, "", "is_multiframe"], [102, 5, 1, "", "scale_factors"], [102, 5, 1, "", "series_signature"], [102, 5, 1, "", "vendor"], [102, 5, 1, "", "voxel_sizes"]], "nibabel.nicom.dicomwrappers.SiemensWrapper": [[102, 2, 1, "", "__init__"], [102, 5, 1, "", "b_matrix"], [102, 4, 1, "", "is_csa"], [102, 5, 1, "", "q_vector"], [102, 5, 1, "", "series_signature"], [102, 5, 1, "", "slice_normal"], [102, 5, 1, "", "vendor"]], "nibabel.nicom.dicomwrappers.Wrapper": [[102, 2, 1, "", "__init__"], [102, 5, 1, "", "affine"], [102, 4, 1, "", "b_matrix"], [102, 5, 1, "", "b_value"], [102, 5, 1, "", "b_vector"], [102, 2, 1, "", "get"], [102, 2, 1, "", "get_data"], [102, 2, 1, "", "get_pixel_array"], [102, 2, 1, "", "get_unscaled_data"], [102, 5, 1, "", "image_orient_patient"], [102, 5, 1, "", "image_position"], [102, 5, 1, "", "image_shape"], [102, 5, 1, "", "instance_number"], [102, 4, 1, "", "is_csa"], [102, 4, 1, "", "is_mosaic"], [102, 4, 1, "", "is_multiframe"], [102, 2, 1, "", "is_same_series"], [102, 4, 1, "", "q_vector"], [102, 5, 1, "", "rotation_matrix"], [102, 5, 1, "", "scale_factors"], [102, 5, 1, "", "series_signature"], [102, 5, 1, "", "slice_indicator"], [102, 5, 1, "", "slice_normal"], [102, 5, 1, "", "vendor"], [102, 5, 1, "", "voxel_sizes"]], "nibabel.nicom.dicomwrappers.WrapperError": [[102, 2, 1, "", "__init__"]], "nibabel.nicom.dicomwrappers.WrapperPrecisionError": [[102, 2, 1, "", "__init__"]], "nibabel.nicom.dwiparams": [[102, 3, 1, "", "B2q"], [102, 3, 1, "", "nearest_pos_semi_def"], [102, 3, 1, "", "q2bg"]], "nibabel.nicom.structreader": [[102, 1, 1, "", "Unpacker"]], "nibabel.nicom.structreader.Unpacker": [[102, 2, 1, "", "__init__"], [102, 2, 1, "", "read"], [102, 2, 1, "", "unpack"]], "nibabel.nicom.utils": [[102, 1, 1, "", "Vendor"], [102, 3, 1, "", "find_private_section"], [102, 3, 1, "", "vendor_from_private"]], "nibabel.nicom.utils.Vendor": [[102, 4, 1, "", "GE"], [102, 4, 1, "", "PHILIPS"], [102, 4, 1, "", "SIEMENS"], [102, 2, 1, "", "__init__"]], "nibabel.nifti1": [[103, 1, 1, "", "Nifti1DicomExtension"], [103, 1, 1, "", "Nifti1Extension"], [103, 1, 1, "", "Nifti1Extensions"], [103, 1, 1, "", "Nifti1Header"], [103, 1, 1, "", "Nifti1Image"], [103, 1, 1, "", "Nifti1Pair"], [103, 1, 1, "", "Nifti1PairHeader"], [103, 1, 1, "", "NiftiExtension"], [103, 3, 1, "", "load"], [103, 3, 1, "", "save"]], "nibabel.nifti1.Nifti1DicomExtension": [[103, 2, 1, "", "__init__"], [103, 4, 1, "", "code"]], "nibabel.nifti1.Nifti1Extension": [[103, 2, 1, "", "__init__"], [103, 4, 1, "", "code"]], "nibabel.nifti1.Nifti1Extensions": [[103, 2, 1, "", "__init__"], [103, 2, 1, "", "count"], [103, 2, 1, "", "from_fileobj"], [103, 2, 1, "", "get_codes"], [103, 2, 1, "", "get_sizeondisk"], [103, 2, 1, "", "write_to"]], "nibabel.nifti1.Nifti1Header": [[103, 2, 1, "", "__init__"], [103, 2, 1, "", "copy"], [103, 2, 1, "", "default_structarr"], [103, 4, 1, "", "exts_klass"], [103, 2, 1, "", "from_fileobj"], [103, 2, 1, "", "from_header"], [103, 2, 1, "", "get_best_affine"], [103, 2, 1, "", "get_data_shape"], [103, 2, 1, "", "get_dim_info"], [103, 2, 1, "", "get_intent"], [103, 2, 1, "", "get_n_slices"], [103, 2, 1, "", "get_qform"], [103, 2, 1, "", "get_qform_quaternion"], [103, 2, 1, "", "get_sform"], [103, 2, 1, "", "get_slice_duration"], [103, 2, 1, "", "get_slice_times"], [103, 2, 1, "", "get_slope_inter"], [103, 2, 1, "", "get_xyzt_units"], [103, 4, 1, "", "has_data_intercept"], [103, 4, 1, "", "has_data_slope"], [103, 4, 1, "", "is_single"], [103, 2, 1, "", "may_contain_header"], [103, 4, 1, "", "pair_magic"], [103, 4, 1, "", "pair_vox_offset"], [103, 4, 1, "", "quaternion_threshold"], [103, 2, 1, "", "set_data_dtype"], [103, 2, 1, "", "set_data_shape"], [103, 2, 1, "", "set_dim_info"], [103, 2, 1, "", "set_intent"], [103, 2, 1, "", "set_qform"], [103, 2, 1, "", "set_sform"], [103, 2, 1, "", "set_slice_duration"], [103, 2, 1, "", "set_slice_times"], [103, 2, 1, "", "set_slope_inter"], [103, 2, 1, "", "set_xyzt_units"], [103, 4, 1, "", "single_magic"], [103, 4, 1, "", "single_vox_offset"], [103, 4, 1, "", "template_dtype"], [103, 2, 1, "", "write_to"]], "nibabel.nifti1.Nifti1Image": [[103, 2, 1, "", "__init__"], [103, 4, 1, "", "files_types"], [103, 4, 1, "", "header_class"], [103, 2, 1, "", "update_header"], [103, 4, 1, "", "valid_exts"]], "nibabel.nifti1.Nifti1Pair": [[103, 2, 1, "", "__init__"], [103, 2, 1, "", "as_reoriented"], [103, 2, 1, "", "get_data_dtype"], [103, 2, 1, "", "get_qform"], [103, 2, 1, "", "get_sform"], [103, 4, 1, "", "header_class"], [103, 4, 1, "", "rw"], [103, 2, 1, "", "set_data_dtype"], [103, 2, 1, "", "set_qform"], [103, 2, 1, "", "set_sform"], [103, 2, 1, "", "to_file_map"], [103, 2, 1, "", "update_header"]], "nibabel.nifti1.Nifti1PairHeader": [[103, 2, 1, "", "__init__"], [103, 4, 1, "", "is_single"]], "nibabel.nifti1.NiftiExtension": [[103, 2, 1, "", "__init__"], [103, 4, 1, "", "code"], [103, 5, 1, "", "content"], [103, 4, 1, "", "encoding"], [103, 2, 1, "", "from_bytes"], [103, 2, 1, "", "from_object"], [103, 2, 1, "", "get_code"], [103, 2, 1, "", "get_content"], [103, 2, 1, "", "get_object"], [103, 2, 1, "", "get_sizeondisk"], [103, 2, 1, "", "json"], [103, 5, 1, "", "text"], [103, 2, 1, "", "write_to"]], "nibabel.nifti2": [[104, 1, 1, "", "Nifti2Header"], [104, 1, 1, "", "Nifti2Image"], [104, 1, 1, "", "Nifti2Pair"], [104, 1, 1, "", "Nifti2PairHeader"], [104, 3, 1, "", "load"], [104, 3, 1, "", "save"]], "nibabel.nifti2.Nifti2Header": [[104, 2, 1, "", "__init__"], [104, 2, 1, "", "default_structarr"], [104, 2, 1, "", "get_data_shape"], [104, 2, 1, "", "may_contain_header"], [104, 4, 1, "", "pair_magic"], [104, 4, 1, "", "pair_vox_offset"], [104, 4, 1, "", "quaternion_threshold"], [104, 2, 1, "", "set_data_shape"], [104, 4, 1, "", "single_magic"], [104, 4, 1, "", "single_vox_offset"], [104, 4, 1, "", "sizeof_hdr"], [104, 4, 1, "", "template_dtype"]], "nibabel.nifti2.Nifti2Image": [[104, 2, 1, "", "__init__"], [104, 4, 1, "", "header_class"]], "nibabel.nifti2.Nifti2Pair": [[104, 2, 1, "", "__init__"], [104, 4, 1, "", "header_class"]], "nibabel.nifti2.Nifti2PairHeader": [[104, 2, 1, "", "__init__"], [104, 4, 1, "", "is_single"]], "nibabel.onetime": [[105, 1, 1, "", "ResetMixin"]], "nibabel.onetime.ResetMixin": [[105, 2, 1, "", "__init__"], [105, 2, 1, "", "reset"]], "nibabel.openers": [[106, 1, 1, "", "DeterministicGzipFile"], [106, 1, 1, "", "Fileish"], [106, 1, 1, "", "ImageOpener"], [106, 1, 1, "", "Opener"]], "nibabel.openers.DeterministicGzipFile": [[106, 2, 1, "", "__init__"]], "nibabel.openers.Fileish": [[106, 2, 1, "", "__init__"], [106, 2, 1, "", "read"], [106, 2, 1, "", "write"]], "nibabel.openers.ImageOpener": [[106, 2, 1, "", "__init__"], [106, 4, 1, "", "compress_ext_map"]], "nibabel.openers.Opener": [[106, 2, 1, "", "__init__"], [106, 4, 1, "", "bz2_def"], [106, 2, 1, "", "close"], [106, 2, 1, "", "close_if_mine"], [106, 5, 1, "", "closed"], [106, 4, 1, "", "compress_ext_icase"], [106, 4, 1, "", "compress_ext_map"], [106, 4, 1, "", "default_compresslevel"], [106, 4, 1, "", "default_level_or_option"], [106, 4, 1, "", "default_zst_compresslevel"], [106, 2, 1, "", "fileno"], [106, 4, 1, "", "fobj"], [106, 4, 1, "", "gz_def"], [106, 5, 1, "", "mode"], [106, 5, 1, "", "name"], [106, 2, 1, "", "read"], [106, 2, 1, "", "readinto"], [106, 2, 1, "", "seek"], [106, 2, 1, "", "tell"], [106, 2, 1, "", "write"], [106, 4, 1, "", "zstd_def"]], "nibabel.optpkg": [[107, 3, 1, "", "optional_package"]], "nibabel.orientations": [[108, 1, 1, "", "OrientationError"], [108, 3, 1, "", "aff2axcodes"], [108, 3, 1, "", "apply_orientation"], [108, 3, 1, "", "axcodes2ornt"], [108, 3, 1, "", "flip_axis"], [108, 3, 1, "", "inv_ornt_aff"], [108, 3, 1, "", "io_orientation"], [108, 3, 1, "", "ornt2axcodes"], [108, 3, 1, "", "ornt_transform"]], "nibabel.orientations.OrientationError": [[108, 2, 1, "", "__init__"]], "nibabel.parrec": [[109, 1, 1, "", "PARRECArrayProxy"], [109, 1, 1, "", "PARRECError"], [109, 1, 1, "", "PARRECHeader"], [109, 1, 1, "", "PARRECImage"], [109, 3, 1, "", "exts2pars"], [109, 3, 1, "", "one_line"], [109, 3, 1, "", "parse_PAR_header"], [109, 3, 1, "", "vol_is_full"], [109, 3, 1, "", "vol_numbers"]], "nibabel.parrec.PARRECArrayProxy": [[109, 2, 1, "", "__init__"], [109, 5, 1, "", "dtype"], [109, 2, 1, "", "get_unscaled"], [109, 5, 1, "", "is_proxy"], [109, 5, 1, "", "ndim"], [109, 5, 1, "", "shape"]], "nibabel.parrec.PARRECError": [[109, 2, 1, "", "__init__"]], "nibabel.parrec.PARRECHeader": [[109, 2, 1, "", "__init__"], [109, 2, 1, "", "as_analyze_map"], [109, 2, 1, "", "copy"], [109, 2, 1, "", "from_fileobj"], [109, 2, 1, "", "from_header"], [109, 2, 1, "", "get_affine"], [109, 2, 1, "", "get_bvals_bvecs"], [109, 2, 1, "", "get_data_offset"], [109, 2, 1, "", "get_data_scaling"], [109, 2, 1, "", "get_def"], [109, 2, 1, "", "get_echo_train_length"], [109, 2, 1, "", "get_q_vectors"], [109, 2, 1, "", "get_rec_shape"], [109, 2, 1, "", "get_slice_orientation"], [109, 2, 1, "", "get_sorted_slice_indices"], [109, 2, 1, "", "get_volume_labels"], [109, 2, 1, "", "get_water_fat_shift"], [109, 2, 1, "", "set_data_offset"]], "nibabel.parrec.PARRECImage": [[109, 4, 1, "", "ImageArrayProxy"], [109, 2, 1, "", "__init__"], [109, 4, 1, "", "files_types"], [109, 2, 1, "", "from_file_map"], [109, 2, 1, "", "from_filename"], [109, 4, 1, "", "header_class"], [109, 2, 1, "", "load"], [109, 4, 1, "", "makeable"], [109, 4, 1, "", "rw"], [109, 4, 1, "", "valid_exts"]], "nibabel.pointset": [[110, 1, 1, "", "CoordinateArray"], [110, 1, 1, "", "Grid"], [110, 1, 1, "", "GridIndices"], [110, 1, 1, "", "Pointset"]], "nibabel.pointset.CoordinateArray": [[110, 2, 1, "", "__init__"], [110, 4, 1, "", "ndim"], [110, 4, 1, "", "shape"]], "nibabel.pointset.Grid": [[110, 2, 1, "", "__init__"], [110, 2, 1, "", "from_image"], [110, 2, 1, "", "from_mask"], [110, 2, 1, "", "to_mask"]], "nibabel.pointset.GridIndices": [[110, 2, 1, "", "__init__"], [110, 4, 1, "", "dtype"], [110, 4, 1, "", "gridshape"], [110, 4, 1, "", "ndim"], [110, 4, 1, "", "shape"]], "nibabel.pointset.Pointset": [[110, 2, 1, "", "__init__"], [110, 4, 1, "", "affine"], [110, 4, 1, "", "coordinates"], [110, 5, 1, "", "dim"], [110, 2, 1, "", "get_coords"], [110, 4, 1, "", "homogeneous"], [110, 5, 1, "", "n_coords"]], "nibabel.processing": [[111, 3, 1, "", "adapt_affine"], [111, 3, 1, "", "conform"], [111, 3, 1, "", "fwhm2sigma"], [111, 3, 1, "", "resample_from_to"], [111, 3, 1, "", "resample_to_output"], [111, 3, 1, "", "sigma2fwhm"], [111, 3, 1, "", "smooth_image"]], "nibabel.pydicom_compat": [[112, 3, 1, "", "dicom_test"]], "nibabel.quaternions": [[113, 3, 1, "", "angle_axis2mat"], [113, 3, 1, "", "angle_axis2quat"], [113, 3, 1, "", "conjugate"], [113, 3, 1, "", "eye"], [113, 3, 1, "", "fillpositive"], [113, 3, 1, "", "inverse"], [113, 3, 1, "", "isunit"], [113, 3, 1, "", "mat2quat"], [113, 3, 1, "", "mult"], [113, 3, 1, "", "nearly_equivalent"], [113, 3, 1, "", "norm"], [113, 3, 1, "", "quat2angle_axis"], [113, 3, 1, "", "quat2mat"], [113, 3, 1, "", "rotate_vector"]], "nibabel.rstutils": [[114, 3, 1, "", "rst_table"]], "nibabel.spaces": [[115, 3, 1, "", "slice2volume"], [115, 3, 1, "", "vox2out_vox"]], "nibabel.spatialimages": [[116, 1, 1, "", "HasDtype"], [116, 1, 1, "", "HeaderDataError"], [116, 1, 1, "", "HeaderTypeError"], [116, 1, 1, "", "ImageDataError"], [116, 1, 1, "", "SpatialFirstSlicer"], [116, 1, 1, "", "SpatialHeader"], [116, 1, 1, "", "SpatialImage"], [116, 1, 1, "", "SpatialProtocol"], [116, 3, 1, "", "supported_np_types"]], "nibabel.spatialimages.HasDtype": [[116, 2, 1, "", "__init__"], [116, 2, 1, "", "get_data_dtype"], [116, 2, 1, "", "set_data_dtype"]], "nibabel.spatialimages.HeaderDataError": [[116, 2, 1, "", "__init__"]], "nibabel.spatialimages.HeaderTypeError": [[116, 2, 1, "", "__init__"]], "nibabel.spatialimages.ImageDataError": [[116, 2, 1, "", "__init__"]], "nibabel.spatialimages.SpatialFirstSlicer": [[116, 2, 1, "", "__init__"], [116, 2, 1, "", "check_slicing"], [116, 4, 1, "", "img"], [116, 2, 1, "", "slice_affine"]], "nibabel.spatialimages.SpatialHeader": [[116, 2, 1, "", "__init__"], [116, 2, 1, "", "copy"], [116, 2, 1, "", "data_from_fileobj"], [116, 4, 1, "", "data_layout"], [116, 2, 1, "", "data_to_fileobj"], [116, 4, 1, "", "default_x_flip"], [116, 2, 1, "", "from_header"], [116, 2, 1, "", "get_base_affine"], [116, 2, 1, "", "get_best_affine"], [116, 2, 1, "", "get_data_dtype"], [116, 2, 1, "", "get_data_shape"], [116, 2, 1, "", "get_zooms"], [116, 2, 1, "", "set_data_dtype"], [116, 2, 1, "", "set_data_shape"], [116, 2, 1, "", "set_zooms"]], "nibabel.spatialimages.SpatialImage": [[116, 4, 1, "", "ImageSlicer"], [116, 2, 1, "", "__init__"], [116, 5, 1, "", "affine"], [116, 2, 1, "", "as_reoriented"], [116, 2, 1, "", "from_image"], [116, 2, 1, "", "get_data_dtype"], [116, 4, 1, "", "header_class"], [116, 2, 1, "", "orthoview"], [116, 2, 1, "", "set_data_dtype"], [116, 5, 1, "", "slicer"], [116, 2, 1, "", "update_header"]], "nibabel.spatialimages.SpatialProtocol": [[116, 2, 1, "", "__init__"], [116, 2, 1, "", "get_data_dtype"], [116, 2, 1, "", "get_data_shape"], [116, 2, 1, "", "get_zooms"]], "nibabel.spm2analyze": [[117, 1, 1, "", "Spm2AnalyzeHeader"], [117, 1, 1, "", "Spm2AnalyzeImage"]], "nibabel.spm2analyze.Spm2AnalyzeHeader": [[117, 2, 1, "", "__init__"], [117, 2, 1, "", "get_slope_inter"], [117, 2, 1, "", "may_contain_header"], [117, 4, 1, "", "template_dtype"]], "nibabel.spm2analyze.Spm2AnalyzeImage": [[117, 2, 1, "", "__init__"], [117, 4, 1, "", "header_class"]], "nibabel.spm99analyze": [[118, 1, 1, "", "Spm99AnalyzeHeader"], [118, 1, 1, "", "Spm99AnalyzeImage"], [118, 1, 1, "", "SpmAnalyzeHeader"]], "nibabel.spm99analyze.Spm99AnalyzeHeader": [[118, 2, 1, "", "__init__"], [118, 2, 1, "", "get_best_affine"], [118, 2, 1, "", "get_origin_affine"], [118, 2, 1, "", "set_origin_from_affine"]], "nibabel.spm99analyze.Spm99AnalyzeImage": [[118, 2, 1, "", "__init__"], [118, 4, 1, "", "files_types"], [118, 2, 1, "", "from_file_map"], [118, 4, 1, "", "has_affine"], [118, 4, 1, "", "header_class"], [118, 4, 1, "", "makeable"], [118, 4, 1, "", "rw"], [118, 2, 1, "", "to_file_map"]], "nibabel.spm99analyze.SpmAnalyzeHeader": [[118, 2, 1, "", "__init__"], [118, 2, 1, "", "default_structarr"], [118, 2, 1, "", "get_slope_inter"], [118, 4, 1, "", "has_data_intercept"], [118, 4, 1, "", "has_data_slope"], [118, 2, 1, "", "set_slope_inter"], [118, 4, 1, "", "template_dtype"]], "nibabel.streamlines": [[119, 0, 0, "-", "array_sequence"], [119, 3, 1, "", "detect_format"], [119, 0, 0, "-", "header"], [119, 3, 1, "", "is_supported"], [119, 3, 1, "", "load"], [119, 3, 1, "", "save"], [119, 0, 0, "-", "tck"], [119, 0, 0, "-", "tractogram"], [119, 0, 0, "-", "tractogram_file"], [119, 0, 0, "-", "trk"], [119, 0, 0, "-", "utils"]], "nibabel.streamlines.array_sequence": [[119, 1, 1, "", "ArraySequence"], [119, 3, 1, "", "concatenate"], [119, 3, 1, "", "create_arraysequences_from_generator"], [119, 3, 1, "", "is_array_sequence"], [119, 3, 1, "", "is_ndarray_of_int_or_bool"]], "nibabel.streamlines.array_sequence.ArraySequence": [[119, 2, 1, "", "__init__"], [119, 2, 1, "", "append"], [119, 5, 1, "", "common_shape"], [119, 2, 1, "", "copy"], [119, 2, 1, "", "extend"], [119, 2, 1, "", "finalize_append"], [119, 2, 1, "", "get_data"], [119, 5, 1, "", "is_array_sequence"], [119, 5, 1, "", "is_sliced_view"], [119, 2, 1, "", "load"], [119, 2, 1, "", "save"], [119, 2, 1, "", "shrink_data"], [119, 5, 1, "", "total_nb_rows"]], "nibabel.streamlines.header": [[119, 1, 1, "", "Field"]], "nibabel.streamlines.header.Field": [[119, 4, 1, "", "DIMENSIONS"], [119, 4, 1, "", "ENDIANNESS"], [119, 4, 1, "", "MAGIC_NUMBER"], [119, 4, 1, "", "METHOD"], [119, 4, 1, "", "NB_POINTS"], [119, 4, 1, "", "NB_PROPERTIES_PER_STREAMLINE"], [119, 4, 1, "", "NB_SCALARS_PER_POINT"], [119, 4, 1, "", "NB_STREAMLINES"], [119, 4, 1, "", "ORIGIN"], [119, 4, 1, "", "STEP_SIZE"], [119, 4, 1, "", "VOXEL_ORDER"], [119, 4, 1, "", "VOXEL_SIZES"], [119, 4, 1, "", "VOXEL_TO_RASMM"], [119, 2, 1, "", "__init__"]], "nibabel.streamlines.tck": [[119, 1, 1, "", "TckFile"]], "nibabel.streamlines.tck.TckFile": [[119, 4, 1, "", "EOF_DELIMITER"], [119, 4, 1, "", "FIBER_DELIMITER"], [119, 4, 1, "", "MAGIC_NUMBER"], [119, 4, 1, "", "SUPPORTS_DATA_PER_POINT"], [119, 4, 1, "", "SUPPORTS_DATA_PER_STREAMLINE"], [119, 2, 1, "", "__init__"], [119, 2, 1, "", "create_empty_header"], [119, 2, 1, "", "is_correct_format"], [119, 2, 1, "", "load"], [119, 2, 1, "", "save"]], "nibabel.streamlines.tractogram": [[119, 1, 1, "", "LazyDict"], [119, 1, 1, "", "LazyTractogram"], [119, 1, 1, "", "PerArrayDict"], [119, 1, 1, "", "PerArraySequenceDict"], [119, 1, 1, "", "SliceableDataDict"], [119, 1, 1, "", "Tractogram"], [119, 1, 1, "", "TractogramItem"], [119, 3, 1, "", "is_data_dict"], [119, 3, 1, "", "is_lazy_dict"]], "nibabel.streamlines.tractogram.LazyDict": [[119, 2, 1, "", "__init__"]], "nibabel.streamlines.tractogram.LazyTractogram": [[119, 2, 1, "", "__init__"], [119, 2, 1, "", "apply_affine"], [119, 2, 1, "", "copy"], [119, 5, 1, "", "data"], [119, 5, 1, "", "data_per_point"], [119, 5, 1, "", "data_per_streamline"], [119, 2, 1, "", "extend"], [119, 2, 1, "", "from_data_func"], [119, 2, 1, "", "from_tractogram"], [119, 5, 1, "", "streamlines"], [119, 2, 1, "", "to_world"]], "nibabel.streamlines.tractogram.PerArrayDict": [[119, 2, 1, "", "__init__"], [119, 2, 1, "", "extend"]], "nibabel.streamlines.tractogram.PerArraySequenceDict": [[119, 2, 1, "", "__init__"]], "nibabel.streamlines.tractogram.SliceableDataDict": [[119, 2, 1, "", "__init__"]], "nibabel.streamlines.tractogram.Tractogram": [[119, 2, 1, "", "__init__"], [119, 5, 1, "", "affine_to_rasmm"], [119, 2, 1, "", "apply_affine"], [119, 2, 1, "", "copy"], [119, 5, 1, "", "data_per_point"], [119, 5, 1, "", "data_per_streamline"], [119, 2, 1, "", "extend"], [119, 5, 1, "", "streamlines"], [119, 2, 1, "", "to_world"]], "nibabel.streamlines.tractogram.TractogramItem": [[119, 2, 1, "", "__init__"]], "nibabel.streamlines.tractogram_file": [[119, 1, 1, "", "DataError"], [119, 1, 1, "", "DataWarning"], [119, 1, 1, "", "ExtensionWarning"], [119, 1, 1, "", "HeaderError"], [119, 1, 1, "", "HeaderWarning"], [119, 1, 1, "", "TractogramFile"], [119, 1, 1, "", "abstractclassmethod"]], "nibabel.streamlines.tractogram_file.DataError": [[119, 2, 1, "", "__init__"]], "nibabel.streamlines.tractogram_file.DataWarning": [[119, 2, 1, "", "__init__"]], "nibabel.streamlines.tractogram_file.ExtensionWarning": [[119, 2, 1, "", "__init__"]], "nibabel.streamlines.tractogram_file.HeaderError": [[119, 2, 1, "", "__init__"]], "nibabel.streamlines.tractogram_file.HeaderWarning": [[119, 2, 1, "", "__init__"]], "nibabel.streamlines.tractogram_file.TractogramFile": [[119, 2, 1, "", "__init__"], [119, 5, 1, "", "affine"], [119, 2, 1, "", "create_empty_header"], [119, 5, 1, "", "header"], [119, 2, 1, "", "is_correct_format"], [119, 2, 1, "", "load"], [119, 2, 1, "", "save"], [119, 5, 1, "", "streamlines"], [119, 5, 1, "", "tractogram"]], "nibabel.streamlines.tractogram_file.abstractclassmethod": [[119, 2, 1, "", "__init__"]], "nibabel.streamlines.trk": [[119, 1, 1, "", "TrkFile"], [119, 3, 1, "", "decode_value_from_name"], [119, 3, 1, "", "encode_value_in_name"], [119, 3, 1, "", "get_affine_rasmm_to_trackvis"], [119, 3, 1, "", "get_affine_trackvis_to_rasmm"]], "nibabel.streamlines.trk.TrkFile": [[119, 4, 1, "", "HEADER_SIZE"], [119, 4, 1, "", "MAGIC_NUMBER"], [119, 4, 1, "", "SUPPORTS_DATA_PER_POINT"], [119, 4, 1, "", "SUPPORTS_DATA_PER_STREAMLINE"], [119, 2, 1, "", "__init__"], [119, 2, 1, "", "create_empty_header"], [119, 2, 1, "", "is_correct_format"], [119, 2, 1, "", "load"], [119, 2, 1, "", "save"]], "nibabel.streamlines.utils": [[119, 3, 1, "", "get_affine_from_reference"], [119, 3, 1, "", "peek_next"]], "nibabel.tmpdirs": [[120, 3, 1, "", "InGivenDirectory"], [120, 3, 1, "", "InTemporaryDirectory"], [120, 1, 1, "", "TemporaryDirectory"]], "nibabel.tmpdirs.TemporaryDirectory": [[120, 2, 1, "", "__init__"]], "nibabel.tripwire": [[121, 1, 1, "", "TripWire"], [121, 1, 1, "", "TripWireError"], [121, 3, 1, "", "is_tripwire"]], "nibabel.tripwire.TripWire": [[121, 2, 1, "", "__init__"]], "nibabel.tripwire.TripWireError": [[121, 2, 1, "", "__init__"]], "nibabel.viewers": [[122, 1, 1, "", "OrthoSlicer3D"]], "nibabel.viewers.OrthoSlicer3D": [[122, 2, 1, "", "__init__"], [122, 5, 1, "", "clim"], [122, 2, 1, "", "close"], [122, 5, 1, "", "cmap"], [122, 2, 1, "", "draw"], [122, 5, 1, "", "figs"], [122, 2, 1, "", "link_to"], [122, 5, 1, "", "n_volumes"], [122, 5, 1, "", "position"], [122, 2, 1, "", "set_position"], [122, 2, 1, "", "set_volume_idx"], [122, 2, 1, "", "show"]], "nibabel.volumeutils": [[123, 1, 1, "", "DtypeMapper"], [123, 1, 1, "", "Recoder"], [123, 3, 1, "", "apply_read_scaling"], [123, 3, 1, "", "array_from_file"], [123, 3, 1, "", "array_to_file"], [123, 3, 1, "", "best_write_scale_ftype"], [123, 3, 1, "", "better_float_of"], [123, 3, 1, "", "finite_range"], [123, 3, 1, "", "fname_ext_ul_case"], [123, 3, 1, "", "int_scinter_ftype"], [123, 3, 1, "", "make_dt_codes"], [123, 3, 1, "", "pretty_mapping"], [123, 3, 1, "", "rec2dict"], [123, 3, 1, "", "seek_tell"], [123, 3, 1, "", "shape_zoom_affine"], [123, 3, 1, "", "working_type"], [123, 3, 1, "", "write_zeros"]], "nibabel.volumeutils.DtypeMapper": [[123, 2, 1, "", "__init__"]], "nibabel.volumeutils.Recoder": [[123, 2, 1, "", "__init__"], [123, 2, 1, "", "add_codes"], [123, 4, 1, "", "fields"], [123, 2, 1, "", "keys"], [123, 2, 1, "", "value_set"]], "nibabel.wrapstruct": [[124, 1, 1, "", "LabeledWrapStruct"], [124, 1, 1, "", "WrapStruct"], [124, 1, 1, "", "WrapStructError"]], "nibabel.wrapstruct.LabeledWrapStruct": [[124, 2, 1, "", "__init__"], [124, 2, 1, "", "get_value_label"]], "nibabel.wrapstruct.WrapStruct": [[124, 2, 1, "", "__init__"], [124, 2, 1, "", "as_byteswapped"], [124, 5, 1, "", "binaryblock"], [124, 2, 1, "", "check_fix"], [124, 2, 1, "", "copy"], [124, 2, 1, "", "default_structarr"], [124, 2, 1, "", "diagnose_binaryblock"], [124, 5, 1, "", "endianness"], [124, 2, 1, "", "from_fileobj"], [124, 2, 1, "", "get"], [124, 2, 1, "", "guessed_endian"], [124, 2, 1, "", "items"], [124, 2, 1, "", "keys"], [124, 5, 1, "", "structarr"], [124, 4, 1, "", "template_dtype"], [124, 2, 1, "", "values"], [124, 2, 1, "", "write_to"]], "nibabel.wrapstruct.WrapStructError": [[124, 2, 1, "", "__init__"]], "nibabel.xmlutils": [[125, 1, 1, "", "XmlBasedHeader"], [125, 1, 1, "", "XmlParser"], [125, 1, 1, "", "XmlSerializable"]], "nibabel.xmlutils.XmlBasedHeader": [[125, 2, 1, "", "__init__"]], "nibabel.xmlutils.XmlParser": [[125, 2, 1, "", "CharacterDataHandler"], [125, 2, 1, "", "EndElementHandler"], [125, 4, 1, "", "HANDLER_NAMES"], [125, 2, 1, "", "StartElementHandler"], [125, 2, 1, "", "__init__"], [125, 2, 1, "", "parse"]], "nibabel.xmlutils.XmlSerializable": [[125, 2, 1, "", "__init__"], [125, 2, 1, "", "to_xml"]]}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "class", "Python class"], "2": ["py", "method", "Python method"], "3": ["py", "function", "Python function"], "4": ["py", "attribute", "Python attribute"], "5": ["py", "property", "Python property"]}, "objtypes": {"0": "py:module", "1": "py:class", "2": "py:method", "3": "py:function", "4": "py:attribute", "5": "py:property"}, "terms": {"": [1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 14, 15, 19, 20, 23, 26, 28, 29, 30, 31, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 48, 49, 51, 52, 54, 56, 60, 61, 62, 66, 68, 69, 76, 77, 78, 79, 84, 85, 86, 87, 88, 90, 92, 94, 96, 102, 103, 105, 106, 107, 108, 109, 110, 111, 115, 116, 118, 119, 122, 123], "0": [2, 4, 7, 8, 9, 10, 12, 14, 15, 17, 20, 23, 25, 26, 27, 29, 30, 31, 35, 36, 37, 38, 39, 40, 43, 54, 55, 57, 58, 59, 60, 61, 62, 64, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 84, 86, 88, 90, 91, 92, 93, 94, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 108, 109, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 122, 123, 124, 125], "00": [26, 109], "000": 109, "0000": [30, 35], "00000": 109, "000087": 18, "0001": [35, 52], "0002": [35, 52], "0003": 35, "0004": 35, "0005": 35, "0006": 35, "0007": 35, "0008": [33, 35], "00080070": 9, "000859": 119, "0009": 33, "000e": 33, "0010": [35, 37, 38], "0011": [31, 33, 35], "0012": [30, 31, 33], "0013": [31, 33], "0017": 30, "0018": [33, 38], "0019": 35, "0020": [30, 31, 33, 35, 38], "0021": 35, "0022": 35, "0024": 35, "0028": [33, 35, 38], "0029": [39, 40], "003": 109, "0030": [35, 38], "0031": 33, "0032": [33, 35, 38], "0033": 30, "0037": 38, "005": 77, "0050": 33, "0054": [35, 37], "0055": 37, "0060": 33, "0088": [33, 38], "00ff": 35, "00xx": 35, "01": [9, 94], "0100": [33, 35], "0103": 35, "0105": 33, "0110": 33, "0123361559": 86, "01_task": 15, "02": 9, "03": [7, 8, 9, 11], "04": 14, "0410": 35, "0412": 35, "0414": 35, "05": [102, 113], "0510": 30, "0531": 30, "054": 94, "06": [6, 9, 102], "07": [12, 13, 57, 103], "071": 94, "072": 94, "0731": 113, "08": [74, 113], "081068739295": [61, 62], "09": [11, 15], "0f22701": 43, "0m": 1, "0rc0": 1, "0saga": 20, "0x0010": 37, "0x0054": 37, "0x0055": 37, "0x10": 102, "0x1001": 37, "0x1004": 37, "0x29": 102, "0xff": 102, "1": [2, 4, 6, 9, 10, 11, 12, 14, 20, 26, 29, 30, 31, 33, 35, 36, 38, 39, 40, 43, 54, 55, 57, 58, 59, 60, 61, 62, 64, 66, 68, 69, 70, 71, 72, 74, 76, 77, 78, 81, 82, 84, 86, 90, 91, 92, 93, 94, 96, 97, 102, 103, 104, 105, 106, 108, 109, 110, 111, 112, 113, 115, 116, 117, 118, 119, 123, 124], "10": [1, 2, 9, 26, 29, 30, 35, 38, 40, 41, 54, 61, 62, 68, 69, 71, 72, 77, 84, 86, 93, 94, 102, 103, 105, 108, 109, 117, 123, 124], "100": [9, 31, 41, 68, 77, 103, 117], "1000": [1, 9, 35, 97, 103, 119], "1001": 37, "1004": 37, "1005": 1, "1008": [1, 39, 94], "1009": [1, 39, 40, 94], "100k": 20, "1010": 39, "1013": 1, "1016": 1, "1017": 1, "1018": [1, 31, 39], "1019": [1, 39], "1020": [1, 39], "1022": 1, "1024": [1, 4, 87, 91, 103], "1025": 1, "1038": 1, "1040": 1, "1041": [33, 38], "1043": 1, "1044": 1, "1047": 1, "1048": 1, "1050": 1, "1051": 1, "1052": 1, "1055": 1, "1059": 1, "1070": 109, "1073": 1, "1079": 1, "1082": 1, "1084": 1, "1085": 113, "1087": 113, "109": 30, "1091": 1, "1092": 1, "1093": 1, "1096": 1, "1097": 1, "10ff": 35, "10mm": 2, "11": [10, 12, 22, 26, 35, 36, 38, 40, 54, 59, 61, 62, 68], "110": [9, 86, 117], "1100": 35, "1103": 1, "110455": 10, "110457": 10, "1107": 40, "1110": 1, "1111": 1, "1113": 1, "1115": 1, "1117": 1, "1118": 1, "1122": 109, "1124": 1, "1125": 1, "1126": 1, "1127": 1, "1129": 1, "1131": 1, "1133": 1, "1134": 1, "1137": 109, "1138": 1, "114": [9, 10], "1140": 1, "1142": 1, "1148": 1, "1149": 1, "1153": 1, "1154": 1, "1155": 1, "1156": 1, "11560": 39, "1157": 1, "1158": 1, "1159": 1, "116": 9, "1162": [61, 62], "1165": 1, "1169": 1, "117": [54, 61, 62], "1170": 1, "1171": 1, "1172": 1, "1173": 1, "1175": 1, "1176": 1, "1177": 1, "1178": 1, "1179": 1, "118": 9, "1182": 1, "1184": 1, "1186": 1, "1188": 1, "1189": 1, "1190": 1, "1192": 1, "1194": 1, "1195": 1, "1197": 1, "1199": 1, "11_3": 20, "11ee694744f2552d": 43, "11ff": 35, "12": [9, 30, 35, 36, 38, 40, 54, 57, 59, 61, 62, 68, 69, 102, 109], "120": 9, "1200": [1, 62], "1208": 1, "121": 12, "1210": [1, 40], "1212": 1, "1213": 1, "1218": 1, "1221": 1, "122357444": 13, "1224": 1, "1227": 1, "1228": 1, "123": 38, "1234": 1, "1234567890": 102, "1237": 1, "1243": 1, "1247": 1, "125": 9, "1250": 1, "1251": 1, "1253": 1, "1255": 1, "1256": 1, "1258": 1, "1260": 1, "1261": 1, "1262": 1, "1263": 1, "1266": 1, "1267": 1, "1269": 1, "127": [62, 76], "1270": 1, "1271": 1, "1272": 1, "1273": 1, "1275": 1, "1276": 1, "128": [30, 35, 39, 40, 41, 55, 61, 62, 71, 76], "1280": 1, "1282": 1, "1284": 1, "1286": 1, "1289": 1, "1290": 1, "1291": 1, "1296": 1, "1297": 1, "1298": 1, "12_9": 20, "13": [9, 35, 40, 59, 61, 62, 109], "130": 9, "1302": 1, "1304": 1, "131": 10, "1310": 1, "1313": 1, "1315": 1, "1319": 1, "1320": 1, "1321": 1, "1323": 1, "1325": 1, "1329": 1, "1330": 1, "1331": 1, "1332": 1, "1333": 1, "1334": 1, "1336": 1, "1337": 1, "1339": 1, "1340": 1, "1341": 1, "1342": 1, "1350": 1, "1351": 1, "1352": 1, "1353": 1, "1354": 1, "1355": 1, "1357": 1, "1358": 1, "1359": 1, "136": 54, "1360": 1, "1361": 1, "1362": 1, "1363": 1, "1364": 1, "1368": 1, "1369": 1, "137": 1, "1370": 1, "13720": 37, "13d7934": 43, "14": [35, 43, 54, 57, 59, 61, 62, 68, 94, 109], "140": 9, "14320": 37, "145": 12, "15": [30, 35, 41, 59, 61, 62, 84, 97], "1543569408": 69, "16": [2, 10, 15, 31, 35, 41, 58, 59, 61, 62, 68, 94, 102, 104, 109], "165": 2, "16842758": 84, "17": [9, 35, 38, 54, 59, 61, 62, 68, 94, 102], "17946125": 102, "18": [13, 14, 18, 35, 59, 61, 62], "180": 113, "1860": 109, "188": 94, "19": [1, 9, 35, 61, 62], "191x1": 30, "1951": 109, "1977": 109, "199": 43, "1993": 58, "1994": 86, "1999": 58, "19999": 61, "1c": 35, "1d": [29, 68, 77, 123], "1e": [40, 102, 113], "1e38": 123, "1f": 103, "1j": 123, "1mm": 111, "1x1": 30, "1x2": 30, "2": [2, 3, 6, 7, 9, 10, 11, 12, 14, 20, 26, 30, 31, 33, 35, 36, 37, 38, 40, 41, 42, 43, 54, 55, 56, 57, 59, 60, 61, 62, 64, 65, 66, 68, 69, 70, 71, 72, 74, 76, 78, 79, 81, 84, 86, 87, 90, 92, 93, 94, 98, 102, 103, 104, 105, 108, 109, 110, 111, 113, 115, 116, 117, 118, 119, 123, 124], "20": [9, 30, 35, 57, 59, 61, 62, 68, 93, 97, 105, 108, 119, 122], "200": [9, 12, 20], "2000": [61, 62, 113], "2001": [31, 94], "2004": 58, "2005": 30, "2006": [58, 59, 86, 102], "20061114": 59, "2007": [58, 59], "20070214": 59, "20070220": 59, "20070301": 59, "20070315": 59, "20070425": 59, "20070803": 59, "20070905": 59, "20070917": 59, "20070930": 59, "2008": 59, "20080624": 59, "20080630": 59, "20080710": 59, "20081017": 59, "2009": [38, 58, 59], "20090205": 59, "20090303": 59, "201": 20, "2010": [26, 40, 58, 59], "20100114": [39, 40], "20100412": 59, "20100706": 59, "2011": [7, 8, 9, 12, 58, 59, 94], "2012": [10, 59], "2013": [11, 58], "2014": [58, 59, 119], "2015": [12, 13, 58, 59], "2016": [59, 102], "2017": 59, "2018": [14, 59], "2019": [58, 59], "2020": [6, 14, 57, 59], "2021": 15, "2022": 59, "2023": 59, "2024": 59, "205": [2, 39], "21": [9, 10, 35, 36, 38, 40, 61, 62, 94], "210": 9, "2147483520": 76, "2147483648": 76, "215": 13, "216": 18, "2171": 28, "22": [35, 36, 38, 40, 57, 59, 61, 62], "220": 9, "2210": 38, "23": [7, 9, 59, 61, 62, 68, 76, 113], "230": 9, "233": 94, "24": [7, 10, 41, 55, 59, 61, 62, 64, 68, 76, 81, 87, 103, 116], "240": 9, "24879837036": [61, 62], "249": 1, "25": [6, 54, 59, 61, 62, 74], "254": 71, "255": [1, 71, 92, 111], "256": [10, 35, 90, 111], "25b4125": 26, "26": [2, 8, 9, 59, 61, 62, 109], "2622": 102, "2669": 76, "267": 94, "26aa21a": 43, "27": [2, 26, 59], "272500": 10, "274": 109, "275": 109, "278dd2a": 43, "28": [2, 31, 59, 60, 62, 68], "28404e": 109, "29": [9, 40, 68], "29001ed": 43, "29035": 109, "296": 1, "298": 1, "2d": [2, 12, 36, 68, 86, 102, 114, 119], "2dec1ac": 43, "2e991e8": 42, "2f": 114, "2mm": 2, "2x2x2": 77, "3": [2, 7, 8, 9, 11, 12, 14, 15, 20, 22, 30, 31, 33, 35, 36, 38, 39, 40, 42, 55, 57, 58, 59, 60, 61, 62, 64, 68, 69, 72, 73, 74, 77, 79, 80, 81, 84, 86, 87, 92, 94, 98, 101, 102, 103, 104, 105, 106, 108, 109, 111, 112, 113, 115, 116, 117, 118, 119, 122, 123, 124], "30": [2, 9, 37, 57, 59, 62, 72, 93, 108, 109, 124], "30000": 37, "302": 1, "307": 1, "309": 103, "30ap_10rl_20fh_sense_14_1": 58, "31": [2, 9, 12, 26, 36, 38, 40, 59, 84], "312": [1, 74], "315": 1, "32": [2, 9, 10, 12, 22, 30, 35, 36, 38, 40, 41, 54, 61, 62, 77, 84, 94, 104], "325": 1, "32767": 76, "32768": 76, "328": 1, "329": 1, "33": [2, 9, 26, 74], "332": 1, "336": 1, "339": 1, "34": [9, 26, 62, 102], "340": 1, "345": 1, "347": 1, "348": [1, 61, 62, 69], "35": [9, 26, 42, 54, 61, 62], "3500": 10, "3500000": 77, "35000000": [94, 125], "3511": 74, "352": [1, 62, 103], "355": 1, "358": 1, "36": [2, 9, 54, 61, 62, 68, 102, 109], "360": 1, "363": 1, "364": 2, "365": 1, "370000": 10, "3738": [77, 104], "376adbd": 42, "379": 1, "38": [9, 18, 62], "383": 1, "387500": 10, "39": [9, 109], "391": 1, "393": 1, "399998": 61, "3d": [2, 9, 12, 18, 30, 32, 36, 40, 60, 68, 69, 74, 77, 84, 86, 93, 102, 103, 110, 115, 116, 122], "3mm": 2, "3rd": [12, 22, 59, 93], "3t": 101, "3x1": 30, "3x2": 86, "3x3": [60, 86, 113], "4": [2, 3, 7, 9, 10, 11, 12, 20, 26, 30, 35, 36, 38, 39, 40, 41, 54, 55, 57, 59, 60, 61, 62, 64, 68, 69, 72, 74, 76, 77, 81, 84, 86, 87, 92, 93, 94, 95, 97, 99, 100, 102, 103, 104, 105, 108, 109, 111, 113, 115, 116, 117, 118, 119, 122, 123], "40": [9, 62, 72, 96], "402": 94, "403": 1, "404": 1, "409": 1, "41": [9, 74], "413": 1, "413049": 1, "414": 1, "42": [9, 43, 62, 80], "426": 1, "427": 1, "429": 1, "4294967295": 40, "434": 1, "437": 1, "439": 1, "44": [9, 62, 68], "442175": 1, "446": 1, "45": [9, 42], "453": 2, "455": 1, "46": [9, 42, 62], "460": 1, "478": 1, "48": [30, 42, 62], "480": 9, "485": 1, "486": 1, "49": [9, 10, 74, 102], "493": 1, "494": 1, "495": 1, "4aff2a8": 42, "4d": [2, 9, 12, 18, 30, 31, 84, 93, 102, 116, 122], "4gb": 5, "4th": 109, "4x3": 68, "4x4": [1, 15, 30, 68, 77, 84, 103, 109], "5": [2, 8, 9, 11, 12, 26, 30, 31, 35, 40, 57, 59, 60, 61, 62, 68, 69, 70, 74, 76, 77, 80, 86, 87, 92, 93, 97, 98, 102, 103, 105, 108, 111, 112, 114, 117, 118, 119, 120, 122, 123], "50": [62, 72, 96], "500": 1, "5000": [30, 77], "502": 1, "503": 1, "507": 1, "509": 1, "5090": 113, "50k": 4, "51": [26, 109], "512": [1, 84], "512l": 84, "5134": 86, "514": 1, "516": 1, "517": 1, "517920": 1, "52": [2, 62, 74], "521": 1, "528": 1, "5281": 26, "53": [2, 30, 61, 109], "533": 1, "536": 1, "54": [9, 62], "540": [62, 104], "544": [1, 104], "5492877960205": 2, "5493": 26, "550": 1, "551": 1, "552": 1, "56": [2, 62], "562": 2, "564": 1, "567": 102, "569": 1, "57": [2, 61, 62], "572": 1, "575": 1, "576": 1, "5762787e": 103, "58": 30, "580": 1, "582": 1, "583": 1, "584": 1, "587": 1, "588": 1, "59": 9, "591": 1, "592": 1, "593": 1, "597": 1, "599": 1, "5d": [9, 12], "5mm": 2, "5th": [12, 103], "6": [9, 10, 26, 33, 35, 38, 40, 58, 59, 61, 62, 68, 69, 76, 77, 79, 84, 94, 102, 103, 104, 109, 111, 113, 117, 118, 119, 123], "60": [2, 9, 37], "600": [1, 9], "6000": 9, "600000": 37, "601": 1, "602": 1, "604": 1, "606": 1, "607": 1, "60847": 26, "61": [2, 9, 43], "610": 1, "611": 1, "614": [1, 94], "615": 1, "617": 1, "618": 1, "62": [9, 109], "621": 1, "637": 1, "638": 1, "64": [1, 2, 12, 22, 39, 60, 61, 103, 104, 109], "641": 1, "642": 94, "644": 1, "646": 1, "647": 1, "6482436": 26, "6482473": 26, "6491": 90, "64mm": 61, "65": [10, 94], "651": 1, "653": 1, "655": 1, "658": 1, "65af65": 42, "66": [9, 43, 94], "661338147750939e": 104, "666": 1, "67": 2, "672": 1, "674": 1, "678": 1, "679": 1, "6792": 30, "68": 9, "682": 1, "685": 1, "686": 1, "68f6752": 42, "69": 109, "695": 1, "699": 1, "6ad92e5": 43, "6d8e1e": 42, "6x10": 9, "7": [6, 9, 12, 26, 33, 35, 38, 43, 54, 57, 59, 61, 62, 68, 69, 76, 77, 81, 84, 102, 103, 111, 118, 120, 123], "700": 1, "701": 1, "703": 1, "705": 1, "706": 94, "707": 1, "71": 9, "711": 1, "714": 1, "719": 1, "72": [54, 61, 62], "720": 1, "721fc64": 43, "722": 1, "7229423523": [61, 62], "724": 1, "726": 1, "728": 1, "732": 1, "738": 1, "739": 1, "74": 30, "742": 1, "743": 1, "749": 1, "75": [60, 102], "750": [1, 109], "751": 1, "753": 1, "754": 1, "755": 1, "759": 1, "76": 2, "760": 1, "761": 1, "762": 1, "763": 1, "764": 1, "768": 1, "769": 1, "77": [9, 39, 109], "774": 1, "778": 1, "78": [2, 9, 60], "782": 1, "785": 1, "786": 1, "787": 1, "79": 43, "793": 1, "794": 1, "795": 1, "796": 1, "798": 1, "799": 1, "7beda5a": 42, "7fe0": 38, "7th": 103, "8": [2, 6, 9, 10, 12, 33, 35, 39, 40, 57, 59, 60, 61, 62, 68, 69, 72, 76, 77, 94, 103, 104, 105, 109, 111, 117, 118, 125], "80": 9, "800": 1, "80248": 39, "804": 1, "806": 1, "8080": 34, "809": 1, "81": [2, 109], "811": 1, "815": 1, "818": 1, "8194": 123, "82": [9, 10, 74], "821": 1, "823": 1, "827": 1, "83": 9, "833": 1, "84374": 26, "844": 1, "845": 1, "846": 1, "847": 1, "848": 1, "852": 1, "853": 1, "855102539": [61, 62], "857": 1, "858": 1, "859": 1, "86": [9, 54, 61, 62], "860": 1, "862": 1, "8630830": 43, "865": 1, "866": [1, 2], "87": 9, "883363e": 74, "887": [1, 2], "889": 1, "89": 9, "893": 1, "894": 1, "896": 1, "8b88b34": 92, "8k": [77, 94], "9": [2, 8, 9, 12, 20, 31, 35, 40, 57, 59, 61, 62, 68, 76, 106], "90": 109, "901": 1, "909": 1, "91": [9, 30, 60], "910": 1, "911": 1, "914": 1, "916": 1, "917": 1, "918": 1, "919": 1, "91x109x91": 30, "92": [9, 30], "922": 1, "925": 1, "93": 9, "930": 1, "931": 1, "934": 1, "936": 28, "938": 1, "94510681403e": [61, 62], "946": 1, "947": 1, "948": 1, "949": 1, "95": [9, 62], "955": 1, "956fbab": 42, "958": 1, "96": [41, 55, 61, 62], "963": 1, "964": 1, "967": 1, "97": [9, 30, 54, 61, 62], "972": 1, "98": [10, 109], "980": 1, "983": 1, "99": [7, 9, 55, 64, 68, 80], "991": 1, "996": 1, "996708512306": [61, 62], "9999": 103, "A": [1, 2, 6, 12, 14, 15, 16, 17, 18, 20, 22, 23, 25, 27, 28, 29, 30, 33, 36, 38, 40, 41, 43, 49, 54, 58, 60, 61, 68, 69, 70, 72, 74, 75, 76, 77, 78, 80, 84, 86, 90, 92, 94, 99, 100, 102, 105, 106, 108, 109, 110, 111, 112, 113, 115, 116, 118, 119, 122, 123, 124, 126], "AND": [58, 117], "AS": [35, 58], "AT": [9, 35], "And": [25, 30, 69, 87], "As": [2, 3, 8, 9, 19, 20, 27, 34, 35, 55, 60, 61, 76, 96, 120], "At": [6, 7, 8, 10, 12, 13, 14, 20, 22, 39, 40, 41, 43, 61, 76, 94, 106, 114], "BE": [43, 58], "BUT": 58, "BY": 58, "Be": 37, "Being": [9, 51], "But": [8, 14, 38, 43, 68, 76, 105, 120], "By": [4, 6, 10, 12, 20, 23, 38, 54, 61, 62, 64, 69, 71, 79, 80, 84, 92, 105, 109, 110, 117, 118, 119, 123, 124], "FOR": 58, "For": [2, 3, 4, 7, 8, 9, 10, 12, 14, 15, 20, 22, 26, 27, 29, 30, 35, 36, 37, 38, 40, 41, 42, 43, 48, 51, 54, 55, 56, 57, 60, 61, 62, 66, 68, 69, 71, 72, 76, 77, 80, 82, 84, 87, 92, 93, 94, 102, 103, 105, 108, 109, 111, 113, 115, 116, 119, 120, 122, 123], "IF": 58, "IN": 58, "If": [1, 2, 3, 6, 7, 8, 9, 12, 14, 15, 16, 20, 22, 23, 26, 27, 29, 30, 31, 35, 37, 38, 39, 40, 43, 45, 51, 52, 55, 56, 57, 60, 61, 62, 66, 68, 69, 70, 71, 72, 74, 76, 77, 78, 79, 80, 84, 86, 87, 89, 90, 92, 93, 94, 99, 100, 102, 103, 104, 105, 106, 107, 108, 109, 111, 113, 114, 115, 116, 117, 118, 119, 122, 123, 124], "In": [2, 3, 6, 7, 8, 9, 10, 12, 15, 20, 23, 27, 29, 30, 31, 33, 34, 35, 36, 38, 39, 40, 41, 46, 50, 54, 55, 56, 58, 60, 61, 62, 64, 66, 69, 74, 76, 77, 80, 84, 86, 87, 90, 92, 93, 94, 99, 100, 102, 103, 104, 108, 109, 112, 113, 116, 117, 118, 119, 123, 124], "It": [1, 2, 4, 6, 7, 8, 9, 10, 11, 12, 16, 19, 20, 22, 28, 31, 34, 35, 36, 38, 40, 41, 42, 43, 48, 51, 52, 54, 55, 57, 58, 60, 61, 62, 64, 69, 71, 74, 76, 77, 78, 79, 80, 84, 86, 87, 92, 99, 100, 102, 103, 104, 106, 109, 116, 117, 118, 119, 122, 123, 124], "Its": [16, 119], "NO": 58, "NOT": [9, 58, 105], "No": 84, "Not": [7, 15, 66, 76], "OF": [35, 58], "ON": 58, "ONE": [84, 109], "OR": [9, 15, 58, 70], "Of": [8, 9, 11, 12, 27, 35, 41, 42, 107], "On": [1, 2, 7, 12, 43, 57, 64, 85, 102], "One": [2, 4, 9, 10, 11, 12, 13, 20, 27, 69, 77, 84], "Or": [1, 7, 8, 10, 20, 22, 42, 43, 56, 61, 107, 123], "SUCH": 58, "Such": [35, 72], "THAT": 43, "THE": 58, "TO": 58, "That": [2, 7, 8, 10, 14, 20, 30, 35, 38, 69, 108, 119, 123], "The": [1, 4, 6, 7, 8, 10, 11, 13, 14, 16, 17, 18, 19, 20, 22, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 36, 38, 39, 40, 41, 42, 44, 45, 46, 49, 51, 52, 53, 54, 55, 56, 58, 59, 60, 64, 65, 66, 68, 70, 71, 72, 74, 76, 77, 79, 80, 82, 84, 85, 86, 87, 89, 90, 91, 92, 94, 99, 100, 101, 102, 103, 104, 106, 108, 109, 110, 111, 113, 115, 116, 117, 118, 119, 122, 123, 124, 126], "Their": 20, "Then": [2, 7, 14, 20, 26, 31, 36, 38, 39, 40, 41, 43, 52, 53, 57, 86], "There": [2, 3, 4, 6, 9, 10, 11, 18, 20, 22, 28, 30, 31, 34, 35, 36, 39, 40, 41, 43, 47, 48, 49, 61, 62, 65, 74, 76, 77, 80, 84, 86, 87, 102, 109], "These": [1, 2, 3, 9, 10, 12, 19, 20, 31, 35, 38, 40, 44, 48, 64, 69, 72, 77, 88, 106, 110], "To": [1, 2, 5, 6, 8, 9, 10, 11, 15, 20, 22, 29, 35, 36, 38, 40, 41, 42, 43, 56, 57, 62, 66, 72, 76, 80, 86, 92, 96, 103, 105, 106, 109, 119], "WILL": 43, "WITH": 58, "Will": [76, 94, 120], "With": [7, 15, 18, 22, 29, 89, 92, 114], "_": [26, 38, 40, 107, 120, 123], "_______________________________________________________________________": 30, "____________________________________________________________________________": 30, "__array__": 1, "__call__": 82, "__class__": 111, "__eq__": 124, "__file__": 84, "__getattribute__": 123, "__getitem__": [1, 8, 15, 102, 123, 124], "__init__": [1, 3, 8, 26, 65, 68, 69, 70, 71, 72, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 87, 88, 89, 92, 94, 96, 99, 100, 101, 102, 103, 104, 105, 106, 108, 109, 110, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125], "__iter__": 123, "__ne__": 124, "__repr__": 1, "__setitem__": [123, 124], "__str_": 124, "__str__": [1, 124], "__version__": [1, 107], "_bold": 15, "_cach": 27, "_compress": [0, 1, 65], "_data": [7, 8], "_data_cach": 14, "_data_type_cod": 72, "_dataobj": 14, "_default_ord": 70, "_fdata_cach": 14, "_field_recod": 124, "_gzip_open": 106, "_name": 106, "_nipi": 85, "_no_reset": 105, "_not_": 102, "_nulllock": 90, "_positive_slic": 90, "_version": 1, "_version_extra": 26, "_version_micro": 26, "_version_minor": 26, "_zstd_open": 106, "a49498c699a3fda5d635cc1fa222ffc686ea3b5d04b84a3166c4cab0c57b": 58, "a7ff2e5": 42, "a815645": 43, "a_": [9, 38, 40], "a_i": [9, 38], "a_j": 38, "a_modul": 121, "a_prim": 29, "a_slic": 60, "ab": [15, 72, 76, 78, 106], "abandon": 19, "abbrev": 42, "abbrevi": [26, 35], "abc": [2, 77, 119], "abcd": 2, "abil": [1, 3, 19, 27, 43], "abl": [1, 2, 3, 7, 8, 9, 10, 11, 12, 15, 19, 20, 22, 27, 35, 42, 51, 71, 76, 111], "able_int_typ": 65, "abort": [39, 43], "about": [1, 2, 4, 6, 7, 9, 10, 11, 12, 13, 14, 18, 20, 22, 23, 26, 28, 31, 33, 35, 38, 40, 41, 43, 52, 56, 58, 60, 64, 65, 68, 77, 79, 86, 102, 105, 119], "abov": [2, 4, 7, 8, 9, 12, 19, 20, 22, 23, 26, 30, 31, 35, 36, 38, 39, 40, 42, 43, 51, 58, 60, 62, 76, 77, 79, 84, 86, 89, 90, 102, 105, 109, 111, 114, 115, 123], "abs_arr": 76, "absenc": 15, "absent": [9, 35, 38, 40], "absolut": [76, 78, 90, 102], "absorb": 22, "abstract": [20, 25, 28, 30, 35, 77, 87, 119, 125], "abstractclassmethod": 65, "abstractli": 110, "ac": [2, 9, 15], "academ": 86, "accept": [1, 2, 10, 14, 16, 19, 30, 35, 76, 82, 106, 107, 124], "access": [1, 3, 4, 27, 28, 35, 37, 41, 45, 51, 52, 55, 56, 57, 61, 62, 66, 69, 70, 74, 77, 79, 80, 81, 84, 90, 92, 99, 100, 102, 103, 104, 105, 107, 117, 118, 119, 123, 124], "accessor": [1, 105], "accid": 14, "accident": [1, 4, 53], "accommod": 1, "accord": [9, 19, 23, 30, 35, 40, 62, 90, 102, 108, 111, 119], "accordingli": [1, 6, 90, 93], "account": [43, 46, 102, 109, 116], "accur": 1, "achiev": [16, 40, 43], "acknowledg": [25, 26], "acquir": [12, 28, 33, 103], "acquisit": [9, 12, 28, 31, 33, 40, 102, 103, 109], "acquisition_mod": 84, "acquisition_typ": 84, "acquisitionmatrixtext": 40, "acquisitionnumb": [31, 40], "acquisitiontim": 10, "acr": 2, "across": [2, 9, 10, 12, 20, 22, 26, 28, 30, 35, 84, 93, 109, 111], "act": [9, 10, 11, 70], "action": [1, 45, 58, 90], "activ": [6, 16, 23, 26, 49, 87, 116], "actual": [1, 2, 6, 7, 8, 10, 11, 20, 30, 35, 36, 42, 43, 55, 77, 78, 80, 90, 102, 119, 123], "actual_arr": 7, "ad": [1, 2, 3, 6, 9, 10, 19, 22, 23, 25, 26, 42, 43, 52, 62, 77, 79, 80, 89, 90, 92, 109, 111, 119], "adapt": [20, 71, 84, 90, 98, 111, 112, 114], "adapt_affin": 65, "adc": 1, "add": [1, 2, 4, 10, 11, 12, 19, 20, 22, 25, 26, 31, 36, 42, 43, 49, 51, 52, 53, 68, 69, 79, 85, 90, 94, 102, 105, 106, 109, 114, 117, 118, 119, 123, 124], "add_cod": [65, 123], "add_gifti_data_arrai": [65, 94], "add_new": 37, "addext": 89, "addit": [2, 6, 9, 10, 22, 23, 28, 38, 56, 66, 116, 119, 123, 125], "addition": [1, 22, 61], "address": [1, 6, 16, 22, 42], "adher": 23, "adjac": [15, 38, 102, 110], "adjust": [36, 61, 102, 111, 116, 123], "admin": 43, "administr": 15, "adopt": [22, 28], "advanc": [25, 50, 57, 70], "advantag": [1, 2], "advic": [1, 3, 6, 57], "advis": [22, 43, 58], "advoc": 23, "ae": [26, 35], "af5bd6": 20, "aff": [7, 68, 103, 108, 109, 123], "aff2": 103, "aff2axcod": [54, 61, 65], "aff_plu": 68, "aff_tv2ra": 119, "affect": [7, 14, 16, 43, 70, 74, 87, 109, 116], "affin": [0, 1, 3, 7, 9, 10, 13, 15, 27, 30, 32, 36, 41, 54, 55, 59, 60, 61, 64, 65, 66, 69, 74, 77, 84, 87, 92, 93, 94, 99, 100, 102, 103, 104, 108, 109, 110, 111, 115, 116, 117, 118, 119, 122, 123, 126], "affine_so_far": 2, "affine_to_rasmm": [65, 119], "affine_transform": [111, 115], "affineerror": 65, "afil": 79, "afni": [9, 13, 28, 56, 66, 68, 74], "afni_atlas_spac": 74, "afniarrayproxi": 65, "afniextension1": 9, "afnihead": 65, "afniheadererror": 65, "afniimag": 65, "afniimageerror": 65, "after": [2, 6, 7, 10, 11, 14, 19, 22, 23, 26, 35, 39, 40, 43, 45, 62, 69, 72, 77, 80, 81, 90, 91, 92, 94, 102, 103, 104, 105, 119, 123], "ag": 35, "again": [1, 6, 8, 20, 32, 35, 36, 42, 43, 54, 61, 62, 80, 105, 116, 120], "against": [1, 10, 20, 23, 27, 31, 34, 76, 100, 109], "agg_data": [1, 15, 65, 94], "aggreg": [94, 109], "ago": 42, "agre": [2, 4, 23, 109], "agreement": [20, 23], "ahead": [40, 43], "ahsburn": 40, "aiaa": 113, "aim": [1, 22, 78, 109], "ain": 6, "aka": 57, "alarm": 1, "alejandro": 1, "alert": 81, "alert_future_error": 65, "alexandr": [1, 11, 56], "alexi": 1, "algorithm": [1, 4, 15, 19, 20, 22, 34, 38, 40, 62, 69, 79, 90, 113, 117], "alia": [1, 42, 43, 69, 74, 77, 78, 84, 87, 92, 94, 99, 100, 103, 104, 109, 116, 117, 118, 123], "alias": [1, 61, 103, 105, 116, 123], "aliases1": 123, "aliases2": 123, "alien": 19, "align": [1, 2, 12, 15, 28, 54, 62, 74, 103, 111, 115, 118, 126], "all": [1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 18, 19, 20, 22, 23, 26, 27, 30, 35, 38, 40, 41, 42, 43, 52, 53, 55, 56, 58, 60, 61, 62, 64, 66, 71, 76, 77, 78, 79, 80, 84, 86, 89, 90, 93, 94, 96, 97, 102, 103, 105, 106, 109, 113, 114, 115, 116, 118, 119, 123, 124], "all_ful": 90, "allclos": [86, 87, 102, 103, 113, 117], "allianc": 58, "alloc": [1, 68, 119], "allow": [1, 2, 6, 7, 9, 10, 12, 13, 15, 23, 35, 41, 45, 55, 61, 69, 70, 71, 79, 84, 87, 89, 90, 93, 99, 102, 103, 107, 113, 119], "allow_step": 78, "allow_unknown": 103, "allur": 1, "almost": [2, 60, 103], "along": [1, 9, 12, 16, 35, 38, 54, 60, 61, 68, 74, 77, 86, 90, 93, 102, 109, 111, 114, 116, 119], "alongsid": 119, "alpha": [77, 86, 92, 94], "alreadi": [1, 4, 7, 11, 12, 20, 26, 29, 30, 34, 35, 43, 51, 52, 55, 57, 61, 77, 80, 86, 87, 90, 93, 105, 113, 116, 119, 123], "also": [1, 2, 3, 6, 7, 8, 10, 11, 12, 13, 14, 18, 19, 20, 22, 23, 26, 28, 29, 30, 31, 34, 35, 36, 38, 41, 42, 43, 53, 55, 56, 58, 60, 61, 62, 64, 66, 69, 70, 74, 76, 77, 79, 80, 84, 86, 87, 90, 92, 99, 100, 102, 103, 104, 108, 109, 113, 116, 117, 118, 119, 123, 124], "altern": [1, 6, 8, 10, 13, 22, 77, 90, 103, 122], "although": [2, 78, 99, 100, 113], "alwai": [3, 4, 7, 8, 9, 12, 13, 17, 19, 20, 25, 27, 30, 35, 38, 39, 54, 55, 61, 62, 74, 76, 80, 84, 90, 92, 94, 95, 103, 109, 112, 118, 119, 123, 126], "am": [43, 52, 74], "ambigu": [3, 86], "amd64": 26, "amend": 43, "amirbekian": [1, 56], "among": [1, 15, 19, 23, 28, 76, 78], "amongst": 37, "amount": [10, 22, 36, 77, 86, 94, 119, 125], "an": [1, 3, 4, 6, 7, 8, 9, 10, 11, 12, 14, 16, 18, 19, 20, 22, 23, 26, 27, 28, 29, 30, 31, 33, 34, 35, 36, 37, 40, 41, 42, 43, 49, 52, 54, 55, 56, 57, 58, 60, 61, 62, 64, 66, 68, 69, 70, 71, 72, 74, 76, 77, 78, 79, 80, 81, 84, 86, 87, 89, 90, 92, 93, 94, 96, 98, 99, 100, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 113, 115, 116, 117, 118, 119, 123, 124, 125, 126], "an_int": 124, "ana_img": 61, "anaconda": 57, "analysi": [1, 23, 28], "analyz": [0, 1, 8, 27, 29, 56, 57, 60, 61, 65, 66, 70, 72, 87, 89, 103, 105, 116, 117, 118, 123], "analyze_imag": 61, "analyze_map": 69, "analyze_to_dicom": 40, "analyzehead": [65, 72, 117, 118], "analyzeimag": [61, 65, 87, 103, 116, 118], "anat": [15, 74], "anat_img": 2, "anat_img_data": 2, "anat_vox_cent": 2, "anatom": [2, 18, 27, 38, 74, 77], "anatomicalstructureprimari": 15, "andersson": 40, "andrew": [1, 56], "angl": [2, 68, 86, 113], "angle_axis2eul": 65, "angle_axis2mat": [65, 86], "angle_axis2quat": 65, "angul": 109, "angular_compress": 84, "ani": [1, 2, 4, 6, 7, 9, 10, 12, 14, 15, 16, 19, 20, 22, 23, 26, 27, 30, 35, 40, 41, 42, 43, 44, 51, 52, 56, 57, 58, 60, 61, 66, 68, 69, 70, 71, 72, 74, 75, 76, 77, 79, 80, 84, 86, 87, 89, 90, 91, 92, 94, 96, 102, 103, 105, 106, 108, 109, 110, 111, 116, 119, 121, 122, 123, 124], "anib": [1, 56], "annex": 35, "annoi": 8, "annot": [1, 26, 56, 66, 92], "announc": [26, 56, 102], "anoth": [2, 7, 9, 10, 11, 16, 18, 20, 26, 27, 35, 38, 40, 42, 61, 69, 87, 90, 92, 102, 103, 109, 115, 119], "another_imag": [30, 61], "answer": [14, 20], "ant": 28, "anterior": [2, 12, 18, 54, 60, 77, 94, 109], "any_other_list": 9, "anymor": 1, "anyon": [6, 19, 23, 27, 43, 56], "anyth": [4, 10, 41, 43, 52, 56, 62, 72, 90], "anywai": [8, 90], "anywher": 7, "ap": [65, 79, 109], "apart": [69, 90], "api": [6, 7, 9, 10, 12, 13, 17, 19, 20, 23, 25, 41, 42, 55, 56, 59, 64, 66, 70, 77, 84, 87, 109, 116, 119], "app": [26, 57], "appar": [18, 34, 36, 39, 40, 102], "appeal": [19, 23], "appear": [1, 6, 9, 12, 18, 23, 31, 35, 40, 51, 56, 58, 60, 62, 68, 74, 76, 77, 84, 86, 87, 89, 96, 102, 103, 109, 121, 123], "append": [11, 37, 40, 65, 68, 77, 89, 90, 119], "append_cifti_vertic": [65, 77], "append_diag": 65, "appendix": [22, 56], "appl": 26, "appli": [1, 6, 7, 10, 13, 14, 20, 22, 28, 29, 30, 36, 38, 40, 56, 60, 62, 65, 68, 69, 71, 72, 74, 77, 80, 86, 90, 99, 100, 102, 103, 104, 108, 109, 110, 111, 113, 115, 116, 119, 123, 126], "applic": [1, 7, 9, 29, 35, 68, 77, 78, 94], "applies_to": 9, "applies_to_matrix_dimens": 77, "appliestomatrixdimens": 77, "apply_affin": [1, 2, 60, 65, 119], "apply_orient": 65, "apply_read_sc": 65, "approach": [6, 10, 15, 16], "appropri": [6, 10, 15, 23, 61, 77, 81, 103], "approv": [19, 23], "approxim": [1, 8, 29, 54, 103], "appveyor": 1, "apr": 59, "april": [14, 59], "aprim": 29, "apt": [20, 47, 57], "ar": [1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19, 20, 22, 23, 26, 27, 28, 29, 30, 31, 34, 35, 36, 38, 39, 40, 41, 42, 43, 44, 45, 47, 48, 49, 51, 52, 53, 54, 55, 56, 57, 58, 60, 61, 62, 64, 65, 66, 68, 69, 71, 72, 74, 75, 76, 77, 78, 79, 80, 84, 86, 87, 89, 90, 92, 93, 94, 101, 102, 103, 104, 105, 108, 109, 110, 111, 113, 114, 115, 117, 118, 119, 120, 122, 123, 124, 126], "arang": [7, 11, 55, 61, 62, 64, 68, 69, 71, 81, 87, 90, 93, 103, 116, 123], "arbitrari": [2, 9, 53, 62, 78, 86, 102, 113], "architectur": [1, 11], "archiv": [1, 6, 9, 20, 22, 26, 34, 56, 58, 66, 79], "arctan": 86, "are_values_differ": 65, "area": [4, 6, 35, 60], "aren": [4, 9], "arg": [30, 68, 70, 71, 74, 75, 76, 77, 78, 79, 81, 82, 83, 87, 88, 89, 92, 94, 96, 99, 101, 102, 103, 105, 106, 108, 109, 110, 116, 119, 121, 124, 125], "argsort": 1, "argu": 22, "argument": [1, 12, 30, 62, 66, 68, 71, 77, 79, 80, 82, 86, 87, 88, 90, 94, 98, 102, 103, 106, 107, 111, 119, 123, 125], "argv": 4, "ariel": [1, 56], "aris": [58, 86, 90], "arithmet": 15, "arm64": 1, "arnaud": 1, "around": [1, 2, 4, 6, 7, 9, 14, 16, 19, 27, 36, 40, 76, 86, 103, 113, 124, 125], "arr": [7, 8, 12, 69, 71, 76, 81, 90, 92, 103, 108, 116, 118, 123], "arr2": 123, "arr_slic": 30, "arrai": [1, 3, 8, 9, 10, 11, 12, 14, 15, 29, 30, 36, 38, 39, 40, 41, 42, 54, 56, 59, 60, 62, 64, 65, 66, 68, 69, 70, 71, 73, 74, 76, 77, 78, 80, 84, 86, 90, 92, 93, 94, 99, 100, 102, 103, 104, 108, 109, 110, 111, 113, 114, 115, 116, 117, 118, 119, 122, 123, 124, 126], "arrang": [12, 20, 35, 36, 38, 54, 60, 102], "array2str": 94, "array_data": [55, 61, 62], "array_equ": [61, 74, 94], "array_from_fil": 65, "array_head": 62, "array_img": [55, 61, 62], "array_index_order_cod": 94, "array_lik": 77, "array_sequ": 65, "array_to_fil": [1, 65, 73], "arrayimgt": 80, "arraylik": [65, 74, 80, 99, 100, 109, 116, 123], "arrayproxi": [0, 1, 3, 55, 61, 64, 65, 69, 73, 74, 80, 84, 92, 99, 100, 118], "arraysequ": [1, 65], "arraywrit": [0, 1, 65], "arriv": [35, 94], "art": 16, "articl": [45, 76], "artifact": [1, 61], "as_analyze_map": [65, 69, 109], "as_byteswap": [65, 92, 124], "as_closest_canon": [54, 65], "as_filenam": 27, "as_homogen": 110, "as_int": [1, 65], "as_niminc": 12, "as_reori": [65, 103, 116], "asanyarrai": [14, 69, 74, 77, 80, 92, 99, 100, 103, 104, 109, 116, 117, 118], "asarrai": [7, 55, 70], "ascconv": [1, 65], "ascconv_str": 102, "ascconvparseerror": 65, "ascend": [9, 38, 40], "ascertain": 6, "ascii": [1, 9, 94, 109, 114, 119], "asconvparseerror": 102, "ashburn": [40, 117], "asin": 86, "ask": [3, 6, 19, 20, 27, 30, 35, 38, 46, 55, 61, 64, 90, 107], "asl": 109, "aspect": [34, 122], "asscalar": 1, "assert": [9, 27, 42, 77, 80, 120], "assign": [1, 6, 35, 77, 102, 119], "assign2atom": 65, "assign_ast": 102, "associ": [1, 2, 3, 7, 10, 13, 14, 15, 18, 30, 35, 58, 61, 64, 69, 74, 75, 77, 80, 84, 87, 92, 94, 98, 99, 100, 103, 104, 108, 109, 116, 117, 118, 119], "assum": [1, 8, 9, 12, 15, 19, 27, 30, 36, 38, 40, 51, 62, 69, 70, 77, 79, 90, 102, 103, 111, 113, 114, 119, 122, 123], "assumpt": [1, 62], "ast": 102, "astyp": [14, 61, 69, 71, 76, 123], "ata": 63, "atal": 102, "atan2": 86, "atla": [2, 62], "atlas": [20, 74], "atol": [102, 113], "atom": [35, 65], "attach": [12, 20, 28, 58, 61], "attempt": [1, 6, 31, 64, 68, 72, 77, 89, 90, 103, 116], "attent": 43, "attitud": 86, "attr": [77, 94, 102, 125], "attribut": [1, 7, 9, 14, 15, 33, 37, 38, 61, 62, 64, 69, 70, 71, 72, 74, 75, 77, 80, 81, 87, 88, 92, 94, 95, 99, 100, 102, 103, 104, 105, 109, 111, 115, 116, 117, 118, 119, 126], "attributeerror": [1, 64, 79, 102, 121, 123, 124], "aug": [26, 59], "august": 59, "austria": 58, "auth": 1, "author": [1, 2, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 20, 26, 27, 58, 92], "auto": [1, 22, 105], "auto_attr": [1, 105], "autom": [1, 26], "automat": [1, 4, 10, 19, 51, 62, 77, 92, 103], "autosummari": 59, "aux_fil": [61, 62, 69, 103, 104, 117, 118], "avail": [1, 6, 11, 12, 20, 22, 23, 26, 35, 41, 47, 56, 58, 66, 87, 94, 102, 103, 105, 112, 116, 123], "averag": 40, "avg": 109, "avoid": [1, 6, 7, 8, 9, 11, 12, 15, 19, 20, 35, 43, 55, 60, 76, 81, 90, 103, 116, 118, 123], "avx512": 1, "aw": [4, 9, 71], "awai": [55, 69, 70, 90], "awar": [14, 74], "awesom": [17, 28], "ax": [1, 9, 15, 17, 21, 25, 28, 38, 42, 54, 65, 68, 86, 90, 93, 94, 95, 102, 103, 108, 109, 111, 115, 116, 122, 126], "axcod": 108, "axcodes2ornt": 65, "axi": [1, 2, 15, 38, 42, 54, 60, 61, 65, 68, 69, 74, 86, 90, 93, 102, 103, 108, 109, 113, 115, 116, 119, 122], "axial": 122, "axial_samp_mod": 84, "axis_and_angl": 113, "axis_indic": 9, "axis_label": 12, "axis_length": 9, "axis_metadata": 9, "axis_nam": 9, "axisindex": 42, "b": [2, 7, 9, 15, 26, 29, 35, 38, 40, 43, 58, 61, 62, 68, 75, 77, 86, 90, 102, 103, 104, 106, 108, 109, 112, 119, 122, 123], "b0": 102, "b1": 58, "b2q": 65, "b605216": 42, "b_matrix": [65, 102], "b_val": 109, "b_valu": [65, 102], "b_vector": [65, 102, 109], "back": [1, 2, 4, 8, 9, 10, 14, 18, 26, 27, 29, 34, 35, 40, 43, 52, 55, 59, 61, 87, 102, 103, 108, 116, 119, 123], "backend": 116, "background": [21, 25, 54, 60, 97, 126], "backport": [1, 22], "backup": 43, "backward": [6, 7, 13, 20, 22, 90, 112], "bad": [3, 6, 26, 72, 83], "bad_fileobj": 124, "badg": [1, 26, 56, 66], "badli": 35, "bago": [1, 56], "bail": 40, "baker": [1, 56], "balling": 56, "balls1": [4, 58], "bar": [89, 113], "baratz": [1, 56], "bare": 89, "barr": 34, "base": [1, 9, 10, 11, 12, 19, 23, 28, 30, 38, 40, 43, 58, 62, 68, 69, 70, 71, 72, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 87, 88, 89, 90, 92, 94, 96, 99, 100, 101, 102, 103, 104, 105, 106, 108, 109, 110, 113, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125], "base64": 1, "base_dir": 83, "base_format": 11, "base_path": 79, "baseclass": 103, "baselin": 35, "bashism": 1, "basi": [1, 8, 15, 27, 38, 40, 62, 69, 76, 103, 104, 123], "basic": [1, 51, 57, 69, 84, 90, 117, 118, 125], "basil": [1, 56], "batteri": 72, "batteryrunn": [0, 65, 96], "battl": 34, "bauer": [1, 56], "bc": 1, "bdist32": 26, "bdist64": 26, "bear": 60, "beat": 20, "becaus": [1, 2, 4, 6, 7, 8, 9, 10, 12, 14, 19, 20, 26, 28, 29, 31, 33, 34, 35, 36, 38, 40, 43, 51, 54, 55, 57, 60, 61, 62, 68, 69, 74, 76, 77, 79, 80, 86, 90, 94, 102, 103, 113, 118, 119, 123, 124], "becom": [1, 9, 19, 20, 23, 27, 60, 81, 86, 105, 123], "becq": [1, 56], "bed": [2, 38], "bed_elev": 84, "bed_posit": 84, "been": [1, 4, 6, 7, 9, 12, 19, 20, 21, 25, 34, 36, 43, 51, 56, 58, 61, 62, 76, 77, 80, 81, 90, 93, 102, 103, 104, 105, 108, 111, 112, 119], "beer": 56, "befor": [1, 4, 5, 6, 7, 10, 12, 19, 22, 23, 26, 36, 43, 55, 61, 62, 71, 77, 79, 81, 90, 93, 94, 102, 103, 119, 123], "began": 102, "begin": [1, 2, 6, 9, 36, 38, 39, 40, 92, 102, 103, 106, 108, 119], "behav": [7, 55, 77, 119], "behavior": [1, 12, 14, 30, 55, 62, 69, 70, 71, 74, 80, 84, 92, 93, 94, 99, 100, 102, 109, 118, 120, 122, 123], "behind": [2, 19, 27, 49, 60], "being": [1, 2, 3, 6, 9, 10, 11, 12, 14, 19, 20, 31, 35, 36, 38, 60, 69, 70, 74, 80, 84, 87, 90, 92, 94, 99, 100, 102, 109, 110, 111, 118, 119, 123], "believ": [20, 38, 40], "belong": [19, 77, 92, 103], "below": [4, 6, 7, 9, 19, 20, 23, 35, 38, 39, 58, 72, 76, 77, 82, 84, 86, 102, 108, 109, 113, 123, 124], "ben": [1, 56], "bench": [65, 73], "bench_array_to_fil": 65, "bench_arrayproxy_sl": 65, "bench_fileslic": [65, 90], "bench_finite_rang": 65, "bench_load_sav": 65, "benchmark": [0, 1, 65, 66, 90], "benefit": [11, 16, 20, 42, 80, 90], "benjamin": [1, 56], "bennet": [1, 56], "bennett": 1, "berkelei": [6, 26], "bertrand": 56, "best": [6, 20, 29, 31, 35, 52, 57, 62, 71, 76, 84, 85, 90, 103, 108, 123], "best_float": 65, "best_typ": 76, "best_write_scale_ftyp": 65, "beta": [15, 86], "better": [1, 3, 6, 13, 38, 43, 48, 52, 76, 90, 111], "better_float_of": 65, "better_typ": 123, "between": [1, 7, 9, 10, 11, 12, 14, 15, 19, 23, 29, 30, 33, 35, 38, 39, 40, 60, 61, 68, 69, 71, 74, 76, 77, 78, 84, 87, 90, 92, 94, 99, 100, 102, 103, 104, 109, 116, 117, 118, 119, 122, 126], "bewar": 26, "beyond": 6, "bf": [22, 52], "biap": [1, 15, 25, 28], "biap1": [8, 17, 25], "biap2": [17, 25], "biap3": [17, 25, 28], "biap4": [17, 25, 28], "biap5": [17, 25], "biap6": [17, 25, 28], "biap7": [17, 25], "biap8": [17, 25], "biap9": [17, 25], "biap_": 6, "biap_0000": 6, "bic": 26, "big": [1, 23, 26, 84, 92], "biiig": 8, "bin": [26, 124], "bin_siz": 84, "binari": [1, 26, 35, 58, 62, 69, 77, 84, 87, 90, 91, 92, 103, 104, 109, 116, 117, 118, 119, 124], "binary128": [1, 76], "binary_logarithm": 76, "binaryblock": [65, 69, 77, 84, 92, 99, 100, 103, 104, 117, 118, 124], "binaryio": 103, "binblock2": [69, 117, 118], "binblock3": [69, 117, 118], "binopen": 1, "bio": 123, "biol": 102, "bipe": 38, "birth": 35, "bit": [1, 7, 9, 20, 22, 27, 35, 41, 60, 61, 77, 94, 103, 104, 109, 111], "bitbucket": 4, "bitpix": [61, 62, 69, 72, 103, 104, 117, 118], "bitsalloc": 40, "bitsstor": 40, "bk": 22, "black": 2, "blake": [1, 56], "blank": [69, 84], "blob": [10, 20, 26, 92, 109], "block": [2, 35, 36, 61, 69, 77, 84, 90, 92, 102, 103, 104, 117, 118, 120, 122, 123, 124], "block_no": 84, "block_siz": 123, "blow": 78, "blue": [1, 2, 22, 42, 77, 94], "bluedynam": 58, "blunt": 22, "blur": 111, "bm": 77, "bm_cortex": 77, "bm_full": 77, "bm_thal": 77, "bmatrix": [2, 36, 38], "bob": 9, "bodi": 6, "bogu": 1, "boil": [8, 9], "bold": [15, 42], "bolu": 35, "bomber": 65, "bombererror": 65, "book": [47, 49], "bool": [66, 68, 69, 71, 74, 77, 79, 80, 82, 86, 87, 88, 89, 90, 92, 95, 99, 102, 103, 106, 107, 109, 110, 113, 116, 117, 118, 121, 123, 124], "bool_": 1, "boolean": [1, 77, 102, 103, 119], "border": 2, "bore": [1, 2], "bosch": [1, 56], "botch": 43, "both": [1, 2, 4, 9, 10, 19, 20, 23, 35, 39, 40, 41, 55, 62, 69, 76, 77, 86, 88, 102, 103, 104, 109, 118, 119, 123], "bottom": [2, 18, 38, 60, 103], "bouix": 102, "bound": [1, 2, 90, 103], "boundari": [15, 39, 102, 111], "box": [1, 2, 63, 102], "bq": 37, "br": 42, "brain": [1, 2, 12, 15, 28, 58, 60, 69, 77, 92, 111], "brain_model": [65, 77], "brain_stem": 77, "brain_structur": 77, "brainmodel": [1, 77], "brainmodelaxi": [15, 65], "brainordin": 77, "brainstructur": 77, "brainvoyag": [12, 21, 25], "branch": [16, 19, 22, 26, 42, 46, 49, 51, 52, 53, 57], "branching_fract": 84, "branchnam": 43, "braun": [1, 56], "break": [1, 2, 16, 22, 39, 40, 77, 94, 102, 105, 120], "breakag": 1, "breaker": 69, "brendan": [1, 10, 28, 56], "brett": [1, 4, 7, 8, 9, 10, 12, 13, 14, 23, 42, 56, 58], "brick": 74, "brick_typ": 74, "brief": 20, "briefli": 6, "brik": [56, 66, 74], "brikhead": [0, 65], "bring": [2, 19, 43, 119], "broad": 28, "broadcast": 90, "broader": 6, "broken": [1, 2, 40, 76], "brows": 6, "bryan": 86, "bs_wstr": 124, "bsd": [31, 34, 56, 66], "bstr": 87, "btrun": 72, "buf": 102, "buffer": [1, 12, 77, 90, 94, 102, 106, 119, 125], "buffer_s": [77, 94, 119, 125], "bufsiz": 91, "bug": [19, 22, 42, 43, 52, 57, 59, 76, 102], "bugfix": [1, 22, 43], "bugtrack": 22, "bui": 7, "build": [1, 3, 6, 9, 20, 26, 28, 31, 41, 57, 119], "buildbot": 26, "builder": 26, "buildslav": 26, "built": [1, 26, 62, 90, 106], "builtin": 1, "bullet": 16, "bump": 26, "burden": [1, 15, 19], "burn": [1, 56], "busi": 58, "butil": 65, "button": [26, 43, 45], "buxfix": 43, "bv": 21, "bval": [9, 109], "bvec": [9, 109], "bvqxtool": 18, "bw": 22, "bxh": 9, "bystestr": 103, "byte": [1, 9, 18, 20, 30, 35, 39, 40, 60, 62, 70, 73, 76, 77, 84, 87, 90, 91, 92, 94, 102, 103, 106, 119, 123, 124, 125], "byte_str": 91, "bytearrai": 1, "byteord": 94, "byteorder_str": 74, "bytesio": [69, 87, 103, 106, 116, 123, 124], "bytestr": [87, 103, 125], "byteswap": [69, 103, 123, 124], "bz2": [73, 89, 106, 123], "bz2_def": [65, 106], "bz2file": [1, 106], "c": [1, 2, 8, 9, 12, 15, 26, 29, 30, 33, 34, 35, 36, 37, 38, 40, 42, 43, 52, 56, 57, 58, 62, 68, 69, 70, 71, 73, 74, 76, 80, 81, 84, 85, 86, 90, 92, 94, 99, 100, 102, 103, 109, 114, 116, 118, 123, 124], "c99": 76, "c_char_arrai": 90, "c_x": 36, "c_y": 36, "c_z": 36, "cach": [1, 7, 14, 27, 59, 71, 80, 83, 86, 102, 103, 105, 119], "cache_build": 119, "cached_properti": [1, 105], "cachingerror": 65, "cal": 117, "cal_max": [1, 61, 62, 69, 103, 104, 117, 118], "cal_min": [1, 61, 62, 69, 103, 104, 117, 118], "cal_unit": [69, 117, 118], "calc_scal": [65, 71], "calc_slicedef": 65, "calcul": [1, 9, 14, 28, 36, 38, 39, 40, 62, 63, 68, 71, 76, 86, 90, 101, 102, 108, 109, 111, 113], "calculate_dwell_tim": 65, "calculate_scal": 1, "calibr": 117, "calibration_unit": 84, "calibration_units_typ": 84, "call": [1, 2, 7, 9, 12, 13, 14, 19, 20, 22, 26, 27, 30, 31, 34, 35, 36, 38, 39, 40, 41, 42, 43, 54, 55, 60, 61, 62, 64, 69, 71, 76, 77, 80, 82, 84, 86, 87, 90, 92, 94, 102, 103, 105, 107, 109, 119, 121, 123, 124], "callabl": [34, 72, 82, 90, 107, 119, 123], "caller": 119, "cal\u00e1bkov\u00e1": [1, 56], "came": [2, 20, 31], "cameron": [1, 56], "camino": 11, "can": [1, 2, 4, 5, 6, 7, 8, 9, 11, 14, 15, 18, 19, 20, 22, 23, 25, 26, 27, 28, 29, 30, 34, 35, 36, 37, 38, 39, 41, 42, 43, 49, 51, 52, 53, 54, 55, 56, 57, 60, 61, 62, 63, 64, 68, 69, 70, 71, 72, 74, 76, 77, 78, 79, 80, 84, 86, 87, 90, 91, 92, 94, 99, 100, 102, 103, 104, 105, 106, 107, 108, 109, 110, 112, 113, 114, 115, 116, 117, 118, 119, 120, 122, 123, 124], "can_cast": 1, "can_slic": 90, "candid": [9, 20, 26], "cannot": [9, 12, 28, 40, 62, 69, 70, 71, 74, 80, 84, 87, 90, 92, 94, 99, 100, 102, 103, 104, 109, 116, 117, 118, 123], "canon": [1, 3, 15, 54, 77, 90, 93, 103, 110, 123], "canonic": 116, "canonical_img": [54, 93], "canonical_slic": 65, "canva": 1, "canvas": 122, "capabl": [10, 41, 102, 105, 119, 123], "capit": 9, "captur": [28, 77], "carbon": 31, "cardan": 86, "cardiac": 109, "cardin": 68, "care": [12, 14, 23, 37, 41, 55, 62, 64, 76, 100, 109], "carefulli": 2, "caret": [0, 1, 65], "caretmetadata": [65, 77, 94], "caretspec": 15, "carl": [1, 56], "carri": [9, 15, 20, 23, 26, 87, 116], "cartesian": [9, 110], "casa": 9, "case": [1, 2, 6, 8, 10, 12, 13, 16, 18, 19, 20, 21, 22, 25, 26, 27, 35, 36, 38, 39, 40, 41, 43, 51, 54, 55, 57, 60, 61, 62, 64, 68, 69, 74, 76, 77, 80, 81, 84, 86, 87, 89, 90, 92, 93, 94, 99, 100, 102, 103, 104, 106, 107, 108, 109, 113, 114, 116, 117, 118, 119, 123, 124], "cast": [0, 1, 14, 23, 29, 49, 61, 62, 65, 71, 87, 103, 111, 116, 123], "cast_funct": 29, "castingerror": 65, "catch": [1, 22], "catch_warn": 81, "categori": [9, 22], "caught": 40, "caus": [1, 22, 26, 58, 60, 92, 103, 118, 124], "caveat": 105, "cc": 1, "cc0": 4, "cd": [44, 52, 53, 56, 66], "cdot": 38, "ceil": [2, 36, 76, 102], "ceil_exact": 65, "ceil_val": 76, "celebratori": 6, "cell": [40, 114], "cell_valu": 114, "center": [2, 33, 36, 38, 69, 77, 92, 109, 118, 119, 123], "center_i": 2, "center_j": 2, "center_k": 2, "center_vox_valu": 2, "centr": 58, "central": [62, 68, 102, 118], "cerebellum": 77, "certain": [2, 22], "cf": 102, "cfg": 1, "cgreen": 42, "ch": [34, 58], "chain": 105, "challeng": 6, "champion": 6, "chanc": [6, 10, 119], "chang": [2, 6, 7, 8, 9, 10, 12, 14, 16, 17, 18, 20, 22, 23, 26, 28, 30, 38, 40, 42, 44, 46, 52, 53, 55, 56, 59, 61, 62, 69, 70, 74, 76, 77, 81, 87, 90, 103, 104, 105, 106, 109, 116, 119, 120, 122, 123, 124], "changelog": [25, 26, 56, 59, 66], "channel": 22, "chapter": 12, "charact": [1, 35, 39, 40, 77, 94, 114], "character": 30, "characterdatahandl": [65, 77, 94, 125], "characterist": 87, "charg": 58, "cheat": [7, 49], "check": [1, 2, 3, 4, 7, 9, 10, 12, 13, 20, 22, 26, 27, 30, 31, 39, 43, 52, 59, 62, 65, 68, 69, 70, 71, 76, 77, 79, 84, 85, 87, 90, 92, 93, 102, 103, 104, 105, 116, 117, 118, 119, 123], "check_affin": 93, "check_fix": [65, 72, 124], "check_ind": 90, "check_is_dwi": 102, "check_nan": 123, "check_onli": [65, 72], "check_scal": 71, "check_slic": [65, 116], "checker": 22, "checklist": 25, "checkout": [4, 42, 43, 49, 51, 52, 57], "checksum": 20, "checkwarn": 1, "cheng": [1, 56], "child": [75, 77, 94], "chk": 72, "chk_bitpix": 72, "chk_datatyp": 72, "chk_pixdim": 72, "chk_version": [65, 92], "choic": [9, 19, 42, 69, 103, 111, 119, 123], "choos": [2, 8, 13, 22, 43, 59, 61], "chose": [2, 3, 9, 62], "chosen": [10, 16, 90], "chri": [1, 15, 23, 31, 34, 56, 58, 60], "christian": [1, 56, 58], "christoph": [1, 56], "chronolog": 84, "chunk": [77, 90, 91, 92, 94], "ci": [1, 4, 26, 42], "cieslak": [1, 56], "cifti": [1, 28, 56, 65, 66], "cifti2": [0, 1, 65], "cifti2_ax": 65, "cifti2_map": 77, "cifti2brainmodel": 65, "cifti2extens": 65, "cifti2head": 65, "cifti2headererror": 65, "cifti2imag": [1, 15, 65], "cifti2label": 65, "cifti2labelt": 65, "cifti2matrix": 65, "cifti2matrixindicesmap": 65, "cifti2metadata": [1, 65], "cifti2namedmap": 65, "cifti2parcel": 65, "cifti2pars": 65, "cifti2surfac": 65, "cifti2transformationmatrixvoxelindicesijktoxyz": 65, "cifti2vertexindic": 65, "cifti2vertic": 65, "cifti2volum": 65, "cifti2voxelindicesijk": 65, "cifti_brain_structur": 77, "cifti_index_type_brain_model": 77, "cifti_index_type_label": 77, "cifti_index_type_parcel": 77, "cifti_map_typ": 77, "cifti_model_typ": 77, "cifti_model_type_surfac": 77, "cifti_model_type_voxel": 77, "cifti_structure_cortex_left": 77, "cifti_structure_thalamus_left": 77, "cimg": [77, 84, 116], "cinde": [1, 56], "cipollini": [1, 56], "circular": [1, 105], "citabl": 26, "citat": 65, "cite": 4, "citeseerx": 86, "cl": 11, "claim": [19, 58, 113], "clarif": 19, "clarifi": [1, 7], "clariti": [1, 7], "clash": 9, "class": [1, 3, 8, 10, 11, 12, 15, 30, 35, 61, 62, 68, 69, 70, 71, 72, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 87, 88, 89, 92, 93, 94, 95, 96, 98, 99, 100, 101, 102, 103, 104, 105, 106, 108, 109, 110, 111, 112, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125], "class_map": 1, "classic": 35, "classif": 10, "classifi": 22, "classmethod": [10, 11, 15, 69, 74, 77, 80, 84, 87, 92, 94, 99, 100, 103, 104, 109, 110, 116, 117, 118, 119, 124], "clean": [1, 26, 49], "cleanup": 1, "clear": [1, 8, 9, 11, 13, 14, 19, 34, 55, 84], "clear_cach": 65, "clearer": 20, "clearli": [12, 19, 20], "clemen": [1, 56], "clever": 123, "cli": 1, "clib": 1, "click": [26, 43, 45, 56, 66, 122], "client": 35, "clim": [65, 122], "clinic": 35, "clip": [76, 123], "clipped_sc": 123, "clipped_scaled_clip": 123, "clipped_scaled_clipped_n2z": 123, "clockwis": 86, "clone": [22, 43, 44, 49, 52, 56, 57, 66], "close": [1, 3, 9, 25, 26, 43, 51, 54, 65, 86, 93, 102, 106, 113, 122], "close_if_min": [65, 106], "closer": [2, 7], "closest": [1, 2, 85, 93, 102, 103, 108, 113], "clutter": 9, "cm": 1, "cmap": [2, 60, 65, 122], "cmd": 57, "cmdline": [0, 1, 65], "cmp": 78, "co": [2, 6, 26, 42, 86], "coalson": 1, "coc": 1, "code": [1, 2, 4, 6, 7, 9, 10, 11, 14, 16, 19, 23, 25, 26, 28, 34, 35, 36, 37, 39, 40, 41, 42, 43, 52, 53, 56, 57, 59, 65, 66, 69, 71, 72, 74, 77, 78, 84, 85, 86, 92, 94, 96, 102, 103, 104, 108, 117, 118, 122, 123, 124], "code1": 123, "code2": 123, "code_repr": 103, "code_syn_seq": 123, "codebas": [6, 13], "codecov": 1, "codes_seq": 123, "codespel": [1, 22], "coerc": [1, 69, 103, 123], "coin_samp_mod": 84, "col": [36, 114], "col_nam": 114, "collabor": 43, "collaps": [43, 116], "collat": [77, 94], "collect": [1, 2, 6, 12, 15, 19, 20, 31, 35, 36, 40, 41, 43, 69, 77, 84, 94, 102, 103, 106, 110], "collin": 58, "collis": 35, "colon": 43, "color": [1, 42, 77, 92, 122], "colormap": 122, "colort": 92, "colortab": 92, "colour": 77, "column": [1, 2, 9, 12, 18, 32, 35, 36, 40, 68, 70, 77, 84, 86, 92, 94, 102, 110, 111, 113, 114, 123], "com": [1, 4, 8, 10, 13, 18, 20, 26, 38, 42, 43, 44, 45, 47, 51, 52, 53, 56, 57, 58, 66, 76, 79, 86, 90, 92, 109, 113], "combin": [1, 2, 9, 15, 20, 31, 35, 43, 62, 68, 69, 74, 77, 80, 84, 86, 87, 90, 92, 99, 100, 103, 104, 109, 116, 117, 118, 123], "combined_parcel": 77, "come": [1, 2, 9, 18, 20, 23, 41, 43, 49, 52, 62, 70, 84, 86, 90, 103, 115, 119, 124], "comma": 77, "command": [1, 6, 34, 35, 42, 43, 44, 49, 52, 78, 100, 102, 109, 111], "comment": [6, 9, 19, 36, 40, 84, 92, 103], "commerci": 37, "commissur": [2, 94], "commit": [1, 4, 6, 19, 20, 23, 26, 42, 49, 52], "commit_": 22, "commit_hash": 26, "commit_sourc": 26, "committ": 22, "common": [1, 2, 10, 11, 12, 13, 15, 20, 22, 27, 28, 29, 35, 42, 49, 56, 60, 62, 66, 74, 78, 87, 94, 119, 124], "common_shap": [65, 119], "commonhead": 11, "commonli": [60, 116], "commun": [1, 6, 19, 25, 35, 37, 49], "compact": [51, 62], "compar": [1, 2, 7, 31, 51, 60, 78, 82, 86, 100, 102, 107, 123], "comparison": [1, 31, 78], "compat": [1, 4, 6, 7, 9, 10, 11, 13, 22, 28, 31, 37, 77, 103, 112, 119], "compet": 6, "compil": [1, 10, 26, 35, 76, 102], "compileal": 26, "complet": [1, 4, 6, 9, 12, 20, 26, 28, 40, 41, 51, 61, 103, 109, 123], "complex": [8, 10, 19, 71, 103, 113], "complex64": 27, "complianc": 41, "compliant": 119, "complic": [2, 9, 34, 35, 43, 61, 68, 76, 109, 120], "compon": [1, 2, 15, 36, 38, 68, 77, 102, 103], "compos": [35, 119], "composed_m": 86, "composit": [35, 86], "comprehens": [1, 16, 48, 56], "compress": [1, 4, 20, 67, 69, 74, 106, 117, 118], "compress_ext_icas": [65, 106], "compress_ext_map": [65, 106], "compression_cod": 84, "compresslevel": 106, "compris": [2, 16, 35, 102], "compromis": 6, "comput": [1, 35, 42, 53, 78, 86, 97, 103, 105, 109, 113, 119], "concat_imag": [1, 65], "concat_img": 93, "concaten": [1, 12, 13, 65, 77, 93, 94, 109], "concentr": 12, "concept": [49, 56, 123], "conceptu": 60, "concern": 19, "concis": 6, "concret": [6, 23, 77], "conda": 1, "condit": [1, 35, 58, 81], "conduct": 22, "conf": [26, 102], "confid": 23, "config": [1, 22, 42, 49, 52, 79], "config_filanam": 79, "config_filenam": 79, "configur": [1, 22, 26, 43, 46, 50, 79, 85], "confirm": [20, 35], "conflict": [7, 9, 26, 35, 43], "conform": [1, 3, 9, 35, 39, 65, 70, 94], "confus": [1, 2, 7, 34, 35, 38, 43, 60, 76, 92, 118], "confusingli": [40, 60, 86], "conjq": 113, "conjug": 65, "conjunct": 38, "connect": [15, 53, 58], "consensu": [6, 23], "consequ": 7, "consequenti": 58, "conserv": 4, "consid": [3, 6, 12, 15, 19, 20, 22, 26, 38, 46, 51, 52, 69, 72, 78, 90, 94, 102, 103, 108, 109, 116, 119], "consider": [9, 10, 110], "consist": [1, 6, 11, 15, 19, 22, 23, 27, 35, 36, 65, 75, 77, 78, 84, 87, 92, 94, 102, 113], "consol": [2, 109], "const": 10, "constant": [9, 11, 38, 40, 67, 111], "constrain": [1, 9, 69, 102, 103], "constraint": [9, 90, 105], "construct": [1, 19, 62, 75, 77, 87, 89, 102, 111, 113, 119], "constructor": [1, 3, 7, 10, 77, 93, 103, 106, 119, 123], "consult": [2, 19, 56, 66], "consum": [15, 62, 68], "consumpt": 15, "cont": 109, "contact": 9, "contain": [1, 2, 3, 6, 7, 10, 11, 12, 13, 15, 16, 18, 20, 28, 30, 31, 33, 35, 36, 37, 38, 40, 41, 42, 61, 62, 68, 69, 70, 74, 76, 77, 79, 80, 84, 87, 88, 90, 92, 94, 99, 100, 102, 103, 104, 108, 109, 111, 113, 115, 116, 117, 118, 119, 120, 122, 123, 124, 125], "content": [1, 4, 6, 20, 22, 26, 32, 35, 37, 42, 46, 50, 61, 65, 69, 75, 77, 79, 87, 92, 94, 103, 108, 114, 116, 117, 118, 119, 124], "context": [1, 9, 35, 96, 106, 110, 120], "contigu": [8, 12, 30, 64, 70, 90], "continu": [6, 7, 9, 12, 19, 23, 28, 33, 36, 40, 77, 90, 123], "contract": 58, "contrari": 68, "contrast": [9, 15, 20, 35, 119], "contrib": 20, "contribut": [1, 19, 23, 26, 56, 57], "contributor": [6, 19, 26, 58], "control": [1, 20, 26, 43, 61, 62, 69, 70, 74, 80, 84, 92, 94, 99, 100, 106, 109, 113, 118, 122, 123], "controversi": [6, 28], "conveni": [6, 11, 38, 49, 55, 68, 102, 119, 122, 123], "convent": [2, 3, 9, 22, 54, 86, 94, 102, 119, 126], "convers": [1, 10, 11, 31, 32, 33, 34, 36, 57, 69, 71, 76, 77, 86, 103, 123], "convert": [1, 2, 6, 7, 10, 17, 20, 25, 34, 40, 42, 65, 69, 71, 76, 77, 86, 87, 103, 107, 108, 109, 111, 113, 114, 119, 123], "convolv": 30, "cookbook": 34, "cool": [43, 51], "coord": [15, 84, 92, 94, 110], "coordaxi": 15, "coordin": [1, 3, 17, 25, 30, 32, 33, 36, 40, 41, 54, 60, 61, 62, 65, 68, 69, 74, 77, 86, 92, 94, 99, 100, 102, 103, 104, 108, 109, 110, 111, 113, 115, 116, 117, 118, 119, 122, 123, 126], "coordinate_system": 119, "coordinatearrai": 65, "coordinateaxi": 15, "coordinateimag": [1, 15], "coordinatesystem": 92, "coordinatesystemtransformmatrix": 94, "coords_2": 94, "coords_3": 94, "coords_4": 94, "coords_5": 94, "coordsi": 94, "copi": [1, 4, 9, 10, 12, 14, 20, 26, 27, 30, 42, 43, 46, 50, 52, 53, 55, 56, 58, 61, 62, 63, 64, 65, 66, 69, 70, 74, 87, 88, 92, 93, 103, 109, 112, 116, 119, 124], "copul": 43, "copy_file_map": 65, "copy_if": 8, "copyright": [1, 26, 30, 56, 57, 59], "core": [6, 25, 28, 42, 47], "coregist": 10, "corner": [1, 2, 19, 38, 113, 119], "coron": [109, 122], "corr": 42, "corran": 42, "correct": [1, 9, 12, 13, 23, 36, 76, 79, 84, 93, 103, 115, 119, 123], "correctli": [1, 3, 9, 10, 12, 26, 43, 76, 100, 103], "correspond": [1, 2, 7, 9, 10, 15, 18, 22, 27, 35, 40, 41, 55, 62, 68, 74, 77, 86, 87, 89, 90, 92, 94, 102, 103, 108, 109, 110, 111, 113, 116, 119, 122, 123, 124], "corrupt": [1, 20], "cortex": [15, 77], "cortex_left": 77, "cortexleft": [15, 77], "cortexright": 77, "cortic": 77, "cos_gamma": 2, "cosin": [38, 40, 92], "cost": [20, 27], "costli": 15, "cote": 1, "cottaar": [1, 56], "could": [1, 2, 7, 8, 9, 10, 11, 12, 13, 15, 20, 22, 27, 55, 60, 77, 86, 90, 102, 107, 109, 118, 121], "council": 6, "count": [65, 84, 97, 103, 123], "count_nonzero_voxel": 65, "counter": 86, "counterclockwis": 86, "coupl": [9, 11], "courag": 41, "cours": [8, 9, 10, 11, 12, 15, 18, 20, 27, 41, 42, 49, 107], "courtesi": 58, "cover": [1, 7, 9, 19, 33, 35, 56, 58, 77], "coverag": [1, 3, 66], "coveral": 1, "cr": 42, "cra": 92, "crabb": 1, "crash": [1, 40, 49], "creat": [1, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 26, 30, 33, 35, 37, 41, 42, 46, 61, 62, 64, 65, 69, 70, 71, 74, 79, 80, 84, 87, 89, 92, 93, 99, 100, 102, 103, 104, 105, 106, 109, 116, 118, 119, 120, 122, 123, 124], "create_arraysequences_from_gener": 65, "create_dcmmeta": 10, "create_empty_head": [65, 119], "create_stamp": 92, "creation": [20, 28, 62, 79, 81, 124], "creator": [35, 37, 102], "cred": 42, "credit": 57, "creset": 42, "criteria": 22, "critic": [19, 72], "crop": [1, 61, 78, 116], "cropimag": 1, "cropped_img": 61, "cross": [2, 36, 38, 40, 63, 114], "crosshair": 1, "csa": [1, 10, 32, 36, 40, 102], "csa1": 32, "csa2": 32, "csa_dict": 102, "csa_head": 102, "csa_info": 102, "csa_max_po": 39, "csa_posit": 39, "csa_str": 102, "csa_typ": 102, "csaerror": 65, "csaimageheaderinfo": [39, 40], "csaread": 65, "csareaderror": 65, "csaseri": 10, "csv": 109, "ct": [35, 40], "ctab": 92, "cti": 84, "ctime": 92, "ctrl": 1, "ctype": 90, "cumbersom": 13, "curl": 86, "current": [1, 7, 9, 10, 11, 13, 20, 22, 23, 26, 27, 28, 36, 40, 42, 43, 51, 53, 56, 57, 64, 66, 80, 82, 84, 87, 91, 92, 94, 102, 103, 105, 106, 116, 119, 122, 123, 124], "curv": 92, "curvatur": [77, 92], "curvnew": 92, "custom": [1, 69, 103, 106, 109], "cut": 26, "cval": 111, "cwd": 1, "cy": 86, "cy_thresh": 86, "cycl": 105, "c\u00f4t\u00e9": [1, 11, 56], "d": [1, 2, 8, 9, 20, 26, 35, 37, 38, 40, 42, 43, 56, 57, 61, 79, 81, 102, 103, 108, 111, 123, 124], "d157741": 10, "d304a73": 42, "d6": 58, "d_1": 119, "d_2": 119, "d_d": 119, "da": 35, "dai": [6, 14], "damag": [1, 58], "dan": 23, "daniel": [1, 56], "dark": 60, "darrai": 94, "darwin": [1, 26, 56], "dash": [16, 35], "dat": 30, "data": [0, 1, 3, 7, 8, 9, 12, 13, 17, 18, 25, 27, 29, 30, 32, 33, 34, 37, 41, 42, 55, 56, 59, 60, 64, 65, 66, 69, 70, 71, 72, 74, 75, 77, 78, 80, 84, 87, 88, 90, 92, 93, 94, 98, 99, 100, 102, 103, 104, 105, 108, 110, 111, 115, 117, 118, 119, 122, 123, 124, 125, 126], "data1": 11, "data2": 11, "data3": 11, "data4d": 84, "data_again": [55, 61, 80], "data_arrai": 55, "data_dict": 94, "data_dir": 79, "data_dtyp": [99, 100, 116], "data_for_point": 119, "data_for_streamlin": 119, "data_from_fileobj": [65, 69, 84, 92, 99, 116], "data_func": 119, "data_layout": [65, 99, 116], "data_max_abs_diff": 78, "data_max_rel_diff": 78, "data_once_mor": 55, "data_packag": 20, "data_path": [41, 54, 55, 61, 62, 79, 80], "data_per_point": [65, 119], "data_per_streamlin": [65, 119], "data_tag": 1, "data_to_fileobj": [65, 69, 99, 116], "data_typ": [61, 62, 69, 103, 117, 118], "data_type_cod": 94, "data_unit": 84, "dataarr": 94, "dataarrai": [1, 94], "databas": [9, 20, 43], "datadict": 119, "datadir": 74, "dataerror": [1, 65], "datamodel": 105, "dataobj": [1, 3, 7, 13, 14, 15, 55, 61, 62, 65, 69, 74, 77, 80, 84, 92, 98, 99, 100, 103, 104, 109, 111, 116, 117, 118], "dataobj_imag": [0, 1, 65], "dataobjimag": [1, 65, 77, 84, 95, 99, 100, 116], "dataoobj": 116, "datapkg": 20, "datarrai": 12, "dataset": [1, 10, 12, 30, 35, 37, 40, 55, 74, 77, 102, 103, 109, 117], "dataset_rank": 74, "datasourc": 65, "datasource_or_bomb": 65, "dataspac": [12, 94], "datatyp": [9, 11, 30, 61, 62, 69, 72, 77, 78, 87, 92, 94, 103, 104, 108, 116, 117, 118, 123, 124], "datawarn": 65, "date": [6, 9, 16, 26, 31, 35, 42, 94], "datetim": 31, "datobj": 80, "dave": 1, "db_name": [61, 62, 69, 103, 117, 118], "dc": 103, "dcluni": 38, "dcm": [9, 36, 102], "dcm1": 31, "dcm2": 31, "dcm2nii": 34, "dcm_data": [36, 102], "dcm_w": 102, "dcm_wrp": 102, "dcmdata": 102, "dcmext": 37, "dcmmeta": 10, "dcmread": 102, "dcmstack": [17, 25, 28], "dct": 123, "dd": [6, 16], "de": [1, 56, 58, 87], "deactiv": 26, "dead": 9, "deadlock": 23, "deal": [2, 10, 12, 13, 58, 67, 70, 76, 86, 114, 123], "dealt": 40, "dear": 38, "deb": [20, 57], "debian": [1, 22, 31, 47, 58, 59, 79], "debug": 120, "decemb": 59, "decid": [2, 6, 7, 8, 20, 23, 90], "decim": [2, 15, 35, 54, 61, 62], "decis": [1, 6, 12, 19, 25], "declar": 1, "decod": [9, 35, 40, 103, 119], "decode_value_from_nam": 65, "decompos": 1, "decor": [1, 4, 82, 112], "decreas": [20, 35, 66, 77, 103], "decrement": 122, "decrypt": 20, "dedic": 103, "deduct": 1, "dedup": 26, "deem": [19, 113], "deepcopi": 119, "def": [2, 3, 4, 8, 11, 14, 15, 71, 72, 105, 123], "default": [1, 3, 4, 5, 7, 9, 10, 11, 12, 13, 14, 20, 27, 31, 41, 43, 45, 52, 55, 57, 59, 61, 64, 66, 69, 70, 71, 72, 74, 76, 77, 79, 80, 81, 84, 85, 86, 87, 88, 89, 90, 92, 93, 94, 96, 99, 100, 102, 103, 104, 105, 106, 107, 109, 110, 111, 113, 114, 116, 117, 118, 119, 120, 122, 123, 124, 125], "default_class": 102, "default_compresslevel": [65, 106], "default_frame_filt": 102, "default_level_or_opt": [65, 106], "default_out": 123, "default_registri": 20, "default_structarr": [65, 69, 84, 92, 103, 104, 118, 124], "default_x_flip": [65, 69, 116, 118], "default_zst_compresslevel": [65, 106], "defeat": 8, "defer": [1, 6, 16], "defici": 1, "defin": [1, 2, 3, 9, 10, 11, 15, 18, 20, 31, 32, 33, 35, 36, 40, 60, 62, 68, 69, 70, 76, 77, 79, 80, 86, 87, 88, 89, 91, 92, 94, 95, 102, 103, 106, 109, 112, 115, 119, 123, 125], "definit": [2, 3, 9, 10, 11, 12, 18, 20, 33, 40, 60, 77, 90, 99, 100, 102, 103, 106, 109, 126], "deform": 12, "degener": 1, "degre": 113, "delai": [1, 26, 109], "deleg": 1, "delet": [9, 20, 46, 55, 80, 84], "deliber": [53, 60], "delic": 76, "delimit": [1, 35, 40, 102], "deliv": 20, "delphi": 31, "delta": [33, 38, 40, 92, 123], "demian": [1, 56], "demonstr": 23, "den": [1, 56], "denot": 74, "densiti": 15, "depart": 30, "depend": [1, 6, 9, 10, 12, 14, 16, 22, 26, 28, 29, 30, 35, 37, 38, 56, 66, 69, 74, 77, 87, 92, 94, 99, 100, 103, 104, 105, 106, 109, 116, 117, 118, 123], "deploi": 1, "deposit": 26, "deprec": [0, 13, 14, 59, 65, 70, 76, 80, 94, 98, 108, 112, 120], "deprecationwarn": [1, 12, 81, 82], "deproxi": 8, "depth": [12, 49, 103], "der": 58, "derefer": 9, "deriv": [1, 26, 30, 36, 40, 58, 69, 77, 86, 87, 89, 102, 116], "descend": 38, "describ": [1, 2, 3, 6, 9, 12, 15, 16, 23, 26, 28, 34, 35, 36, 38, 48, 55, 62, 68, 70, 72, 77, 86, 94, 103, 104, 109, 110], "describing_rotations_with_quaternion": 113, "descrip": [30, 61, 62, 69, 103, 104, 117, 118], "descript": [1, 3, 9, 18, 35, 38, 43, 74, 75, 77, 81, 86, 93, 94, 102, 103], "descriptor": [77, 105], "deseri": [87, 103], "design": [1, 6, 23, 28, 41, 71, 74, 110], "desir": [15, 20, 29, 102, 103], "despit": 4, "destructor": 1, "detach": 43, "detail": [6, 22, 23, 34, 35, 41, 45, 46, 49, 50, 55, 56, 59, 62, 64, 66, 68, 80, 87, 102, 103, 104, 108], "detect": [1, 26, 51, 72, 78, 102, 109], "detect_format": [11, 65], "determin": [1, 36, 41, 74, 102, 103, 109, 113, 122], "determinist": [1, 106], "deterministicgzipfil": 65, "dev": [1, 22, 26, 58, 81, 92], "dev_tre": 26, "devel": [6, 26], "develop": [6, 16, 20, 26, 28, 31, 44, 49, 50, 51, 56, 58, 59, 66], "deviat": [1, 109], "devic": 35, "devot": 19, "dewei": [1, 56], "dft": [0, 65], "dfterror": 65, "diag": [36, 55, 61, 62, 68, 69, 102, 103, 111, 113, 115, 116, 118], "diag_affin": 60, "diagnos": 1, "diagnose_binaryblock": [65, 92, 124], "diagnosi": 1, "diagnost": [1, 78, 124], "diagon": [2, 60, 68, 69, 93, 111, 115], "diagram": [35, 102], "dicm": [35, 40], "dicom": [1, 2, 9, 12, 28, 31, 56, 57, 66, 83, 102, 103, 109, 112, 126], "dicom2nrrd": 34, "dicom_kwarg": 102, "dicom_mosa": 102, "dicom_orient": 102, "dicom_orientaiton": 102, "dicom_path": 102, "dicom_test": 65, "dicomcompat": 31, "dicomcookbook": 34, "dicomf": [1, 26, 57, 65], "dicomfastread": 31, "dicomlink": 34, "dicomread": 65, "dicomreaderror": 65, "dicomtyp": 31, "dicomwrapp": [1, 65], "dict": [1, 9, 15, 62, 69, 74, 75, 76, 77, 78, 79, 80, 84, 87, 88, 89, 92, 94, 99, 100, 102, 106, 109, 114, 118, 119, 123, 124, 125], "dictionari": [1, 9, 10, 11, 30, 61, 74, 77, 79, 84, 87, 94, 102, 109, 116, 119, 123, 126], "did": [1, 14, 29, 40, 43, 61, 69, 102], "didn": [1, 9, 40], "diebel": 86, "diff": [1, 20, 22, 42, 43, 49, 51, 65, 109], "differ": [1, 2, 4, 6, 7, 9, 10, 11, 12, 18, 19, 22, 26, 29, 31, 35, 36, 38, 39, 40, 48, 60, 61, 62, 68, 72, 76, 78, 87, 90, 102, 103, 108, 109, 111, 116, 119], "difference_upd": [65, 77], "differenti": 31, "difficult": [14, 19, 20, 30, 123], "diffus": [1, 9, 34, 102, 109], "digit": [6, 9, 20, 54, 56, 61, 62, 66, 76], "dim": [1, 12, 30, 40, 61, 62, 65, 69, 77, 92, 102, 103, 104, 110, 116, 117, 118], "dim_info": [1, 12, 61, 62, 103, 104], "dim_len": 90, "dim_un0": [69, 117, 118], "dimens": [1, 2, 3, 8, 11, 12, 13, 15, 30, 36, 38, 40, 65, 68, 69, 74, 77, 90, 92, 93, 102, 103, 104, 108, 109, 111, 115, 116, 119, 122, 123], "dimension": [1, 9, 12, 15, 40, 68, 77, 78, 92, 93, 103, 110, 113, 122], "dimensionindexsequ": 102, "dimensionindexvalu": 102, "dimitri": [1, 56], "dims": 126, "dir": 120, "direct": [1, 2, 6, 9, 12, 15, 38, 40, 58, 60, 86, 92, 102, 103, 108], "directli": [1, 2, 4, 8, 9, 10, 12, 23, 27, 35, 36, 38, 39, 42, 43, 51, 55, 61, 62, 68, 69, 76, 81, 92, 94, 102, 103, 117, 118, 123], "directori": [4, 6, 15, 20, 22, 26, 35, 41, 42, 43, 44, 53, 57, 79, 85, 102, 120], "dirnam": [84, 107], "dirti": [7, 27], "disabl": [1, 43], "disagr": [6, 23], "disallow": 103, "discard": [26, 40, 43, 55, 90, 109], "discern": 106, "disclaim": 58, "discov": [20, 52, 86], "discret": [16, 124], "discuss": [1, 6, 7, 8, 13, 14, 20, 23, 25, 27, 28, 33, 35, 42, 56, 119], "disk": [1, 3, 7, 8, 9, 12, 14, 18, 20, 27, 29, 30, 35, 36, 41, 55, 60, 61, 64, 69, 74, 80, 87, 90, 92, 99, 100, 103, 104, 109, 116, 117, 118, 123], "displac": 12, "displai": [1, 2, 26, 35, 62, 69, 77, 94, 122, 124, 126], "display_diff": 65, "dissent": 6, "dist": [22, 26, 74], "distanc": [9, 15, 38, 68, 90], "distance_matrix": 15, "distance_scan": 84, "distclean": 26, "distinct": [7, 30], "distinguish": [23, 40], "distort": 12, "distribut": [1, 4, 20, 22, 26, 27, 56, 57, 58, 61], "distutil": [1, 26, 79], "ditto": 117, "diverg": 27, "divid": [71, 123], "divis": 2, "divslop": 123, "dlpfc_idc": 15, "dlpfc_img": 15, "dlpfc_mask": 15, "dm": 15, "do": [1, 2, 3, 4, 6, 8, 9, 10, 11, 12, 13, 14, 19, 20, 22, 26, 27, 31, 35, 38, 40, 42, 44, 45, 46, 49, 51, 52, 54, 55, 57, 58, 61, 62, 64, 69, 70, 71, 74, 76, 79, 80, 81, 84, 86, 87, 89, 90, 91, 92, 94, 99, 100, 102, 103, 105, 107, 109, 115, 116, 117, 118, 119, 120, 121, 122, 123], "do_silly_th": 121, "doc": [1, 3, 6, 7, 22, 26, 38, 58, 74, 79, 81, 102, 105], "dock": 1, "dock\u00e8": [1, 56], "docstr": [1, 3, 19, 22, 55, 69, 102, 108, 123], "doctest": [1, 26, 66, 78, 93], "doctor": 38, "document": [2, 3, 6, 9, 11, 12, 14, 17, 18, 19, 20, 21, 23, 26, 34, 35, 40, 41, 57, 58, 59, 66, 68, 74, 77, 84, 87, 94, 125], "doe": [1, 2, 3, 6, 7, 8, 9, 10, 12, 16, 19, 22, 26, 27, 30, 34, 35, 38, 39, 40, 55, 60, 61, 62, 64, 69, 70, 72, 76, 77, 78, 79, 80, 81, 84, 87, 90, 92, 102, 103, 104, 106, 107, 108, 111, 116, 119, 120, 123, 124], "doesn": [7, 9, 19, 38, 43, 58, 87, 92, 103, 104, 105, 107, 119, 123], "dof": 92, "doi": [1, 26, 56, 66, 86], "dom": 6, "don": [1, 3, 4, 6, 7, 8, 9, 10, 12, 13, 14, 19, 20, 26, 29, 34, 37, 40, 43, 44, 45, 52, 57, 69, 76, 80, 81, 90, 102, 105, 115, 123], "donald": 1, "done": [1, 4, 6, 11, 14, 22, 27, 34, 43, 48, 52, 57, 62, 78, 103, 105, 108, 116, 124], "dorota": [1, 56], "dosag": 84, "dose_start_tim": 84, "dot": [2, 10, 36, 40, 68, 86, 113, 115], "dot_product": 68, "dot_reduc": 65, "doubl": [1, 30, 35, 76], "doubt": 6, "dougherti": 9, "down": [1, 2, 8, 9, 38, 43, 44, 55, 61, 69, 90, 114, 116, 122], "download": [2, 20, 31, 47, 57, 60, 94], "downsampl": [15, 61], "downstream": [1, 103], "downward": 1, "dpc": 38, "dr": 9, "draft": [6, 9, 10, 11, 12, 13, 15, 16, 20, 23], "drag": 122, "draw": [2, 65, 122], "drop": [1, 4, 27, 90, 108, 111], "drop_handl": 1, "dropdown": 43, "dsc": 26, "dt": [1, 30, 35, 72, 76, 123], "dtd": 9, "dti": 58, "dtifit": 1, "dtime": 109, "dtseri": 15, "dtype": [1, 11, 14, 29, 41, 55, 61, 62, 64, 65, 69, 70, 71, 72, 76, 77, 78, 80, 84, 87, 90, 92, 93, 97, 103, 104, 109, 110, 113, 116, 117, 118, 119, 123, 124], "dtypelik": [80, 99, 100, 116, 123], "dtypemapp": 65, "due": 1, "duek": [1, 56], "dummi": 78, "dummy_fus": 65, "dump": [1, 9, 10, 35, 39], "duplic": [1, 15, 20, 26], "durat": [37, 103, 120], "dure": [1, 6, 10, 12, 22, 37, 40, 42, 43, 56, 66, 77, 78, 94, 102, 109, 125], "dv": 109, "dwell": 101, "dwell_tim": 101, "dwi": [1, 102], "dwiparam": 65, "dx": [1, 30], "dx_result": 124, "dy": 30, "dyn": 109, "dynam": [30, 33, 35, 103, 109, 113], "dynamicstreamlinefil": 11, "dz": 30, "e": [1, 6, 9, 10, 11, 12, 15, 16, 18, 19, 20, 22, 26, 29, 35, 38, 40, 41, 43, 49, 56, 57, 62, 66, 72, 74, 77, 78, 81, 87, 89, 92, 96, 101, 102, 103, 109, 110, 119, 123], "e00d": 40, "each": [1, 2, 6, 7, 9, 10, 11, 12, 13, 15, 16, 19, 20, 22, 26, 30, 33, 35, 36, 37, 38, 40, 43, 68, 69, 77, 78, 80, 84, 86, 90, 92, 94, 99, 100, 102, 103, 108, 109, 114, 118, 119, 122, 123], "eadc391": 43, "earli": [1, 3, 94], "earlier": [14, 105], "earliest": 6, "eas": 11, "easi": [4, 10, 11, 15, 22, 28, 34, 37, 41, 49, 57, 59, 76], "easier": [1, 3, 10, 14, 28, 43, 80, 123], "easiest": [3, 52], "easili": [2, 4, 10, 11, 15, 22, 23, 41, 77, 94], "ec": 109, "ecat": [0, 1, 12, 13, 56, 65, 66, 70], "ecat7": 84, "ecat_calibration_factor": 84, "ecat_fil": 84, "ecathead": 65, "ecatimag": 65, "ecatimagearrayproxi": 65, "ecatsubhead": 65, "echo": [12, 31, 35, 101, 109], "echo_tim": [9, 10], "echo_train_length": 101, "echonumb": [31, 40], "echotim": 10, "ecod": [37, 103], "ecosystem": [6, 16, 23], "edata": 1, "edg": 111, "edit": [1, 9, 26, 42, 46, 49, 52, 56, 66], "editor": [43, 86], "edu": [6, 9, 26, 58, 86, 92], "eeee": 35, "eeg": 103, "effect": [1, 4, 7, 19, 20, 40, 55, 62, 69, 70, 74, 80, 84, 92, 94, 99, 100, 103, 108, 116, 118], "effici": [1, 14, 19, 28, 55, 71, 90, 103, 110], "effigi": [23, 58], "effort": [19, 34], "egor": [1, 56], "eigenvalu": [102, 113], "eigenvector": 113, "eight": 111, "either": [1, 3, 9, 35, 39, 51, 57, 61, 74, 77, 80, 85, 102, 103, 112, 115], "el": 1, "eleftherio": [1, 56], "element": [1, 10, 12, 13, 20, 28, 30, 33, 38, 40, 41, 68, 72, 75, 77, 80, 86, 89, 90, 92, 94, 102, 108, 109, 111, 113, 115, 119, 123, 126], "element_start": 102, "element_valu": 102, "elementtre": [1, 125], "elimin": 22, "ellips": 90, "elong": 16, "els": [3, 7, 19, 20, 43, 52, 56, 62, 69, 70, 71, 72, 77, 103, 112, 119], "elsedemo": 74, "elsewher": 22, "email": [6, 16, 52], "emb": 1, "embarrass": 43, "embarrassingli": 76, "embed": [1, 41, 68, 115], "emit": [40, 81, 109], "emoji": 6, "empow": 19, "empti": [1, 9, 40, 55, 68, 69, 72, 77, 80, 84, 90, 92, 94, 102, 103, 104, 106, 109, 117, 118, 119, 123, 124], "emptor": 105, "en": [22, 47, 76, 86, 113, 119], "enabl": [1, 5, 22, 87, 109, 110], "enc": [94, 125], "encapsul": [35, 87, 102, 116, 119], "enclos": [10, 115], "encod": [1, 2, 6, 9, 10, 12, 15, 28, 35, 62, 65, 69, 77, 82, 87, 92, 94, 103, 119, 125], "encode_value_in_nam": 65, "encoded_nam": 119, "encount": 94, "encourag": [1, 6, 19], "encrypt": 20, "end": [1, 2, 12, 20, 22, 26, 30, 34, 35, 36, 38, 40, 41, 43, 51, 68, 77, 85, 86, 87, 89, 92, 93, 94, 103, 108, 120], "end_ornt": 108, "endelementhandl": [65, 77, 94, 125], "endian": [1, 18, 30, 39, 40, 61, 62, 65, 69, 77, 78, 84, 92, 94, 102, 103, 104, 109, 117, 118, 119, 123, 124], "endiancod": [103, 124], "endors": 58, "enemi": 2, "enforc": [1, 2, 4, 42, 78, 94, 103], "enforce_diag": 93, "enforce_extens": 89, "eng": 102, "engin": [9, 113], "enh": 43, "enhanc": [6, 25, 59, 102], "enjoy": 20, "enough": [3, 41, 87, 90, 102, 116, 124], "ensur": [1, 15, 19, 22, 23, 29, 41, 43, 77, 90, 94, 103, 105], "enter": [43, 123], "enthought": 58, "entir": [9, 11, 35], "entiti": [10, 23, 126], "entri": [1, 62, 75, 77, 79, 84, 87, 90, 102, 111, 114, 115, 119, 123], "enum": [11, 66, 102], "enumer": [2, 77], "environ": [0, 1, 5, 6, 22, 65, 79], "eof": 1, "eof_delimit": [65, 119], "eol_check": [62, 104], "ep": [76, 108, 113], "epi": [2, 109], "epi_img": 2, "epi_img_data": 2, "epi_vox2anat_vox": 2, "epi_vox_cent": 2, "epsilon": [76, 108], "epydoc": 1, "equal": [9, 20, 35, 38, 42, 62, 77, 82, 86, 92, 94, 102, 113, 123], "equat": 2, "equip": [33, 35, 58], "equiv": 113, "equival": [1, 2, 8, 20, 28, 30, 35, 60, 61, 62, 68, 72, 76, 85, 87, 90, 92, 94, 97, 102, 106, 108, 109, 110, 113, 116, 123], "era": 1, "eric": [1, 56], "erik": 56, "err": 6, "error": [1, 3, 9, 13, 14, 22, 26, 28, 29, 31, 36, 40, 57, 63, 64, 65, 68, 69, 71, 72, 74, 76, 77, 79, 81, 82, 83, 84, 86, 87, 89, 90, 93, 94, 96, 99, 102, 103, 107, 108, 109, 112, 113, 116, 118, 121, 124], "error_class": [81, 82], "error_level": [72, 96, 124], "error_rec": 81, "errorlevel": 65, "escal": [6, 23], "escap": 1, "essenti": [9, 30], "establish": [1, 8], "esteban": [1, 23, 56], "estepar": 102, "estevan": [1, 56], "estim": [13, 28, 40, 68, 86, 102, 113], "etc": [6, 9, 10, 11, 13, 19, 26, 30, 35, 36, 77, 78, 85, 87, 89, 102, 103, 114, 124], "etre": [1, 125], "euclidean": [68, 102], "euler": 86, "euler2angle_axi": 65, "euler2mat": 65, "euler2quat": 65, "euler_angl": 86, "eulerangl": [0, 65], "eulerparamet": [86, 113], "evalu": [1, 19, 105], "even": [1, 6, 8, 19, 26, 29, 35, 40, 41, 43, 52, 58, 69, 78, 90, 117, 118, 123, 124], "event": [6, 23, 58], "eventu": [1, 6, 36], "ever": [31, 41], "everi": [7, 9, 11, 15, 20, 26, 35, 41, 61, 69, 70, 74, 80, 84, 92, 99, 100, 118, 119], "everyon": [6, 109], "everyth": [26, 43, 57, 120], "evid": 9, "exact": [2, 4, 35, 76], "exactli": [2, 8, 29, 35, 61, 76, 77, 123], "exampl": [1, 3, 4, 6, 7, 9, 10, 11, 12, 14, 15, 16, 18, 19, 20, 26, 27, 29, 30, 34, 35, 36, 37, 41, 42, 43, 51, 52, 54, 55, 56, 58, 59, 61, 64, 65, 68, 69, 70, 71, 72, 74, 76, 79, 80, 81, 82, 84, 85, 86, 87, 89, 90, 92, 93, 94, 97, 100, 102, 103, 104, 105, 107, 108, 109, 110, 111, 113, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 126], "example4d": [41, 54, 55, 61, 62, 74, 80], "example_fil": [54, 55, 61, 120], "example_filenam": 41, "example_imag": 3, "example_ni1": 62, "example_ni2": 62, "example_nifti2": 62, "exce": 76, "excel": [3, 34, 49, 52, 57], "except": [1, 2, 10, 13, 15, 38, 40, 42, 71, 72, 76, 77, 79, 81, 82, 83, 87, 88, 89, 90, 92, 93, 99, 102, 108, 109, 111, 115, 116, 119, 121, 123, 124], "excerpt": 35, "excess": 15, "exchang": 35, "excit": 40, "exclud": [1, 51, 68, 103], "exclus": [60, 74], "execut": [1, 19, 105], "exemplari": 58, "exist": [1, 7, 9, 11, 16, 23, 26, 38, 40, 62, 77, 78, 79, 84, 85, 87, 103, 104, 107, 110, 116, 120, 123], "exit": [91, 120], "exit_cod": 78, "exitcod": 66, "exp_dat": [69, 117, 118], "exp_tim": [69, 117, 118], "expand": [1, 6, 9, 28, 49, 68, 69, 90, 103, 104], "expans": 1, "expat": [77, 94, 125], "expaterror": 94, "expect": [1, 3, 11, 12, 14, 23, 26, 40, 70, 77, 82, 84, 85, 87, 89, 94, 99, 109, 116, 122, 123], "expens": [7, 9, 20, 76], "experi": [19, 23, 109], "experiment": [1, 18], "expir": [1, 82], "expireddeprecationerror": [1, 65, 76, 80, 94, 98, 108, 112, 120], "explain": [3, 16, 19, 20, 40, 43, 49, 52, 72, 123], "explan": [19, 35, 38, 40, 43, 80], "explanatori": 35, "explicit": [1, 2, 3, 7, 8, 9, 35, 40, 90, 103], "explicitli": [1, 15, 16, 35, 105], "explor": 19, "expon": [76, 77], "export": 109, "expos": [1, 7, 15, 78, 103], "express": [9, 33, 35, 38, 58, 86, 90, 97, 102, 115], "ext": [89, 106, 123], "ext1": 89, "ext2": 89, "ext_fnam": 94, "ext_map": 1, "ext_offset": 94, "extend": [1, 6, 9, 28, 37, 49, 57, 65, 71, 80, 103, 105, 118, 119], "extended_mysoft": 9, "extens": [1, 10, 13, 17, 25, 27, 28, 37, 59, 61, 74, 77, 87, 89, 103, 104, 106, 109, 119, 123], "extension_cod": [77, 103], "extensionspec": [74, 77, 84, 87, 89, 92, 94, 109], "extensionwarn": 65, "extent": [1, 61, 62, 68, 69, 103, 117, 118], "extern": [1, 19, 27, 58, 94], "externalfilebinari": [1, 94], "extra": [2, 3, 4, 9, 10, 66, 69, 74, 77, 80, 84, 87, 92, 94, 99, 100, 102, 103, 104, 106, 109, 111, 116, 117, 118], "extra_argv": 66, "extract": [3, 34, 40, 74, 78, 79, 84, 94, 102, 113], "extran": 1, "extrem": 123, "extrins": 86, "exts2par": 65, "exts_klass": [65, 103], "exts_sourc": 109, "ey": [2, 7, 30, 41, 54, 61, 62, 65, 68, 77, 81, 86, 87, 93, 97, 103, 116], "f": [1, 2, 8, 11, 15, 26, 35, 36, 38, 40, 43, 56, 69, 70, 71, 73, 76, 78, 90, 91, 92, 99, 103, 108, 116, 123], "f1": 123, "f2": 11, "f4": [69, 76, 84, 87, 92, 103, 116, 117, 118], "f745dc2": 20, "f8": [12, 104], "f_": [38, 40], "fa": 11, "fabian": [1, 56], "face": [2, 3, 15, 92, 110], "facilit": 37, "facility_nam": 84, "fact": [2, 3, 4, 6, 7, 8, 9, 12, 30, 31, 35, 60, 61, 62, 77, 84, 86, 87, 116], "factor": [1, 4, 62, 69, 70, 74, 102, 109], "factori": [77, 110], "fail": [1, 3, 22, 23, 68, 78, 102, 123], "failur": 1, "fairli": [1, 8, 10, 20, 30, 102], "faith": [43, 52], "fall": [59, 102, 103], "fals": [1, 7, 11, 12, 22, 27, 29, 40, 43, 55, 61, 66, 68, 69, 70, 71, 72, 73, 74, 76, 77, 80, 82, 83, 84, 86, 89, 90, 92, 93, 94, 99, 100, 102, 103, 104, 106, 107, 109, 110, 113, 116, 118, 119, 120, 121, 123, 124], "familiar": [2, 7, 19], "famou": 34, "fanci": [2, 43, 90], "faq": 38, "far": [1, 2, 3, 9, 55, 56, 74], "farm": 27, "farray_data": 61, "farray_img": 61, "fast": [12, 18, 27, 31, 51], "faster": [7, 76, 102, 119], "fastest": [3, 8, 9, 12, 18, 69, 76, 103, 106], "fat": [101, 109], "fauber": [1, 56], "faulti": 36, "favor": [1, 7, 13, 80], "fd": [35, 37], "fdata": [14, 80], "featur": [6, 16, 19, 22, 27, 28, 34, 38, 41, 42, 46, 51, 52, 57, 59, 62, 66], "feb": 59, "februari": 59, "fedora": [1, 47, 56], "fee": 58, "feedback": 6, "feel": 19, "feet": 60, "felt": 19, "fernando": [1, 49, 56], "fetch": [30, 43, 51, 53, 55, 61, 84, 87, 116], "few": [1, 9, 23, 26, 43, 109], "fewer": [1, 62, 90, 103, 109, 111], "ff": 51, "ff00": 35, "fffe": 40, "ffff": 35, "ffffffffh": 35, "fh": [78, 109], "fiber_delimit": [65, 119], "fiction": 2, "fiduci": 35, "field": [1, 2, 3, 6, 10, 11, 12, 30, 31, 32, 36, 38, 39, 40, 62, 65, 69, 76, 77, 78, 92, 102, 103, 104, 106, 109, 117, 118, 123, 124, 126], "field1": 123, "field2": 123, "field_skip": [69, 117, 118], "field_strength": 101, "fieldmap": 58, "fieldnam": 124, "fifth": 12, "fig": [2, 65, 122], "figur": [2, 35, 41, 78, 116, 122], "figure_c": 102, "file": [1, 2, 3, 6, 7, 8, 9, 10, 11, 12, 14, 15, 20, 21, 22, 25, 26, 27, 31, 32, 36, 37, 38, 39, 41, 42, 43, 52, 54, 56, 58, 59, 62, 63, 64, 65, 66, 69, 70, 71, 72, 73, 74, 77, 78, 79, 80, 84, 85, 87, 88, 89, 90, 91, 92, 94, 97, 98, 99, 100, 102, 103, 104, 106, 117, 118, 119, 120, 123, 124, 125, 126], "file_": 73, "file_arrai": 30, "file_head": 78, "file_lik": [65, 70, 73, 74, 88, 92, 102, 109], "file_list": 79, "file_map": [3, 61, 69, 74, 77, 80, 84, 87, 88, 92, 94, 99, 100, 103, 104, 109, 116, 117, 118], "file_typ": [69, 80, 84, 87, 92, 99, 100, 118], "filebasedhead": [65, 74, 77, 80, 99, 100, 109, 116, 125], "filebasedimag": [0, 1, 65, 80, 98, 116], "filehandl": [1, 65], "filehold": [0, 61, 65, 69, 74, 80, 84, 87, 92, 94, 99, 100, 109, 118], "fileholdererror": 65, "fileio": 106, "fileish": 65, "filemap": [74, 80, 87, 99, 100, 109, 116], "filenam": [1, 2, 7, 10, 20, 27, 30, 40, 41, 61, 64, 69, 70, 74, 77, 80, 84, 87, 88, 89, 92, 93, 94, 98, 99, 100, 102, 103, 104, 106, 109, 116, 117, 118, 119, 123], "filename_pars": [0, 1, 65], "fileno": [65, 106], "fileobj": [11, 69, 70, 71, 74, 84, 87, 88, 90, 91, 92, 99, 103, 106, 109, 116, 119, 123, 124], "fileobject": [88, 92, 106], "fileoffset": 94, "filepath": 92, "filepoint": 39, "fileroot": 89, "files_typ": [65, 69, 74, 77, 84, 87, 92, 94, 99, 103, 109, 118], "filesl": 73, "fileslic": [0, 1, 65], "filesniff": 87, "filespec": [64, 74, 80, 84, 87, 89, 92, 98], "filespec_to_fil": 1, "filespec_to_file_map": [65, 74, 87, 92], "filesystem": [1, 27, 83], "filetyp": 1, "fileutil": [0, 65], "fill": [1, 3, 14, 26, 38, 55, 69, 80, 84, 90, 103, 111, 114, 123], "fill_ctab": 92, "fill_slic": 65, "fillposit": 65, "filo": 1, "filter": [1, 10, 61, 94, 102, 116], "filterdwiiso": 65, "filtermultistack": 65, "final": [1, 2, 6, 12, 15, 16, 26, 36, 43, 52, 61, 68, 91, 92, 93, 94, 103, 108, 109, 119, 120, 122], "finalize_append": [65, 119], "find": [1, 2, 3, 6, 9, 19, 23, 40, 43, 45, 48, 52, 56, 60, 62, 76, 77, 79, 85, 90, 102, 123], "find_data_dir": 65, "find_private_sect": [1, 65], "fine": [4, 6, 14, 41, 57], "finfo": [1, 76, 113, 123], "finger": [38, 86], "finish": [22, 26, 43, 52], "finit": [71, 103, 123], "finite_rang": [65, 71, 73], "fire": [57, 105], "first": [1, 2, 3, 6, 8, 9, 11, 12, 13, 19, 20, 30, 31, 35, 36, 38, 39, 43, 51, 53, 54, 55, 60, 61, 62, 64, 68, 69, 74, 76, 77, 84, 85, 86, 87, 89, 90, 102, 103, 106, 108, 109, 113, 116, 119, 123], "first_level": 15, "fischer": 56, "fissel": 9, "fiswidget": 9, "fit": [3, 58, 123], "fix": [19, 22, 23, 26, 31, 40, 42, 43, 52, 57, 59, 72, 76, 84, 86, 90, 92, 119, 120, 124], "fix_msg": 72, "fix_problem_msg": 72, "fixabl": 124, "fixed_obj": 72, "fixup": 43, "fl": 35, "flag": [1, 26, 27, 31, 35, 40, 43, 52, 69, 78, 80, 90, 103, 109, 123, 124], "flake8": [1, 22], "flat": 1, "flatten": 123, "flaw": 23, "flexibl": [10, 15, 41, 103], "flip": [1, 2, 36, 38, 60, 61, 62, 69, 93, 102, 103, 108, 109, 116, 123], "flip_angl": 92, "flip_axi": [1, 65], "flipud": [103, 108, 116], "flirt": 27, "float": [1, 9, 15, 17, 18, 25, 29, 35, 60, 61, 69, 70, 71, 76, 77, 78, 80, 87, 90, 92, 94, 97, 99, 100, 101, 102, 103, 104, 108, 109, 111, 113, 114, 116, 117, 118, 119, 122, 123], "float128": [1, 76], "float16": 1, "float32": [1, 30, 62, 71, 76, 99, 100, 103, 116, 119, 123], "float64": [1, 14, 18, 61, 64, 69, 76, 78, 80, 113, 123], "float96": 76, "float_to_int": 65, "floatingerror": 65, "floor": [2, 76], "floor_exact": 65, "floor_log2": 65, "floor_val": 76, "flt_type": 76, "flush": 1, "flush_chardata": [65, 77, 94], "fly": [43, 124], "fm_copi": 88, "fmap": [87, 116], "fmr": 18, "fmri": 77, "fmristat": 12, "fmt": 102, "fname": [3, 4, 7, 27, 30, 64, 74, 79, 87, 89, 94, 116, 120, 123, 125], "fname2": 64, "fname3": 64, "fname_ext_ul_cas": 65, "fnameext2": 89, "fno": 78, "fnum": 92, "fobj": [4, 65, 74, 87, 91, 106, 109, 120], "focu": 19, "focus": [6, 22], "folder": [11, 26], "folk": 2, "follow": [1, 2, 3, 6, 7, 8, 9, 14, 15, 18, 19, 22, 23, 26, 31, 35, 38, 39, 40, 41, 42, 43, 50, 52, 53, 56, 57, 58, 61, 62, 69, 70, 72, 77, 78, 79, 84, 86, 87, 94, 102, 103, 109, 111, 112, 117, 118, 119], "followlink": 83, "followup": 6, "foo": [43, 89], "foot": [2, 38, 109], "footer": [1, 92], "footnot": [20, 35, 60], "forc": [1, 14, 26, 51, 71, 87, 90, 102, 105], "forget": [3, 4, 26, 40], "forgot": 43, "fork": [43, 46, 50, 52], "form": [1, 2, 3, 9, 12, 15, 26, 35, 38, 40, 51, 58, 61, 62, 77, 79, 84, 92, 103, 110, 113, 119], "formal": 23, "format": [1, 2, 9, 10, 11, 12, 14, 16, 19, 21, 22, 25, 26, 28, 30, 31, 32, 34, 40, 41, 42, 52, 56, 57, 60, 61, 62, 65, 66, 70, 72, 74, 75, 77, 78, 79, 80, 84, 86, 87, 91, 92, 94, 98, 99, 100, 102, 103, 104, 114, 116, 117, 118, 119, 123, 126], "format_": 78, "format_char": 114, "formatt": [1, 22], "former": [1, 23], "formula": [32, 40, 86, 113], "fortran": [8, 12, 60, 70, 71, 103, 123], "forum": [1, 6, 77, 104], "forum8": 18, "forward": [9, 23, 51, 79, 80], "found": [1, 19, 22, 23, 31, 37, 77, 79, 86, 87, 89, 91, 92, 94, 102, 116, 123], "foundat": 49, "four": [6, 20, 28, 35, 77, 92, 102], "four_to_thre": [10, 65], "fourth": [9, 38, 62, 74, 92, 122, 123], "fov": [92, 109], "fp": [64, 109], "fptr": 125, "fr": 34, "frac": 38, "frame": [1, 10, 12, 13, 32, 35, 37, 38, 84, 86, 102], "frame0": 84, "frame_data": 102, "frame_dict": 84, "frame_filt": 102, "frame_offset": 84, "frame_ord": [65, 102], "framecontentsequ": 102, "framefilt": 65, "framenumb": 84, "framework": [1, 9, 26], "free": [1, 3, 6, 56, 58, 80], "freec84": [1, 56], "freeli": 19, "freepasc": 31, "freesurf": [0, 1, 15, 28, 56, 65, 66, 94, 103, 104, 111], "freesurfersubject": 15, "freez": [1, 70, 84, 99], "freq": [103, 109], "frequenc": [9, 12, 35, 61, 103], "frequency1": 9, "frequency2": 9, "frequent": 7, "fresh": [69, 103], "freshli": 68, "fri": 59, "fridai": 59, "friend": [2, 57], "friendli": [1, 119], "from": [1, 3, 4, 6, 7, 8, 10, 11, 12, 14, 16, 18, 22, 23, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 37, 39, 40, 41, 42, 44, 47, 49, 50, 51, 53, 54, 55, 56, 57, 58, 60, 61, 62, 64, 68, 69, 70, 71, 72, 74, 76, 77, 78, 79, 80, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 96, 98, 99, 100, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 115, 116, 117, 118, 119, 120, 122, 123, 124, 125, 126], "from_arrai": 1, "from_ax": [65, 77], "from_brain_model": [65, 77], "from_byt": [1, 65, 87, 103], "from_data_func": [65, 119], "from_dict": [65, 94], "from_fil": 1, "from_file_map": [3, 65, 69, 74, 77, 80, 84, 87, 92, 94, 99, 100, 109, 116, 118], "from_fileboj": 3, "from_filenam": [15, 65, 80, 87, 94, 109, 116], "from_fileobj": [65, 74, 87, 92, 103, 109, 124], "from_filespec": 1, "from_head": [65, 69, 74, 87, 92, 103, 109, 116], "from_imag": [65, 77, 84, 87, 110, 116], "from_img": 111, "from_index_map": 65, "from_mask": [65, 77, 110], "from_matvec": 65, "from_nifti": 11, "from_object": [65, 103], "from_spec": 15, "from_stream": [1, 65, 87], "from_surfac": [65, 77], "from_tractogram": [65, 119], "from_url": [1, 65, 87], "fromstr": 1, "front": [1, 2, 18, 38, 108], "froot": 89, "fs_subject": 15, "fsaverage5": 15, "fsl": [1, 7, 9, 27, 28], "fsl3": [61, 62], "fslr": 15, "fslr_hemi": 15, "fslstat": [1, 97], "fslview": 1, "fswiki": 92, "ftype": 123, "fulfil": 1, "full": [1, 2, 4, 6, 8, 9, 15, 20, 22, 23, 26, 28, 35, 36, 40, 55, 56, 57, 66, 69, 74, 77, 79, 80, 81, 84, 90, 109, 113, 118, 123], "fulli": [1, 7, 15, 19, 31, 35, 60, 123], "func": [0, 1, 10, 15, 65, 72, 94, 112], "func_def": 106, "func_img": 94, "function": [1, 2, 3, 7, 10, 11, 12, 15, 16, 18, 19, 20, 21, 29, 31, 33, 35, 41, 55, 61, 68, 70, 74, 76, 77, 78, 81, 82, 86, 90, 92, 93, 94, 97, 102, 103, 105, 106, 107, 111, 112, 113, 115, 116, 119, 123], "functional_01": 30, "functool": 1, "fund": 23, "fundament": [23, 28, 35], "funni": [38, 52, 89], "funused1": 69, "funused2": [69, 118], "funused3": [69, 117, 118], "furnish": 58, "further": [9, 15, 25, 26, 31, 35, 38, 68, 76, 87, 93, 111], "furthermor": 62, "fuse": [1, 65], "fuse_python_api": [65, 78], "fusion": 64, "futur": [1, 12, 14, 19, 21, 23, 26, 80, 81], "futurewarn": [1, 81], "futurewarningmixin": 65, "futz": 64, "fwhm": 111, "fwhm2sigma": 65, "fxd": 26, "f\u00e9lix": [1, 56], "g": [1, 6, 9, 10, 12, 15, 16, 18, 19, 20, 22, 26, 35, 38, 40, 41, 43, 49, 62, 72, 74, 77, 81, 86, 87, 89, 92, 96, 101, 102, 103, 109, 119, 123], "g_vector": 102, "gadd": 56, "gael": 1, "game": 6, "gamma": [2, 86], "gantri": 35, "gantry_rot": 84, "gantry_tilt": 84, "gap": [40, 76, 90, 109], "garbag": 1, "garcia": 1, "garc\u00eda": 56, "garyfallidi": [1, 56], "gather": [6, 33], "gaussian": [15, 111], "gauthier": [1, 56], "gave": [31, 41], "ga\u00ebl": [1, 56], "gcc": [26, 57], "gdcm": [34, 39], "ge": [34, 38, 40, 65, 102], "gem": 86, "gen": 119, "gener": [1, 2, 3, 6, 8, 11, 20, 22, 26, 27, 28, 30, 33, 34, 35, 36, 37, 43, 45, 48, 52, 60, 62, 65, 68, 69, 72, 77, 78, 82, 86, 87, 89, 90, 92, 94, 102, 103, 108, 110, 116, 117, 118, 119, 123, 124, 125], "general_info": 109, "gentl": 19, "geometri": [1, 15, 56, 66, 92], "geometrycollect": 15, "gerhard": [1, 58], "gervai": 56, "get": [1, 2, 3, 4, 7, 8, 9, 10, 13, 14, 20, 22, 26, 27, 29, 30, 32, 33, 34, 35, 39, 40, 42, 43, 47, 48, 49, 50, 52, 53, 55, 59, 62, 64, 65, 69, 70, 71, 76, 77, 78, 80, 82, 84, 85, 89, 90, 92, 99, 102, 103, 104, 107, 108, 109, 111, 116, 117, 118, 119, 123, 124], "get_": [15, 62, 123], "get_acq_mat_txt": 65, "get_affin": [1, 7, 15, 65, 74, 92, 99, 109], "get_affine_from_refer": 65, "get_affine_rasmm_to_trackvi": 65, "get_affine_trackvis_to_rasmm": 65, "get_arrays_from_int": [65, 94], "get_axi": [65, 77], "get_b_matrix": 65, "get_b_valu": 65, "get_base_affin": [62, 65, 69, 116], "get_best_affin": [62, 65, 69, 92, 103, 116, 118], "get_bvals_bvec": [65, 109], "get_cod": [65, 103], "get_cont": [37, 65, 103], "get_coord": [15, 65, 110], "get_csa_head": 65, "get_data": [1, 7, 8, 10, 12, 14, 65, 80, 93, 102, 103, 119], "get_data_bytespervox": [65, 92], "get_data_copi": 7, "get_data_diff": 65, "get_data_dtyp": [3, 30, 41, 61, 62, 65, 69, 70, 74, 77, 84, 92, 99, 100, 103, 109, 116], "get_data_hash_diff": 65, "get_data_offset": [65, 69, 70, 74, 92, 109], "get_data_path": 65, "get_data_s": [65, 92], "get_data_sc": [65, 74, 109], "get_data_shap": [3, 61, 65, 69, 70, 74, 77, 92, 99, 100, 103, 104, 109, 116, 117, 118], "get_def": [65, 109], "get_dim_info": [65, 103], "get_echo_train_length": [65, 109], "get_el": [65, 77], "get_empty_head": 11, "get_fdata": [1, 2, 15, 27, 41, 59, 60, 61, 62, 65, 66, 74, 80, 84, 87, 100, 115, 116], "get_filenam": [7, 27, 30, 61, 65, 79, 87], "get_filetyp": [65, 84], "get_footer_offset": [65, 92], "get_fram": [65, 84], "get_frame_affin": [65, 84], "get_frame_ord": 65, "get_g_vector": 65, "get_graph": 15, "get_head": [1, 7], "get_headers_diff": 65, "get_home_dir": 65, "get_ice_dim": 65, "get_index_map": [65, 77], "get_indic": 15, "get_info": 65, "get_int": [65, 103], "get_labels_as_dict": [65, 94], "get_labelt": 1, "get_longer_field": 123, "get_magic_numb": 11, "get_mesh": 15, "get_meta": [1, 10], "get_metadata": 1, "get_mlist": [65, 84], "get_n_mosa": 65, "get_n_slic": [65, 103], "get_nam": 15, "get_nfram": [65, 84], "get_nibabel_data": 4, "get_nipy_system_dir": [65, 79], "get_nipy_user_dir": [65, 79], "get_obj_dtyp": 65, "get_object": [65, 103], "get_opt_pars": 65, "get_origin_affin": [65, 118], "get_path": [65, 78], "get_patient_ori": [65, 84], "get_pixel_arrai": [65, 102], "get_prepare_fileobj": [65, 88], "get_q_vector": [65, 109], "get_qform": [62, 65, 103], "get_qform_quaternion": [65, 103], "get_ras2vox": [65, 92], "get_rec_shap": [65, 109], "get_rgba": 1, "get_scalar": 65, "get_scaled_data": [65, 99, 100], "get_series_framenumb": 65, "get_sform": [62, 65, 103], "get_shap": [1, 65, 84], "get_sizeondisk": [65, 103], "get_slic": 8, "get_slice_dur": [65, 103], "get_slice_norm": 65, "get_slice_orient": [65, 109], "get_slice_tim": [65, 103], "get_slope_int": [62, 65, 69, 70, 74, 92, 103, 117, 118], "get_sorted_slice_indic": [65, 109], "get_spac": [65, 74], "get_structur": 15, "get_studi": 65, "get_subhead": [65, 84], "get_test_data": 94, "get_triangl": 15, "get_unsc": [1, 65, 70, 98, 109, 116], "get_unscaled_data": [65, 102], "get_value_label": [65, 103, 124], "get_vector": 65, "get_volume_label": [65, 74, 109], "get_vox2ra": [65, 92], "get_vox2ras_tkr": [65, 92], "get_water_fat_shift": [65, 109], "get_xyzt_unit": [41, 65, 103], "get_zoom": [61, 65, 69, 74, 84, 92, 99, 103, 104, 116], "getattr": [65, 78], "getcwd": 120, "geteffectivelevel": 96, "getperistimulustimeseri": 1, "getqformcod": 1, "getscaleddata": 1, "getsformcod": 1, "getter": 123, "getterfunc": 123, "gettimeunit": 1, "getting_start": 119, "getvalu": [69, 103, 123, 124], "getxyzunit": 1, "gggg": 35, "ghosh": [1, 56], "gifti": [0, 1, 15, 28, 56, 65, 66], "gifti_encoding_b64gz": 94, "gifti_encoding_cod": 94, "gifticoordsystem": 65, "giftidataarrai": [1, 65], "giftiimag": [1, 15, 65], "giftiimagepars": [1, 65], "giftiio": 1, "giftilabel": 65, "giftilabelt": 65, "giftimetadata": [1, 65], "giftinvpair": 65, "giftiparseerror": 65, "gii": [15, 94], "git": [1, 4, 5, 6, 19, 20, 25, 26, 43, 44, 48, 50, 51, 52, 53, 56, 57, 58, 66, 73], "gitconfig": 42, "githhub": 43, "github": [1, 3, 4, 6, 8, 10, 13, 16, 18, 19, 22, 23, 26, 44, 46, 47, 48, 49, 51, 52, 53, 56, 57, 58, 66, 76, 90, 92, 109], "gitk": 43, "give": [1, 6, 9, 10, 12, 19, 20, 23, 26, 30, 35, 36, 38, 39, 40, 41, 42, 43, 45, 49, 53, 56, 66, 69, 70, 72, 74, 76, 77, 79, 80, 82, 84, 86, 87, 90, 92, 94, 99, 100, 102, 103, 104, 107, 108, 109, 111, 113, 115, 116, 117, 118, 122, 123, 124, 126], "given": [6, 9, 12, 14, 20, 26, 30, 35, 36, 38, 39, 40, 60, 61, 62, 68, 69, 70, 77, 78, 79, 82, 84, 86, 87, 88, 89, 90, 92, 94, 98, 102, 103, 104, 106, 111, 113, 114, 115, 116, 118, 119, 120, 123, 124, 125], "gl": 117, "glean": 92, "glm": 15, "glmax": [61, 62, 69, 103, 117, 118], "glmin": [61, 62, 69, 103, 117, 118], "glob": [79, 102], "global": [19, 20, 42, 52, 76, 96, 102, 109, 124], "globber": 102, "gmail": 58, "go": [1, 2, 3, 6, 7, 8, 9, 20, 22, 26, 30, 35, 38, 40, 42, 43, 45, 51, 52, 53, 57, 60, 61, 70, 76, 77, 78, 80, 90, 102, 114], "goal": 6, "goe": [2, 40, 54, 70], "goncalv": [1, 56], "gone": [6, 9], "gonzalez": [1, 56], "good": [1, 3, 4, 6, 10, 15, 26, 42, 43, 47, 49, 51, 58, 123], "good_fileobj": 124, "goodrasflag": 92, "gordon": 9, "gorgolewski": [1, 56], "gorro\u00f1o": [1, 56], "got": [7, 43, 51, 118], "gov": [9, 74, 103], "govern": [1, 25], "goyett": 1, "gpg": 20, "gpl": [34, 84], "gracefulli": 1, "grad": 109, "grade": 2, "gradient": [9, 12, 102, 109], "grahamwideman": 92, "grai": [2, 15, 60, 103], "gramfort": [1, 56], "grammar": 1, "grant": [23, 58], "graph": [15, 42, 43, 51], "graphic": [43, 60, 86], "graphicsgem": 86, "grassroot": 34, "grate": [19, 56], "graviti": 35, "grayordin": 77, "grayscal": [2, 35], "great": [19, 41], "greater": [35, 39, 40, 57, 82, 114, 116, 123], "greatli": 11, "green": [2, 77, 94], "gregori": [1, 56], "grep": 26, "greyordin": 77, "grid": [1, 2, 15, 36, 65], "gridindic": 65, "gridshap": [65, 110], "group": [22, 23, 35, 40, 77, 102], "group_no": 102, "grow": 41, "guarante": [1, 15, 70, 102, 124], "guess": [3, 64, 69, 79, 84, 85, 87, 89, 90, 98, 103, 117, 118, 119, 124], "guessed_endian": [65, 69, 84, 92, 124], "guessed_image_typ": [1, 65], "guessed_imp": 64, "guessed_typ": 89, "gui": [3, 31, 34], "guid": [1, 12, 15, 23, 25, 43, 49, 105], "guidanc": [81, 113], "guidelin": [1, 6, 19, 23, 25, 56, 66], "guidotti": [1, 56], "guillaum": [1, 56], "gw": 92, "gz": [2, 41, 54, 55, 58, 60, 61, 62, 66, 73, 74, 80, 89, 106], "gz_def": [65, 106], "gzip": [1, 43, 73, 106], "gzipfil": 106, "gztar": 26, "h": [1, 15, 40, 42, 92], "h5py": [1, 12, 57], "ha": [1, 2, 4, 6, 7, 9, 10, 11, 12, 13, 14, 15, 18, 19, 20, 22, 23, 26, 27, 30, 34, 35, 36, 38, 39, 40, 41, 43, 49, 51, 54, 55, 56, 58, 60, 61, 62, 68, 69, 70, 71, 74, 76, 77, 78, 79, 80, 82, 84, 86, 87, 88, 89, 90, 92, 93, 94, 95, 99, 100, 102, 103, 104, 105, 108, 109, 111, 112, 113, 115, 116, 118, 119, 120], "hack": [1, 43, 52, 103, 104], "had": [1, 2, 9, 13, 14, 20, 43, 54, 55, 62, 64, 80, 108, 123], "haenel": 56, "hahn": 56, "haitz": [1, 56], "halchecko": 1, "halchenko": [1, 23, 56, 58], "half": [2, 119], "hamilton_product": [86, 113], "hand": [2, 7, 8, 12, 26, 38, 60, 86], "handed": 86, "handhold": 19, "handl": [1, 10, 43, 69, 70, 74, 80, 84, 90, 92, 99, 100, 103, 108, 109, 118, 123], "handler": 1, "handler_nam": [65, 125], "hank": [1, 23, 31, 56, 58], "hao": [1, 56], "happen": [12, 40, 60, 72, 76, 105, 108, 120], "happi": [43, 92, 109], "happier": 22, "hard": [3, 11, 20, 43, 90], "hardcor": 45, "harder": [1, 19, 60], "harmon": [43, 77, 103, 116], "harvard": 92, "has_affin": [65, 118], "has_data_intercept": [65, 69, 103, 118], "has_data_slop": [65, 69, 103, 118], "has_intercept": 71, "has_label": 9, "has_nan": [65, 71, 123], "has_slop": 71, "hasattr": [79, 107], "hasdtyp": 65, "haselgrov": [1, 56, 58], "hash": [1, 7, 20, 73, 123], "hashabl": 123, "have": [1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 13, 15, 16, 18, 19, 20, 21, 22, 23, 25, 26, 28, 29, 31, 34, 35, 36, 37, 38, 39, 40, 41, 43, 44, 45, 47, 49, 51, 52, 53, 55, 56, 57, 60, 61, 62, 64, 66, 68, 69, 70, 71, 74, 76, 77, 79, 80, 81, 84, 87, 88, 89, 90, 92, 93, 94, 95, 97, 99, 100, 102, 103, 104, 105, 107, 108, 109, 110, 111, 113, 115, 116, 117, 118, 119, 121, 122, 123, 124], "have_binary128": 65, "have_dicom": 112, "have_pkg": 107, "haven": [1, 7, 100], "hayashi": [1, 56], "hcp": 77, "hcp_label": 77, "hcuge": 34, "hdf5": [12, 13, 28], "hdf5bunch": 65, "hdr": [7, 9, 11, 27, 41, 61, 69, 72, 84, 87, 89, 92, 103, 104, 116, 117, 118, 124], "hdr1": [69, 117, 118], "hdr2": [69, 117, 118], "hdr3": [69, 117, 118], "hdr4": [69, 117, 118], "hdr_affine_from": 7, "hdr_data": 69, "hdr_id": 39, "hdr_to_check": 40, "hdr_vol_out": 40, "hdrt": 87, "he": 40, "head": [1, 2, 35, 38, 42, 43, 51, 56, 60, 66, 74, 92, 109, 114], "header": [1, 2, 3, 7, 10, 12, 13, 14, 15, 17, 18, 25, 27, 28, 30, 32, 33, 34, 35, 36, 38, 40, 41, 56, 59, 64, 65, 66, 70, 72, 74, 77, 78, 80, 84, 87, 89, 92, 93, 94, 96, 99, 100, 102, 103, 104, 106, 109, 111, 116, 117, 118, 124], "header_class": [65, 69, 74, 77, 84, 87, 92, 99, 100, 103, 104, 109, 116, 117, 118], "header_dtyp": 69, "header_field": 78, "header_s": [65, 119], "headerdataerror": [1, 65, 69, 72, 74, 103, 118], "headererror": [1, 65], "headerless": 1, "headertypeerror": [65, 69], "headerwarn": 65, "healthi": 20, "heart": 90, "heavi": [9, 10], "heckbert": 86, "hei": 9, "height": 12, "held": 38, "help": [1, 4, 7, 9, 14, 19, 22, 23, 25, 30, 43, 45, 47, 49, 57, 64, 79, 81, 87, 116], "helper": [1, 77, 78, 123], "helplist": 78, "hemi": 15, "hemispher": [2, 15, 77], "henc": [36, 76, 123], "henri": [1, 56], "her": 38, "here": [1, 2, 7, 8, 9, 12, 14, 15, 16, 18, 20, 23, 26, 31, 33, 34, 35, 36, 37, 38, 42, 43, 45, 47, 48, 49, 52, 53, 54, 60, 61, 62, 68, 69, 77, 86, 92, 102, 103, 104, 109, 112, 113, 117, 118, 123], "herebi": 58, "hertz": [12, 77], "hetting": [58, 105], "heurist": [90, 123], "hexadecim": 35, "hhuuggoo": 42, "hidden": [8, 28], "high": [16, 18, 27, 41, 64, 82], "highbit": 40, "higher": [28, 69, 76, 82, 102, 103, 104, 123], "highest": [76, 109], "highli": [1, 6, 9, 86], "highlight": 119, "hind": 56, "hint": 79, "hire": [2, 60], "hist_un0": [69, 117, 118], "histor": 6, "histori": [6, 19, 20, 49, 57], "hit": [7, 77, 94], "hkey_un0": [69, 117, 118], "hl7": 35, "hold": [20, 23, 26, 76, 80, 87, 102, 116, 124], "hold_val": 30, "holder": [58, 87, 116], "holdov": 74, "holroyd": 1, "home": [20, 26, 42, 45, 56, 85], "home_dir": 85, "homedir": 85, "homogen": [2, 65, 68, 69, 74, 77, 84, 92, 99, 100, 103, 104, 109, 110, 111, 115, 116, 117, 118], "honor": 1, "hope": [1, 3, 9, 20, 35], "horea": [1, 56], "horizont": 38, "horsfield": 9, "host": [1, 35, 48], "houd": 1, "hour": 9, "hous": 64, "how": [2, 7, 8, 9, 10, 11, 13, 16, 20, 23, 25, 27, 28, 35, 36, 40, 49, 51, 52, 57, 60, 62, 70, 74, 77, 80, 84, 102, 105, 109, 119, 122, 125], "howev": [1, 6, 10, 20, 22, 29, 36, 43, 58, 61, 62, 77, 103, 116], "howto": 105, "hrn\u010diar": [1, 56], "htm": [9, 34, 92], "html": [1, 6, 7, 8, 9, 18, 22, 26, 38, 74, 79, 81, 86, 105, 113, 119], "http": [1, 4, 6, 7, 8, 9, 10, 13, 18, 20, 22, 26, 31, 34, 38, 43, 45, 47, 56, 57, 58, 66, 74, 76, 77, 79, 81, 84, 86, 90, 92, 94, 102, 103, 104, 105, 109, 113, 119], "hu": 31, "huge": [1, 7], "hugo": 42, "human": [1, 72], "hundr": 4, "hxist": 84, "hymer": [1, 56], "i": [1, 2, 3, 4, 7, 8, 9, 10, 11, 12, 13, 14, 16, 18, 19, 20, 22, 23, 24, 26, 27, 28, 29, 30, 31, 32, 33, 34, 36, 37, 39, 40, 41, 42, 43, 48, 49, 51, 52, 53, 54, 55, 56, 57, 58, 60, 61, 62, 64, 66, 68, 69, 70, 71, 72, 74, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 92, 93, 94, 96, 99, 100, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 126], "i1": 104, "i2": [69, 74, 92, 103, 104, 117, 118, 123, 124], "i386": 20, "i4": [12, 69, 76, 92, 103, 104, 117, 118, 123], "i8": 104, "i_1": 38, "i_2": 38, "i_3": 38, "i_4": 38, "i_5": 38, "i_6": 38, "ian": [1, 56], "iarr": 76, "ibm": 76, "ic": [40, 102], "ice1": 40, "ice2": 40, "ice_dim": 40, "ico7": 1, "icosahedron": 103, "id": [1, 35, 84, 92], "id_dict": 84, "idea": [2, 3, 6, 9, 19, 26, 41, 60, 64, 84], "ideal": [11, 22], "ident": [2, 4, 27, 29, 40, 41, 54, 61, 77, 103, 111, 113, 115, 116, 122], "identif": 35, "identifi": [1, 9, 10, 17, 20, 25, 28, 31, 33, 35, 40, 56, 66, 77, 84, 102, 110], "identity_thresh": 113, "idiosyncrasi": 102, "idx": [10, 77, 109], "ie": [9, 35], "ieee": [1, 76, 102], "iff": [40, 69, 102, 106], "ifmt": 123, "ignor": [1, 6, 7, 9, 22, 38, 69, 70, 71, 74, 77, 80, 84, 88, 89, 92, 93, 94, 99, 100, 102, 106, 109, 118, 123], "igor": [1, 56], "ii": 43, "iin": 22, "iinfo": 123, "ij": 102, "ijk": [60, 77], "illustr": [16, 19], "im": [9, 52, 109], "imag": [1, 4, 8, 17, 18, 20, 21, 23, 25, 28, 29, 31, 32, 35, 36, 37, 38, 39, 40, 41, 56, 57, 58, 59, 60, 65, 66, 68, 69, 70, 73, 74, 77, 78, 80, 84, 87, 89, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 106, 110, 111, 115, 116, 117, 118, 122, 123, 125, 126], "image_data": [61, 119], "image_def": 109, "image_info": 109, "image_nam": 9, "image_orient_pati": [65, 102], "image_posit": [1, 65, 102], "image_shap": [65, 102], "image_type_mr": 109, "imagearrayproxi": [65, 69, 74, 84, 92, 99, 109], "imageclass": [0, 1, 65], "imagedataerror": [65, 74], "imagedelimitationitem": 40, "imageerror": 64, "imagefileerror": [65, 74, 92, 103, 104], "imageglob": [0, 65, 124], "imageioerror": 64, "imageopen": [65, 88], "imageorientationpati": [36, 38, 40], "imageorientpati": 102, "imagepositionpati": [36, 38, 40], "images_and_memori": [7, 8], "imageslic": [65, 116], "imagestat": [0, 1, 65], "imagetyp": [31, 40], "imagin": [2, 20, 27, 38, 60, 102, 108, 115], "imax": 123, "img": [1, 7, 8, 9, 12, 14, 15, 27, 30, 41, 54, 55, 60, 61, 62, 64, 65, 66, 69, 77, 78, 80, 84, 87, 89, 92, 93, 94, 95, 97, 98, 99, 100, 102, 103, 104, 111, 116, 118], "img1": [27, 66, 115], "img2": [7, 27, 64, 66, 93, 115, 116], "img3": [64, 66, 116], "img4": 64, "img5": 64, "img_a": 87, "img_again": 61, "img_arr": [30, 69], "img_b": 87, "img_data": 60, "img_fnam": 80, "img_ornt_pat": 38, "img_po": 102, "img_slice_4": 30, "imgt": 87, "imin": 123, "immedi": [43, 61, 80, 92], "immut": [8, 17, 25], "impact": [6, 23], "implaus": [69, 103], "implement": [1, 3, 6, 7, 8, 10, 11, 12, 13, 15, 19, 20, 21, 28, 30, 33, 34, 35, 42, 43, 56, 62, 66, 68, 69, 70, 72, 74, 84, 86, 87, 88, 90, 91, 92, 95, 99, 102, 103, 109, 110, 116, 119, 123, 124], "impli": [9, 11, 12, 20, 35, 36, 51, 58, 62, 86, 87, 90, 98, 103, 108, 111, 115, 116, 118, 123], "implic": [1, 22], "implicit": [3, 7, 35, 76], "implicitli": 7, "import": [1, 2, 3, 4, 6, 7, 9, 11, 15, 19, 20, 22, 27, 29, 36, 37, 41, 54, 55, 57, 60, 61, 62, 64, 66, 69, 72, 73, 74, 76, 77, 79, 80, 81, 84, 87, 93, 94, 97, 100, 103, 107, 112, 113, 116, 118, 120, 121, 122, 123, 124], "importerror": [1, 107], "importlib": 57, "impract": 6, "impress": 2, "improv": [1, 28], "imshow": [2, 36, 60], "in_dtyp": 123, "in_fileobj": 11, "in_img": 111, "in_len": 90, "in_memori": [1, 7, 59, 65, 80], "in_shap": 90, "in_typ": 123, "inabl": 69, "inc": [26, 57], "incident": 58, "includ": [1, 2, 4, 6, 9, 10, 12, 15, 16, 20, 23, 26, 28, 35, 37, 43, 51, 56, 58, 66, 70, 72, 76, 77, 82, 88, 90, 91, 92, 103, 106, 113, 116, 119, 122, 123], "inclus": 6, "incompat": [1, 13, 78], "incomplet": [1, 109], "inconsist": 119, "inconveni": 2, "incorpor": [6, 30, 51], "incorrect": [1, 36], "incorrectli": 9, "increas": [1, 17, 19, 28, 35, 38, 60, 66, 76, 77, 103, 104, 123], "increment": [1, 33, 77, 81, 122], "ind_ord": 94, "inde": 120, "indent": 78, "independ": [2, 15, 41, 74, 77, 87, 109, 116], "index": [1, 2, 6, 9, 10, 12, 15, 18, 26, 30, 31, 38, 40, 56, 59, 70, 74, 77, 84, 90, 94, 102, 103, 109, 115, 116, 119, 122, 123], "index_bi": 42, "index_count": 77, "index_offset": 77, "indexcount": 77, "indexed_gzip": 1, "indexedgzipfil": 1, "indexoffset": 77, "indic": [1, 2, 6, 9, 15, 35, 38, 40, 70, 77, 90, 92, 94, 102, 103, 109, 110, 116, 118, 119, 122], "indices_map_to_data_typ": 77, "indicesmaptodatatyp": 77, "indirect": 58, "indirectli": 35, "individu": [8, 9, 10, 12, 35, 62, 77, 86], "induc": 61, "ineffici": 15, "inexact": 14, "inf": [40, 76, 103, 117, 118, 119, 123], "infer": [1, 109, 122], "inferior": [2, 12, 18, 54, 69, 94, 109], "infil": [78, 123], "infin": 76, "infinit": [1, 76], "inflat": 15, "influenc": 108, "infmax": 76, "info": [1, 9, 26, 30, 39, 74, 76, 109], "inform": [1, 2, 3, 6, 9, 10, 11, 12, 15, 18, 20, 23, 28, 30, 32, 33, 37, 38, 39, 40, 41, 43, 47, 56, 57, 61, 65, 66, 69, 74, 77, 79, 80, 84, 87, 92, 94, 99, 100, 102, 103, 104, 106, 115, 116, 117, 118, 119, 124, 126], "ingivendirectori": 65, "inher": 35, "inherit": [3, 10, 11, 62, 69, 74], "ini": [73, 79], "init": [4, 5, 71], "init_bed_posit": 84, "initi": [1, 22, 23, 41, 42, 44, 69, 70, 71, 72, 74, 76, 77, 79, 80, 84, 87, 88, 92, 99, 100, 102, 103, 104, 105, 108, 109, 116, 117, 118, 119, 123, 124], "initialis": [62, 103, 104], "inject": 6, "injuri": 58, "inner": 38, "innov": 9, "inplac": [68, 72], "input": [1, 2, 6, 9, 10, 11, 19, 60, 68, 70, 71, 77, 82, 86, 87, 90, 93, 94, 102, 108, 111, 113, 115, 116, 123], "input_str": 102, "insensit": 89, "insert": [35, 40, 43, 65, 69, 77, 81, 84, 92, 117, 118, 119, 124], "insid": [7, 28, 56, 57, 70, 80, 90, 102], "insight": 19, "inspect": [1, 61, 79], "inspir": [31, 68], "instabl": [86, 113], "instal": [1, 4, 22, 26, 31, 34, 37, 43, 44, 50, 59, 65, 79], "installing_lazarus_on_macos_x": 31, "instanc": [1, 14, 33, 35, 43, 61, 69, 70, 71, 72, 74, 77, 79, 80, 83, 84, 87, 88, 92, 93, 94, 96, 99, 100, 102, 103, 104, 105, 106, 107, 109, 111, 116, 117, 118, 119, 122, 123], "instance_numb": [65, 102], "instance_to_filenam": [65, 87, 116], "instancenumb": [31, 40], "instancestackerror": 65, "instanti": [7, 119], "instead": [1, 2, 9, 11, 14, 19, 37, 38, 51, 52, 59, 69, 74, 76, 77, 87, 92, 94, 98, 102, 103, 107, 108, 109, 118, 119, 123], "institut": [2, 58], "instruct": [1, 6, 23, 26, 31, 43, 44, 45, 47, 51, 53, 56, 57], "instrument": [22, 102], "int": [1, 9, 18, 66, 70, 71, 72, 76, 77, 80, 81, 82, 87, 88, 90, 91, 92, 93, 94, 97, 99, 100, 101, 102, 103, 106, 109, 110, 111, 115, 116, 119, 122, 123, 124, 125], "int16": [9, 14, 41, 55, 61, 62, 76, 103, 123, 124], "int32": [39, 76, 84, 93, 103, 123], "int64": [1, 103], "int8": [71, 76, 123], "int_ab": 65, "int_scinter_ftyp": 65, "int_to_float": [1, 65], "int_typ": 76, "integ": [1, 2, 8, 9, 14, 30, 35, 40, 41, 61, 69, 71, 72, 76, 77, 90, 92, 94, 103, 104, 106, 109, 111, 119, 123, 124], "integr": [1, 10, 28, 46, 69, 76, 103], "intel80": 76, "intemporarydirectori": [1, 65], "intend": [7, 16, 20, 22, 26, 28, 62, 69, 124], "intens": [5, 30, 57], "intent": [1, 15, 94, 103], "intent_cod": [61, 62, 94, 103, 104], "intent_nam": [61, 62, 103, 104], "intent_p1": [61, 62, 103, 104], "intent_p2": [61, 62, 103, 104], "intent_p3": [61, 62, 103, 104], "inter": [1, 15, 62, 65, 69, 70, 71, 103, 118, 123], "interact": [1, 23, 43, 116], "intercept": [1, 14, 21, 25, 62, 69, 70, 71, 102, 103, 109, 116, 117, 118, 123], "interchang": 35, "interest": [6, 11, 16, 20, 23, 33, 35, 61, 77], "interfac": [1, 6, 7, 11, 15, 27, 30, 61, 64, 65, 69, 77, 87, 102, 119, 125], "interleav": 58, "intermedi": [49, 90], "intern": [1, 7, 14, 21, 41, 55, 80, 92, 119, 124], "internet": 4, "interpol": [15, 111], "interpret": [2, 22, 60, 62, 77, 90, 103, 111, 124], "interrupt": [58, 90], "intrins": [35, 86], "intrinsic_tilt": 84, "intro": 26, "introduc": [1, 126], "introduct": [22, 32, 41, 49, 50, 126], "introductori": 1, "inv": [2, 30, 115], "inv_ornt_aff": 65, "invalid": [1, 9, 76, 103, 105, 118, 119], "invers": [65, 77, 92, 126], "invert": [2, 108], "investig": [13, 53], "invit": 23, "invok": 57, "involv": [8, 20, 22, 23, 28, 77], "invq": 113, "io": [22, 26, 30, 43, 58, 65, 69, 77, 87, 88, 94, 103, 106, 116, 119, 123, 124], "io_obj": 87, "io_orient": 65, "iobas": [1, 87, 88, 106, 116, 123], "iod": 35, "ioerror": 1, "ioimp": 64, "iop": [35, 102], "ipython": [43, 61, 119], "is_4d": 31, "is_array_sequ": 65, "is_as_load": 7, "is_bad": 82, "is_bad_vers": [65, 82], "is_correct_format": [11, 65, 119], "is_csa": [65, 102], "is_data_dict": 65, "is_dirti": 27, "is_fanc": 65, "is_ful": 109, "is_lazy_dict": 65, "is_mosa": 65, "is_multifram": [65, 102], "is_ndarray_of_int_or_bool": 65, "is_norm": [86, 113], "is_process": 40, "is_proxi": [7, 8, 61, 65, 80, 84, 99, 109], "is_same_seri": [65, 102], "is_singl": [65, 103, 104], "is_sliced_view": [65, 119], "is_slowest": 90, "is_support": 65, "is_tripwir": 65, "isbn": 86, "iscontingu": 8, "isfil": 120, "isinst": 7, "isn": [7, 38, 61, 100, 105], "iso": 109, "isocent": [2, 60, 109], "isol": 22, "isort": 1, "isort_": 22, "isotope_halflif": 84, "isotope_nam": 84, "isotrop": [102, 111], "issn": 113, "issu": [1, 3, 6, 8, 13, 16, 22, 23, 25, 26, 28, 43, 76, 90, 103, 109], "issubclass": 14, "issubdtyp": 1, "issuecom": 13, "issuer": 35, "issuerofpatientid": 35, "issuerofpatientidqualifierssequ": 35, "ist": 86, "isunit": 65, "item": [1, 28, 35, 62, 65, 117, 119, 123, 124], "item_len": 39, "items": [72, 90], "iter": [11, 13, 72, 77, 102, 103, 113, 119, 123], "iter_img": 13, "iter_structur": [65, 77], "ith": [77, 94], "itk": [28, 34, 38], "its": [1, 2, 6, 9, 10, 11, 13, 16, 23, 26, 27, 28, 35, 36, 38, 41, 55, 58, 68, 70, 80, 82, 87, 92, 94, 103, 116, 119, 120, 123], "itself": [6, 10, 19, 20, 23, 35, 56, 61, 66, 87, 116], "ityp": 76, "itzhack": 113, "iv": 86, "ivan": [1, 56], "ivanov": [1, 122], "j": [1, 2, 6, 9, 12, 30, 32, 36, 56, 60, 86, 102, 113, 119], "ja": 40, "jaakko": [1, 56], "jacob": [1, 56], "jakub": [1, 56], "jame": 86, "januari": [40, 59, 119], "jarecka": [1, 56], "jarrod": [1, 6, 23, 56, 58], "jasper": [1, 56], "jath": [1, 56], "jb": [1, 56], "jean": [1, 56], "jeff": 1, "jerom": 1, "jesper": 40, "jg": 26, "jgfz12zxhwgsfkd85xlplk": 26, "jim": 9, "job": [3, 20, 27, 29, 34, 38, 76, 90, 115], "john": [40, 117], "johnson": 1, "joi": 4, "join": [10, 23, 41, 54, 55, 61, 62, 74, 79, 80, 84, 120], "jon": [1, 56], "jonathan": [1, 42, 56], "jordan": [1, 56], "jose": 102, "josh": 1, "joshu": 42, "joshua": [1, 56], "journal": 113, "json": [1, 10, 13, 17, 25, 28, 65, 103], "jsondecodeerror": 103, "jth": 30, "jul": [58, 59], "julian": [1, 56], "jun": 59, "june": 59, "just": [1, 6, 7, 8, 10, 16, 20, 26, 27, 30, 35, 37, 38, 39, 40, 43, 44, 45, 48, 51, 52, 53, 57, 61, 68, 69, 84, 87, 93, 102, 103, 110, 115, 116, 117, 118, 123], "justif": 16, "justin": 56, "j\u00e9r\u00f4me": [1, 56], "k": [2, 6, 12, 31, 38, 60, 77, 86, 102, 113, 115, 119, 123, 124], "k10": 31, "k_": 38, "k_1": 38, "k_2": 38, "k_3": 38, "kaczmarzyk": [1, 56], "kastman": [1, 56], "kate": 9, "katrin": [1, 56], "keep": [1, 2, 7, 15, 19, 20, 21, 25, 26, 37, 39, 43, 52, 55, 61, 65, 102], "keep_file_open": [1, 69, 70, 74, 77, 80, 84, 92, 99, 100, 118], "keep_file_open_default": [1, 69, 70, 74, 80, 84, 92, 99, 100, 118], "keep_id": 102, "keep_open": 106, "kei": [1, 6, 9, 10, 11, 16, 20, 23, 45, 61, 65, 69, 74, 75, 77, 78, 79, 80, 87, 89, 92, 94, 99, 100, 102, 109, 114, 116, 117, 118, 119, 122, 123, 124], "ken": 86, "kept": [11, 102, 105, 119], "kernel": 111, "kesshi": [1, 56], "kevin": 56, "keyerror": [72, 103], "keyword": [1, 8, 9, 10, 12, 35, 56, 59, 69, 70, 71, 74, 79, 80, 84, 87, 92, 94, 98, 99, 100, 102, 103, 106, 109, 118, 119, 125], "kind": [4, 6, 19, 20, 22, 58, 76], "kindlmann": 9, "klass": [15, 87, 93], "klug": [1, 56], "know": [1, 2, 3, 6, 7, 8, 9, 10, 11, 12, 19, 20, 27, 29, 30, 35, 36, 38, 41, 43, 54, 55, 60, 62, 76, 80, 87, 102, 103, 105, 109, 115, 116, 119], "known": [2, 10, 27, 35, 41, 62, 69, 77, 86, 92, 95, 103, 119, 123, 124], "konstantino": [1, 56], "koudoro": [1, 56], "kraepelin": 9, "krish": [1, 56, 92], "krzyzstof": 1, "kw_only_func": 1, "kw_only_meth": 1, "kwarg": [7, 68, 70, 71, 74, 75, 76, 77, 78, 79, 81, 82, 83, 87, 88, 89, 92, 94, 96, 98, 99, 101, 102, 103, 105, 106, 108, 109, 110, 116, 119, 121, 124, 125], "kwd": 102, "l": [1, 40, 54, 61, 65, 68, 69, 76, 108, 109], "l1232": 10, "l2": 102, "l_beta": 15, "l_bold": 15, "l_contrast": 15, "l_label": 15, "l_midthick": 15, "l_smooth": 15, "la": [1, 11, 60, 62], "label": [1, 2, 9, 15, 20, 22, 26, 31, 35, 38, 42, 52, 62, 66, 74, 77, 92, 94, 103, 108, 109, 123, 124], "label1": 123, "label2": 123, "label_arrai": 92, "label_t": [65, 77], "labelaxi": 65, "labeledwrapstruct": [65, 69, 92], "labelsclutsannotationfil": 92, "labelt": [65, 77, 94], "lack": [28, 40, 79], "lambda": [38, 123], "land": 23, "landman": 1, "languag": [6, 9, 10, 20, 114], "larg": [1, 4, 7, 20, 22, 27, 34, 76, 77, 86, 90, 94, 103, 104, 109, 123], "large1": 7, "large2": 7, "large_img1": 7, "large_img2": 7, "larger": [4, 20, 23, 27, 28, 68, 71, 109], "largest": 123, "larson": [1, 56], "last": [1, 2, 3, 7, 8, 9, 12, 26, 31, 35, 36, 38, 39, 40, 43, 54, 61, 62, 64, 68, 69, 71, 76, 77, 84, 86, 93, 102, 103, 106, 107, 109, 113, 119, 121, 123, 124], "lastli": 35, "later": [2, 3, 6, 9, 35, 38, 56, 61, 64, 66, 87, 90, 105], "latest": [6, 9, 22, 24, 50, 56, 57, 66, 119], "latin": 119, "latter": [9, 19, 23, 77, 92], "layer": [28, 125], "layout": [1, 70, 90, 92, 123, 126], "lazaru": 31, "lazi": [23, 119], "lazili": 119, "lazy_load": [11, 119], "lazy_tractogram": 119, "lazydict": 65, "lazytractogram": [1, 65], "ld": 9, "lea": [1, 56], "lead": [1, 6, 7, 36, 38, 40, 113], "leak": [1, 43], "learn": [3, 15, 19, 48, 49], "least": [3, 6, 10, 12, 14, 19, 20, 23, 26, 29, 38, 70, 74, 76, 79, 84, 88, 94, 95, 102, 106, 114, 123], "leav": [7, 14, 26, 52, 55, 80], "lecher": 56, "led": 81, "lee": [1, 56], "left": [1, 2, 12, 15, 18, 36, 38, 40, 43, 54, 60, 62, 68, 69, 77, 86, 94, 102, 109, 113, 115], "left_cortex": 77, "leftcortex": 77, "legaci": 26, "legarreta": [1, 56], "legitim": 22, "leicest": 9, "leinweb": [1, 56], "len": [2, 4, 9, 68, 69, 90, 103, 104, 111, 123, 124], "length": [1, 2, 4, 9, 12, 18, 39, 60, 62, 68, 70, 77, 90, 91, 92, 93, 101, 102, 108, 109, 111, 114, 115, 119, 123, 126], "lenni": 20, "lepp\u00e4kanga": [1, 56], "less": [4, 6, 7, 12, 20, 31, 35, 39, 60, 61, 77, 78, 85, 116, 119, 123], "lesser": 78, "let": [1, 2, 7, 8, 9, 12, 14, 20, 35, 36, 38, 43, 51, 54, 55, 62, 86, 90, 102, 109], "letter": 9, "level": [1, 4, 9, 10, 16, 28, 30, 41, 64, 72, 79, 96, 106, 124], "level_or_opt": 106, "lg": [42, 43], "lgtm": 1, "lh": 15, "li": 22, "li1_mean": 7, "li2_mean": 7, "lia": 111, "liabil": 58, "liabl": 58, "lib": [43, 84], "liber": 19, "libfslio": 1, "libniftiio": 1, "librari": [1, 6, 9, 12, 16, 18, 19, 23, 26, 28, 41, 57, 61, 76, 105, 120, 123], "licens": [1, 3, 25, 31, 34, 57, 59, 65, 84, 86], "life": [1, 20, 102, 105], "lifetim": [69, 70, 74, 80, 84, 92, 99, 100, 118], "light": 102, "lightweight": 27, "like": [1, 2, 3, 4, 6, 7, 8, 9, 11, 12, 13, 14, 15, 20, 22, 26, 27, 28, 29, 30, 35, 36, 37, 38, 40, 42, 43, 51, 53, 57, 60, 61, 62, 64, 68, 69, 70, 71, 72, 74, 75, 76, 77, 79, 80, 84, 86, 87, 88, 90, 92, 94, 99, 100, 102, 103, 104, 106, 107, 108, 109, 110, 111, 113, 114, 115, 116, 117, 118, 119, 120, 122, 123, 124], "limit": [1, 4, 6, 56, 58, 66, 94, 122], "limitednifti2head": 65, "linalg": [1, 2, 115], "linalgerror": 1, "line": [1, 2, 6, 7, 12, 14, 19, 20, 22, 26, 34, 42, 43, 44, 51, 52, 54, 69, 78, 100, 102, 106, 109, 114], "line_ast": 102, "lineag": 12, "linear": [2, 43, 58, 68, 119], "link": [1, 3, 6, 9, 16, 31, 34, 43, 49, 51, 76, 86, 122], "link_to": [65, 122], "linspac": 122, "linu": 49, "linux": [43, 49, 57], "lipsia": 13, "list": [1, 2, 3, 4, 6, 7, 9, 10, 11, 12, 14, 15, 16, 19, 20, 22, 23, 26, 30, 32, 34, 35, 40, 43, 52, 58, 61, 65, 74, 75, 77, 78, 79, 84, 89, 90, 91, 92, 93, 94, 102, 103, 109, 119], "list_fil": [65, 79], "liter": [80, 99, 103, 116, 123], "littl": [1, 2, 7, 8, 18, 19, 20, 30, 39, 40, 43, 49, 61, 78, 84, 86, 94, 103, 109], "live": [10, 23], "ll": [2, 3, 4, 7, 8, 9, 20, 33, 35, 37, 38, 39, 40, 42, 43, 51, 53, 123], "lo": [9, 35, 37], "load": [1, 2, 3, 8, 10, 11, 12, 15, 17, 21, 25, 29, 30, 36, 37, 41, 54, 55, 59, 60, 62, 64, 65, 66, 69, 73, 74, 77, 80, 84, 87, 91, 92, 94, 99, 100, 109, 116, 123], "load_mgh": 12, "load_nifti": 103, "load_structur": 15, "loader": 3, "loadimageapi": 3, "loadsav": [0, 1, 65], "lobe": 60, "local": [1, 2, 4, 20, 22, 26, 43, 50, 53, 79], "locat": [1, 2, 15, 20, 22, 28, 33, 38, 56, 61, 68, 69, 79, 84, 85, 92, 94, 116, 119], "lock": [70, 90], "log": [1, 22, 26, 43, 45, 49, 51, 72, 76, 96, 124], "log2": 76, "log_level": 72, "log_rais": [65, 72, 96], "logger": [72, 96, 124], "loggingoutputsuppressor": 65, "logic": [1, 3, 35, 85], "logo": 1, "long": [1, 2, 4, 6, 14, 19, 23, 25, 26, 30, 35, 57, 68, 76, 87, 104, 109, 119], "long_descript": 26, "long_str": 109, "longdoubl": [1, 76], "longdouble_lte_float64": 65, "longdouble_precision_improv": 65, "longer": [1, 6, 10, 51, 62, 87, 91], "longer_field": 123, "longest": 123, "look": [1, 2, 3, 4, 6, 7, 8, 9, 11, 20, 26, 30, 31, 35, 38, 39, 40, 41, 43, 47, 51, 57, 60, 69, 84, 86, 89, 102, 103, 104, 106, 120, 123, 124], "loop": [1, 39, 40], "lose": [10, 28, 29, 71, 90, 105], "loss": [9, 58, 116], "lossless": 9, "lossless_slic": 65, "lost": [10, 15, 37, 43], "lot": [1, 7, 8, 9, 102, 119, 123], "loui": 58, "love": 1, "lower": [1, 2, 60, 64, 82, 123], "lowest": [41, 109], "lp": [1, 2, 109], "lpi": [2, 31], "lr": [102, 109], "lsb_first": 74, "lt": 35, "luckili": 43, "lut": 35, "lwr_sctr_thre": 84, "lwr_true_thr": 84, "ly": [1, 2, 56], "m": [1, 2, 7, 9, 12, 22, 26, 31, 32, 34, 35, 37, 38, 39, 43, 52, 56, 68, 77, 84, 86, 92, 102, 103, 108, 111, 113, 117], "m1": 86, "m2": 86, "m3": 86, "m_": [2, 38], "m_i": 119, "mac": [1, 31, 76], "machin": [5, 9, 18, 35, 39, 49, 76, 94], "maco": [1, 26], "macosx10": 31, "macosx_version_min": 31, "macro": 35, "macroscop": 22, "made": [2, 6, 7, 19, 20, 23, 35, 42, 43, 51, 52, 56, 66, 69, 116, 117, 118], "madison": [1, 56], "mag": 109, "magic": [27, 49, 61, 62, 84, 103, 104], "magic_numb": [65, 84, 119], "magnet": [2, 35, 101], "magnitud": 76, "mah5": 9, "mai": [2, 3, 4, 5, 6, 7, 8, 9, 14, 15, 16, 19, 20, 22, 23, 26, 27, 28, 35, 36, 37, 38, 40, 42, 43, 44, 48, 54, 55, 56, 57, 58, 59, 60, 61, 62, 66, 70, 71, 72, 75, 76, 77, 80, 81, 82, 85, 87, 89, 92, 93, 94, 103, 105, 106, 108, 109, 110, 113, 116, 119, 120, 123, 124], "mail": [1, 3, 4, 6, 7, 14, 16, 19, 23, 26, 38, 43, 52, 65, 79], "mailmap": 26, "main": [6, 11, 20, 22, 26, 31, 41, 51, 52, 53, 65, 68, 84, 90], "mainli": [16, 33, 60, 77, 123], "maint": 26, "maintain": [1, 6, 19, 23, 37, 43, 46, 50, 61, 105, 116], "mainten": [19, 22, 26, 59], "major": [1, 6, 9, 16, 17, 23, 38, 70, 77, 79], "make": [1, 2, 3, 4, 6, 7, 9, 10, 11, 12, 13, 14, 15, 16, 19, 20, 22, 25, 27, 28, 29, 35, 36, 42, 46, 49, 50, 51, 53, 54, 58, 60, 61, 62, 71, 74, 79, 80, 82, 87, 90, 92, 100, 103, 109, 114, 116, 118, 119, 123, 124], "make_array_writ": 65, "make_datasourc": 65, "make_dt_cod": 65, "make_file_map": [65, 87, 116], "make_first_level_design_matrix": 15, "makeabl": [65, 69, 74, 77, 87, 92, 99, 109, 118], "makefil": [1, 26], "man": 43, "manag": [1, 11, 22, 23, 35, 49, 57, 61, 64, 96, 103, 106, 119, 120], "mandatori": 35, "mani": [1, 2, 3, 10, 12, 13, 19, 20, 22, 23, 28, 30, 35, 37, 49, 74, 77, 84, 87, 91, 94, 103, 108], "manifest": 1, "manifesto": 102, "manipul": [1, 61, 77], "manner": [10, 119], "manual": [7, 11, 22, 26, 43, 50, 51, 56, 57, 66, 79, 103, 104], "manufactur": [9, 34, 35], "manuipul": 1, "map": [1, 9, 10, 15, 18, 30, 32, 33, 35, 36, 60, 61, 62, 68, 69, 70, 74, 75, 77, 80, 84, 87, 88, 89, 92, 94, 99, 100, 102, 103, 104, 108, 109, 111, 115, 116, 117, 118, 119, 123, 124, 126], "map_mak": 123, "map_nam": 77, "mapnam": 77, "mapped_indic": [65, 77], "mapped_voxel": 115, "mapper": 123, "mapping": 65, "mar": 59, "marc": [1, 11, 56], "march": 59, "margin": 19, "mark": [1, 6, 9, 56, 62, 82], "markdown": 26, "markello": [1, 56], "markiewicz": [1, 15, 23, 56, 58], "mark\u00e9ta": [1, 56], "mask": [1, 18, 43, 77, 97, 103, 110], "mask_data": 97, "mask_volum": 65, "maskedconst": 43, "massachusett": 58, "master": [1, 22, 26, 27, 42, 46, 51, 52, 53, 57], "mat": [1, 9, 13, 30, 32, 113, 118], "mat0": 30, "mat0_int": 30, "mat2eul": 65, "mat2quat": 65, "mat_int": 30, "match": [1, 2, 3, 9, 15, 22, 26, 40, 62, 69, 72, 77, 87, 89, 90, 94, 102, 103, 104, 108, 109, 115, 116, 119, 123], "match_cas": 89, "match_path": [65, 78], "materi": 58, "mathbf": [36, 38], "mathemat": [2, 15, 119], "mathia": [1, 56], "mathieu": [1, 56], "mathworld": [86, 113], "matlab": [1, 9, 12, 18, 30, 34, 40, 76, 92, 118], "matlab4": 30, "matplotlib": [1, 2, 36, 60, 116], "matric": [1, 2, 40, 113], "matrix": [1, 15, 30, 36, 38, 41, 61, 68, 69, 74, 77, 84, 86, 92, 94, 99, 100, 102, 103, 104, 108, 109, 113, 116, 117, 118, 119, 126], "matrixindicesmap": 77, "matt": [1, 56], "matthew": [1, 4, 7, 8, 9, 10, 12, 13, 14, 23, 42, 56, 58], "max": [71, 76, 100, 108, 123], "max_ab": 78, "max_name_len": 119, "max_rel": 78, "maxexp": 76, "maxim": 78, "maximum": [1, 2, 36, 76, 87, 90, 102, 113, 117, 119, 123], "may_contain_head": [65, 69, 77, 99, 100, 103, 104, 117], "mayb": [3, 7, 9, 10, 20, 27, 31, 43, 60, 68, 69, 71, 84, 90, 106, 109, 119, 123], "maybe_imag": 87, "mayo": 69, "mb": [1, 7, 8, 9, 14, 119], "mb312": 26, "mc": 1, "mccarthi": [1, 56], "mcconnel": 58, "mcgill": 58, "md": [75, 77], "md5": 78, "md_": 36, "mdc": 92, "me": [7, 31], "mean": [1, 2, 4, 6, 7, 8, 9, 12, 20, 23, 26, 28, 35, 36, 38, 40, 43, 53, 54, 55, 60, 61, 62, 64, 69, 76, 77, 78, 80, 86, 90, 92, 96, 100, 103, 108, 111, 112, 115, 116, 123, 124], "meanfunct": 27, "meaning": [12, 22, 35, 86], "meaningless": 1, "meant": [6, 105, 111], "meantim": 43, "meantunct": 27, "meanwhil": 120, "measur": [29, 33, 35, 90], "mechan": [1, 6, 35, 76, 124], "med": 102, "media": 35, "medial": 15, "medic": [9, 34, 35, 38, 58, 102], "medit": 64, "medium": 103, "meet": 19, "meg": 103, "megabyt": 4, "meld": 43, "member": [19, 23, 30], "membership": 23, "memmap": [1, 69, 70, 74, 80, 84, 92, 94, 99, 100, 109, 118, 123], "memmappedniftiimag": 1, "memor": 20, "memori": [1, 3, 8, 9, 11, 12, 14, 15, 26, 27, 29, 30, 35, 41, 59, 60, 61, 69, 70, 71, 74, 80, 84, 90, 92, 94, 99, 100, 109, 118, 119, 123], "mention": 1, "mentorship": 19, "menu": 43, "merchant": 58, "mercuri": 20, "mere": 19, "merg": [1, 6, 9, 17, 23, 25, 26, 28, 46, 49, 51, 53, 58], "mesh": [15, 92, 94, 103, 110], "mess": 7, "messag": [1, 14, 19, 23, 32, 40, 43, 52, 65, 72, 77, 79, 81, 96, 104, 107, 121, 126], "messi": 43, "met": 58, "meta": [1, 6, 15, 18, 35, 65, 77, 84, 87, 94, 102, 116], "meta_valid": 10, "metadata": [1, 2, 3, 27, 28, 56, 61, 64, 65, 66, 69, 74, 75, 77, 80, 84, 87, 92, 94, 99, 100, 103, 104, 109, 116, 117, 118, 119], "meter": 77, "meter_expon": 77, "meterexpon": 77, "method": [1, 3, 7, 10, 15, 21, 27, 28, 40, 41, 55, 61, 62, 64, 65, 69, 70, 71, 72, 75, 76, 77, 80, 82, 84, 87, 92, 93, 94, 96, 99, 100, 102, 103, 104, 105, 106, 109, 110, 113, 114, 116, 118, 119, 123, 124], "mex": 30, "mgh": [1, 12, 15, 56, 66, 70, 92], "mgherror": 65, "mghformat": 65, "mghheader": 65, "mghimag": [1, 15, 65], "mgz": [15, 92, 106], "mh": 1, "mi": 9, "michael": [1, 23, 31, 56, 58], "michiel": [1, 56], "middl": [2, 38, 60, 90], "midlin": 2, "might": [2, 3, 4, 7, 8, 9, 10, 12, 13, 20, 22, 26, 27, 35, 38, 42, 46, 55, 64, 69, 70, 72, 74, 76, 80, 87, 88, 90, 92, 102, 103, 108, 111, 115, 116, 119, 120, 123], "migrat": 1, "miguel": [1, 56], "mih": 23, "millimet": [2, 36, 61, 77, 111], "million": 12, "millisecond": [9, 61], "millman": [1, 6, 23, 56, 58], "mim": 77, "mimic": 66, "min": [71, 76, 79, 100, 123], "min_vers": 107, "minc": [1, 2, 3, 8, 9, 12, 61, 81, 99, 100, 116], "minc1": [0, 1, 12, 56, 65, 66, 70], "minc1fil": [1, 65, 100], "minc1head": 65, "minc1imag": [1, 65, 100], "minc2": [0, 1, 4, 12, 56, 57, 65, 66], "minc2_4d": 12, "minc2fil": 65, "minc2head": 65, "minc2imag": 65, "minc_fil": 99, "mincerror": 65, "mincfil": [1, 99, 100], "minchead": [65, 100], "mincimag": 1, "mincimagearrayproxi": 65, "mincstat": 100, "mind": 3, "minexp": 76, "mingw": [1, 76], "mini": [10, 114], "minim": [1, 9, 29, 72, 116], "minimum": [1, 2, 14, 26, 29, 41, 70, 76, 79, 107, 117, 123], "minor": [1, 9, 23, 79], "minut": [26, 42], "mirror": 46, "miscellan": [1, 21, 30], "misfortun": 121, "mismatch": 1, "miss": [1, 4, 9, 22, 26, 27, 40, 90, 102, 109, 111, 114, 115, 119, 121], "mission": 23, "misspel": 22, "mistak": [19, 43], "misus": 58, "mit": [1, 56, 58, 66], "mitk": 11, "mix": 13, "mixin": 105, "mk": 119, "mkdtemp": 120, "mkstemp": [27, 64], "ml": 37, "mlist": 84, "mlist_id": 84, "mlist_row": 84, "mm": [1, 2, 16, 33, 36, 38, 40, 41, 68, 77, 102, 111, 119, 123], "mm3": 97, "mmap": [1, 69, 70, 74, 77, 80, 84, 90, 92, 94, 99, 100, 109, 118, 123], "mmm": 6, "mn": [76, 123], "mnc": [12, 99, 100], "mnc2": 12, "mne": 15, "mni": [2, 58, 62, 74], "mni152": 30, "mni_icbm152_t1_tal_nlin_asym_09a": 59, "mock": 1, "mod_data": 64, "mod_fnam": 123, "modal": [33, 35, 40], "mode": [1, 52, 56, 64, 65, 66, 69, 70, 74, 80, 84, 88, 90, 92, 94, 99, 100, 106, 109, 111, 118, 119, 122, 123], "model": [20, 23, 35, 52, 77, 102, 105], "model_typ": 77, "modeltyp": 77, "moder": 2, "modern": 1, "modif": [1, 55, 58, 72, 103, 106], "modifi": [1, 6, 8, 19, 21, 25, 35, 39, 41, 43, 55, 58, 69, 70, 72, 80, 90, 93, 119, 123], "modul": [1, 3, 22, 26, 33, 35, 57, 58, 65, 66, 72, 76, 79, 81, 86, 107, 112, 113, 121], "module_nam": 81, "module_setup": 107, "moduleproxi": 65, "moduletyp": 107, "mollier": 1, "molonei": [1, 10, 28, 56], "moment": [2, 7, 8, 10, 12, 13, 14, 20, 26, 94], "mon": 59, "mondai": 59, "monitor": 19, "monoton": 77, "month": 28, "montreal": [2, 58], "more": [1, 2, 3, 4, 6, 8, 9, 10, 11, 13, 15, 18, 19, 20, 22, 23, 26, 28, 29, 30, 35, 38, 40, 41, 45, 52, 55, 56, 57, 60, 61, 62, 65, 66, 68, 69, 70, 77, 80, 84, 87, 90, 103, 104, 108, 109, 110, 111, 119, 123], "morenc": [1, 56], "moreno": [1, 56], "moreov": [11, 119], "morphologi": 1, "morphometri": [15, 56, 66, 92], "morpometri": 92, "mosaic": [10, 31, 32, 34, 40, 102], "mosaic_s": 102, "mosaic_to_nii": 65, "mosaicwrapp": 65, "most": [1, 2, 8, 9, 10, 11, 12, 13, 14, 15, 35, 41, 43, 47, 55, 56, 57, 64, 69, 71, 76, 90, 94, 96, 103, 106, 107, 111, 121, 123, 124], "mostli": 1, "motion": [9, 12, 13], "motiv": [8, 21], "mous": 122, "move": [1, 2, 11, 12, 19, 23, 38, 39, 50, 60, 68, 86, 92, 112, 122], "movement": [60, 122], "mpl": 122, "mr": [12, 18, 31, 33, 35, 39, 40, 102], "mri": [2, 10, 12, 18, 60, 101, 103, 109], "mri_convert": 111, "mricron": [31, 34], "mrierror": 65, "mriutil": [0, 65], "mrphoenixprotocol": 10, "mrtrix": [1, 11, 119], "msec": 1, "msg": [78, 79, 81, 121], "msg05084": 79, "msg_id": [77, 104], "msk": 18, "msvc": 76, "msysgit": 47, "mtime": 106, "much": [1, 3, 7, 20, 35, 43, 62], "mult": 65, "multi": [9, 10, 12, 18, 32, 38, 40, 87, 102], "multidimension": 10, "multiformat": 119, "multifram": [1, 10, 102], "multiframewrapp": 65, "multilin": [1, 114], "multipl": [1, 2, 6, 9, 10, 12, 15, 17, 19, 22, 25, 77, 84, 86, 94, 109, 113, 114, 119, 123, 126], "multipli": [2, 9, 38, 62, 77, 113], "must": [1, 6, 7, 12, 19, 23, 35, 40, 58, 68, 69, 70, 71, 74, 77, 78, 87, 89, 90, 92, 94, 102, 103, 106, 118, 119, 123], "mutabl": [1, 103], "mutablemap": [1, 75, 77, 119], "mutablesequ": 77, "mutli": 109, "mx": [76, 123], "mx3": 15, "my": [7, 20, 26, 42, 43, 51], "my_4d": 12, "my_cwd": 120, "my_field1": 9, "my_field2": 9, "my_fil": [36, 66], "my_file_copi": 66, "my_funni": 100, "my_huge_imag": 8, "my_imag": [14, 61, 74], "my_image_again": 61, "my_new_fil": 43, "my_pair_imag": 61, "my_pkg_path": 20, "my_tck": 11, "my_trk": 11, "myfil": 124, "mypi": 1, "mypkg": 20, "mypkg_path": 20, "myslic": 9, "mysoft_on": 9, "mysoft_two": 9, "mytim": 9, "n": [1, 6, 9, 15, 26, 30, 31, 35, 38, 40, 56, 61, 62, 68, 75, 77, 84, 86, 91, 92, 102, 103, 104, 108, 110, 111, 113, 116, 119, 120, 123], "n1_header": 62, "n1_img": 62, "n2_header": 62, "n2_img": 62, "n_": 38, "n_1": [38, 40], "n_2": [38, 40], "n_3": [38, 40], "n_block": [36, 102], "n_byte": [90, 102], "n_col_block": [36, 102], "n_coord": [15, 65, 110], "n_dim": 111, "n_direct": 109, "n_i": 2, "n_j": 2, "n_k": 2, "n_label": 92, "n_mosaic": 102, "n_row": [84, 119], "n_row_block": [36, 102], "n_sensor": 15, "n_slab_col": [36, 102], "n_slab_row": [36, 102], "n_slice": [36, 102], "n_slice_col": [36, 102], "n_slice_row": [36, 102], "n_string": 91, "n_surface_vertic": 77, "n_t": 119, "n_tag": [39, 102], "n_time": 15, "n_triangl": 15, "n_vert": 15, "n_vertic": 92, "n_volum": [65, 122], "na": [58, 75, 77], "name": [1, 6, 7, 10, 11, 12, 15, 16, 18, 23, 26, 27, 30, 31, 35, 37, 38, 39, 40, 43, 45, 51, 52, 53, 58, 61, 65, 69, 75, 77, 78, 79, 80, 81, 86, 87, 88, 89, 92, 94, 95, 102, 103, 105, 106, 107, 109, 110, 114, 119, 120, 123, 124, 125, 126], "named_brain_model": 77, "named_map": [65, 77], "namedmap": 77, "namespac": [1, 9, 20, 102, 107], "nan": [1, 61, 62, 69, 71, 76, 78, 102, 103, 108, 117, 118, 119, 123], "nan2zero": [1, 71, 76, 123], "narrow": 1, "nasti": 7, "nativ": [18, 57, 69, 78, 103, 117, 118, 124], "native_cod": [69, 117, 118, 124], "natur": [6, 8, 28, 35], "nb": [10, 15, 97], "nb_fiber": 11, "nb_point": [65, 119], "nb_properties_per_streamlin": [65, 119], "nb_scalars_per_point": [65, 119], "nb_streamlin": [65, 119], "nbirn": 9, "nbs_wstr": 124, "ndarrai": [7, 15, 41, 55, 69, 70, 74, 76, 78, 80, 84, 90, 92, 94, 99, 100, 102, 103, 108, 109, 110, 116, 119, 123], "ndgrid": 15, "ndim": [1, 65, 69, 70, 80, 84, 90, 99, 103, 104, 108, 109, 110], "ndimag": [1, 111, 115], "nearest": [1, 39, 54, 76, 102, 103, 111], "nearest_pos_semi_def": 65, "nearli": [2, 7, 8, 19, 35, 76, 93, 113], "nearly_equival": 65, "neat": [38, 61, 62], "necessari": [1, 2, 9, 11, 14, 16, 19, 22, 23, 27, 60, 64, 79, 80], "necessarili": [6, 29, 64, 123], "need": [1, 2, 3, 4, 6, 7, 8, 9, 10, 12, 15, 16, 19, 20, 22, 26, 27, 28, 29, 30, 31, 35, 38, 40, 41, 42, 43, 44, 45, 51, 55, 60, 61, 64, 69, 70, 71, 72, 76, 77, 79, 80, 86, 87, 90, 92, 99, 100, 102, 103, 107, 108, 109, 111, 115, 116, 117, 118, 119, 123, 124], "needs_nibabel_data": 4, "neg": [1, 2, 38, 66, 69, 76, 84, 86, 90, 94, 102, 103, 108, 109, 113], "neglig": 58, "negoti": 35, "neither": [22, 58, 123], "nema": [2, 9, 102], "nemec": 1, "nep": 43, "ness": [30, 102], "net": [9, 31, 41, 84], "netcdf": [1, 59, 99], "network": [35, 43], "networkx": [15, 19], "neuro": 31, "neurodebian": [22, 57], "neuroimag": [1, 2, 11, 28, 41, 56, 66, 78, 110], "neurolex": 9, "neurolog": [2, 58, 69, 84, 126], "neurologi": 60, "neurologist": [60, 69], "neurosci": 30, "neurostar": 19, "never": [2, 3, 6, 103], "new": [4, 6, 9, 10, 11, 12, 13, 15, 16, 19, 20, 22, 23, 25, 26, 40, 41, 44, 46, 51, 52, 53, 55, 57, 59, 61, 62, 65, 68, 69, 70, 74, 78, 80, 84, 87, 90, 92, 93, 99, 100, 103, 106, 113, 116, 118, 119, 124], "new_affin": 7, "new_data": 62, "new_file_nam": 43, "new_fnam": 30, "new_head": [7, 62], "new_imag": 66, "new_img": 62, "new_iter": 119, "new_seq": 119, "new_shap": 68, "new_slic": 115, "new_vol": 30, "newaxi": [90, 122], "newer": 1, "newli": 62, "newton": [1, 56], "nexp": 76, "next": [2, 6, 7, 26, 31, 35, 38, 39, 40, 55, 60, 61, 62, 69, 76, 80, 84, 105, 119], "next_item": 119, "next_item_po": 39, "nextdir": 84, "nf": [22, 42, 43, 93], "nface": 92, "nframe": [12, 84], "nfree": 84, "nguyen": [1, 56], "ni1": [93, 103], "ni2": 104, "nib": [1, 2, 7, 11, 12, 14, 26, 37, 41, 54, 55, 60, 61, 62, 66, 73, 74, 80, 84, 87, 94, 100, 116, 124], "nibabbl": 62, "nibabel": [0, 5, 6, 7, 8, 9, 11, 13, 14, 15, 16, 17, 18, 19, 20, 23, 25, 27, 28, 37, 41, 43, 44, 46, 48, 51, 52, 53, 54, 55, 57, 60, 62, 64, 65, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126], "nibabel_data": 4, "nibabel_dir": 84, "nibabel_imag": 7, "nibotmi": 26, "nice": [9, 18, 34, 42, 49, 52, 60, 78], "nich": 19, "nichol": [1, 56], "nicom": [0, 1, 65, 112], "niethamm": 102, "nifti": [1, 2, 3, 8, 12, 13, 14, 17, 25, 27, 28, 30, 31, 32, 34, 36, 40, 41, 54, 59, 60, 61, 69, 77, 78, 94, 102, 103, 109, 116, 117, 118, 123], "nifti1": [0, 1, 8, 29, 37, 56, 61, 62, 65, 66, 69, 70, 81, 94, 104, 109, 111], "nifti1_diagnos": 26, "nifti1_imag": 81, "nifti1dicomextens": [37, 65], "nifti1extens": [10, 37, 65, 77], "nifti1head": [61, 62, 65, 104, 109], "nifti1imag": [1, 7, 41, 54, 55, 61, 62, 65, 66, 81, 93, 97, 102, 104, 109, 111, 119], "nifti1pair": [61, 65, 104], "nifti1pairhead": 65, "nifti2": [0, 1, 3, 56, 62, 65, 66, 69, 77], "nifti2head": [62, 65, 77], "nifti2imag": [62, 65], "nifti2pair": 65, "nifti2pairhead": 65, "nifti_dx": 65, "nifti_ecode_afni": 9, "nifti_ecode_com": 9, "nifti_ecode_dicom": 9, "nifti_ecode_ignor": 9, "nifti_ecode_jimdiminfo": 9, "nifti_ecode_workflow_fwd": 9, "nifti_ecode_xced": 9, "nifti_head": [65, 77], "nifti_intent_label": 94, "nifti_intent_non": 94, "nifti_intent_pointset": [94, 103], "nifti_intent_time_seri": 94, "nifti_intent_triangl": 94, "nifti_type_float32": [94, 123], "nifti_type_int32": 94, "nifti_type_rgba32": 94, "nifti_type_uint8": 94, "nifti_xform_aligned_anat": 94, "nifti_xform_mni_152": 94, "nifti_xform_scanner_anat": 94, "nifti_xform_talairach": 94, "nifti_xform_template_oth": 1, "nifti_xform_unknown": 94, "nifticlib": 1, "niftiextens": [1, 65], "niftifil": 1, "niftiformat": 1, "niftiimag": 1, "niftiwrapp": 10, "niftyreg": 28, "nightli": 1, "nih": [9, 74, 103], "nii": [1, 2, 7, 8, 10, 11, 12, 14, 15, 27, 30, 37, 41, 54, 55, 58, 60, 61, 62, 64, 66, 77, 80, 97, 103], "niistr": 123, "nikolaa": [1, 56], "nil": 1, "nilearn": [13, 15, 28], "nim": 37, "nimh": [9, 74, 103], "niminc1": 12, "niminc2": 12, "niml": 74, "nimmo": [1, 56], "nipi": [1, 2, 4, 6, 7, 8, 9, 13, 18, 20, 23, 26, 27, 28, 44, 51, 52, 53, 56, 57, 66, 68, 79, 85, 105, 109, 119], "nipy_data_path": 79, "nipy_dir": 85, "nipy_extra_test": 5, "nipy_header_vers": 9, "nipy_user_dir": 85, "nipyer": 60, "nisext": 1, "nitem": 39, "nitest": [4, 58], "nitpicki": 19, "nitransform": 28, "nitrc": [31, 77, 94, 104, 119], "nmant": 76, "nmr": 92, "node": [27, 103], "noisi": 1, "nolan": [1, 56], "nom": 34, "nomin": [23, 33], "non": [1, 2, 7, 9, 23, 40, 42, 58, 71, 73, 77, 94, 97, 106, 113, 116, 123], "none": [1, 3, 8, 14, 15, 19, 20, 61, 62, 64, 66, 68, 69, 70, 71, 72, 74, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 92, 93, 94, 98, 99, 100, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125], "none_or_clos": 65, "noninfring": 58, "noqa": 103, "nor": [9, 11, 22, 58], "norm": [65, 86, 102], "norma": 102, "normal": [1, 6, 15, 28, 31, 35, 54, 61, 72, 86, 105, 113], "nose": [1, 78], "nosetest": 66, "not_a_packag": 107, "notat": [10, 38], "note": [1, 3, 5, 8, 9, 14, 16, 18, 19, 26, 30, 31, 35, 37, 40, 42, 43, 51, 52, 53, 56, 61, 62, 65, 66, 76, 77, 78, 79, 80, 81, 84, 86, 90, 92, 94, 102, 103, 104, 108, 109, 113, 116, 119, 120, 123, 124], "noth": [2, 7, 27, 72], "notic": [43, 58], "notif": [19, 35], "notifi": 6, "notimplementederror": [11, 14], "nov": 59, "novalu": 65, "novel": 64, "novemb": 59, "novic": 19, "now": [1, 2, 3, 4, 7, 8, 10, 12, 14, 15, 20, 24, 26, 30, 35, 38, 39, 40, 41, 42, 43, 44, 45, 51, 52, 53, 62, 68, 69, 71, 90, 105, 117, 118], "np": [1, 2, 7, 9, 11, 14, 15, 27, 29, 41, 54, 55, 60, 61, 62, 64, 68, 69, 70, 71, 72, 74, 76, 77, 78, 80, 81, 86, 87, 90, 92, 93, 94, 97, 99, 100, 102, 103, 104, 108, 109, 111, 113, 115, 116, 117, 118, 122, 123, 124], "np_type": [76, 116], "np_version": 26, "npd": 102, "npl": 2, "npt": [80, 99, 100, 116, 123], "npz": 119, "nr": 18, "nrofvertic": 18, "nrrd": 34, "nse": 26, "nt": 119, "nt_str": 65, "nuke": 26, "nul": 9, "null": [35, 39, 102], "num": 39, "num_bed_po": 84, "num_dim": [1, 65, 94], "num_fram": 84, "num_gat": 84, "num_plan": 84, "numavail": 84, "number": [1, 2, 4, 6, 9, 10, 12, 15, 18, 19, 20, 26, 31, 33, 34, 35, 36, 38, 39, 40, 62, 69, 76, 77, 79, 83, 84, 87, 90, 91, 92, 95, 97, 102, 103, 104, 109, 110, 111, 119, 122, 123], "number_of_mapped_indic": [65, 77], "number_of_series_point": 77, "numberoffram": 31, "numberofimagesinmosa": [36, 40, 102], "numberofseriespoint": 77, "numberoftemporalposit": 31, "numda": [65, 94], "numer": [1, 86, 94, 102, 103, 106, 113, 123], "numpi": [1, 2, 3, 7, 9, 11, 12, 14, 15, 19, 20, 22, 29, 41, 54, 55, 56, 57, 58, 60, 61, 62, 66, 68, 69, 70, 71, 74, 76, 77, 78, 80, 84, 87, 90, 92, 94, 97, 99, 100, 103, 108, 109, 110, 113, 116, 118, 119, 122, 123, 124], "numpy_min_vers": 26, "numpydoc": [1, 22], "numus": 84, "nuse": 84, "nvertex": 77, "nvertic": 77, "nvtx": 92, "nx": 77, "nx3": 15, "nxm": 68, "nxn": 68, "ny": [43, 77], "nz": 77, "o": [1, 11, 41, 47, 54, 55, 56, 61, 62, 64, 74, 79, 80, 84, 87, 89, 92, 98, 107, 120], "ob": [35, 39, 40, 123], "obei": 9, "obj": [43, 69, 70, 71, 72, 74, 78, 80, 92, 99, 100, 103, 104, 109, 116, 117, 118, 119, 121, 123], "obj_from_atom": 65, "obj_id": 102, "obj_kei": 102, "obj_root": 102, "obj_typ": 102, "object": [1, 2, 6, 7, 9, 10, 11, 12, 14, 15, 20, 23, 27, 28, 30, 31, 33, 38, 41, 42, 43, 55, 56, 59, 62, 64, 66, 69, 70, 71, 72, 74, 75, 76, 77, 78, 79, 80, 81, 82, 84, 87, 88, 90, 91, 92, 94, 95, 96, 98, 99, 100, 102, 103, 104, 105, 106, 107, 109, 110, 111, 115, 116, 117, 118, 119, 121, 122, 123, 124, 125, 126], "obliqu": [1, 65], "observ": 2, "obsolet": [6, 30], "obtain": [38, 58, 74, 77, 80, 119], "obviou": [2, 7, 9, 20, 38, 54, 86], "obvious": [8, 20, 31, 36, 38, 40, 76], "occasion": [22, 23, 124], "occupi": [92, 118], "occur": [1, 9, 15, 23, 35, 86, 89, 92, 109, 113], "occurr": 102, "oct": 59, "octob": 59, "odd": [1, 35, 40, 102], "odder": 30, "odditi": 76, "odict": 58, "oesteban": 23, "off": [7, 8, 22, 51, 61], "offcentr": 109, "offer": [11, 13, 41, 105], "offici": [6, 22], "offlin": 56, "offset": [1, 9, 30, 36, 40, 64, 65, 69, 70, 71, 73, 74, 78, 84, 90, 92, 102, 103, 109, 123], "often": [1, 3, 6, 12, 13, 14, 19, 26, 27, 28, 30, 35, 43, 55, 98, 124], "oh": 38, "ohind": 1, "oidc": 1, "ok": [4, 9, 26, 27, 40, 124], "ok_float": 65, "old": [1, 9, 70, 92], "older": [6, 9, 14, 61, 76], "oliv": 56, "omax": [69, 117, 118], "omin": [69, 117, 118], "omit": [9, 16, 77, 94, 106], "on_powerpc": 65, "onc": [6, 11, 19, 23, 26, 36, 45, 57, 80, 87, 103, 105, 115], "one": [1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19, 20, 22, 23, 26, 28, 30, 35, 38, 40, 43, 45, 52, 58, 60, 61, 62, 68, 69, 70, 72, 74, 77, 78, 79, 80, 84, 87, 89, 90, 92, 94, 99, 100, 102, 103, 105, 106, 108, 109, 110, 118, 119, 122, 123, 124], "one_lin": 65, "one_vox_axis_0": 2, "onelin": [43, 51], "onerussian": 58, "ones": [3, 6, 10, 30, 40, 41, 77], "onetim": [0, 1, 65], "onetimeproperti": 105, "ongo": 23, "onli": [1, 2, 4, 7, 9, 10, 11, 12, 15, 16, 18, 20, 22, 25, 30, 31, 35, 36, 38, 39, 40, 41, 43, 45, 51, 53, 55, 60, 61, 62, 64, 68, 69, 70, 71, 72, 74, 76, 77, 79, 80, 84, 87, 90, 92, 93, 94, 95, 99, 100, 102, 103, 105, 106, 109, 116, 118, 119, 120, 123, 124], "onlin": [34, 50, 56], "onset": 1, "onto": [2, 20, 30, 43, 80], "oosterhof": [1, 56], "op": 102, "open": [0, 1, 13, 23, 25, 26, 31, 43, 57, 65, 69, 70, 74, 78, 80, 84, 87, 88, 90, 92, 99, 100, 118, 119, 120, 123, 124, 125], "opendatacommon": 58, "openerdef": 106, "opensourc": 1, "oper": [1, 12, 15, 30, 35, 38, 57, 77, 102, 110, 113, 119], "operator_nam": 84, "opinion": [6, 16, 23], "opportun": [4, 6, 15, 19, 26], "oppos": [110, 123], "opposit": [69, 108, 117, 118, 123], "opt": [22, 78], "optim": [1, 8, 15, 22, 27, 30, 90], "optimize_read_slic": 65, "optimize_slic": 65, "optimum": 62, "option": [1, 6, 9, 12, 16, 20, 22, 23, 31, 35, 37, 43, 51, 52, 57, 66, 68, 69, 70, 71, 72, 74, 76, 77, 78, 79, 80, 81, 82, 84, 86, 87, 88, 89, 90, 91, 92, 93, 94, 99, 100, 102, 103, 104, 106, 107, 108, 109, 111, 113, 114, 116, 117, 118, 119, 120, 122, 123, 124, 125], "optional_packag": 65, "optpkg": [0, 1, 65], "order": [1, 2, 6, 7, 8, 9, 10, 12, 15, 18, 27, 29, 30, 31, 32, 35, 37, 38, 56, 69, 70, 71, 72, 73, 77, 78, 79, 80, 84, 86, 87, 90, 92, 93, 94, 102, 103, 109, 111, 116, 119, 123, 124], "order_stor": 84, "ordereddict": [10, 78, 92, 102], "orderedset": [59, 123], "orfano": [1, 56], "org": [1, 4, 6, 7, 8, 9, 20, 26, 31, 34, 56, 58, 76, 77, 79, 81, 86, 94, 102, 104, 105, 113, 119], "organ": [22, 43], "orient": [0, 1, 2, 9, 31, 32, 33, 35, 59, 60, 61, 62, 65, 69, 84, 86, 93, 102, 103, 111, 116, 117, 118, 122], "orientationerror": 65, "orig": [26, 74], "orig_id": [1, 92], "origin": [1, 2, 6, 7, 15, 19, 20, 26, 27, 33, 36, 38, 40, 42, 43, 52, 53, 58, 60, 62, 65, 68, 69, 70, 72, 74, 80, 87, 90, 94, 102, 103, 106, 109, 115, 116, 117, 118, 119, 122, 123, 124], "original_filenam": 84, "ornt": [103, 108, 116], "ornt2axcod": 65, "ornt_transform": 65, "orthogon": [2, 36, 38, 103, 122], "orthoslicer3d": [1, 65, 116], "orthoview": [1, 65, 116], "oscar": [1, 23, 56], "oserror": [103, 104, 123], "oset": 58, "osx": [26, 47, 57], "other": [1, 4, 6, 7, 8, 9, 10, 11, 12, 13, 16, 19, 20, 22, 23, 26, 30, 31, 34, 35, 37, 40, 41, 46, 49, 51, 54, 57, 58, 61, 62, 69, 70, 71, 76, 77, 78, 84, 87, 88, 89, 90, 95, 102, 103, 105, 106, 109, 116, 117, 118, 119, 121, 122, 123, 124, 126], "other_fil": 66, "otherwis": [1, 8, 9, 11, 13, 27, 40, 58, 69, 71, 72, 74, 77, 79, 82, 85, 87, 88, 89, 90, 92, 93, 102, 103, 106, 107, 109, 111, 113, 118, 119, 123, 124], "our": [2, 3, 4, 9, 12, 14, 19, 20, 22, 23, 26, 27, 28, 29, 30, 34, 38, 39, 48, 51, 53, 54, 57, 61, 62, 69, 77, 84, 86, 87, 90, 103, 105, 115, 116, 122], "ourpkg": 20, "ourselv": [8, 38], "out": [1, 3, 4, 6, 7, 8, 9, 10, 12, 13, 19, 27, 32, 33, 40, 41, 52, 58, 60, 62, 76, 77, 78, 82, 84, 87, 90, 92, 99, 100, 101, 102, 115, 116, 120, 122, 123], "out_ax_ind": 90, "out_class": 111, "out_dtyp": [65, 71, 123], "out_filenam": 11, "out_img": 111, "out_len": 90, "out_shap": [90, 111], "out_siz": 30, "out_typ": 71, "outcom": 19, "outdat": 1, "outer": [42, 122], "outlin": 23, "output": [1, 10, 26, 30, 36, 40, 43, 60, 61, 62, 64, 66, 68, 69, 70, 71, 77, 78, 86, 89, 90, 92, 93, 94, 100, 102, 103, 108, 109, 111, 115, 116, 119, 120, 123, 124, 125, 126], "output_affin": 115, "output_offset": 9, "output_shap": [111, 115], "output_vector": 9, "outputt": 1, "outsid": [9, 10, 26, 70, 76, 109, 111], "outstand": 26, "over": [1, 2, 6, 9, 11, 12, 19, 20, 26, 33, 35, 60, 77, 90, 92, 93, 102, 111, 122, 124], "overal": 62, "overflow": [1, 14, 76, 123], "overhead": 105, "overlai": 35, "overlin": [15, 114], "overrid": [1, 3, 9, 15, 27, 69, 102, 110, 116], "overridden": [15, 69, 87], "overview": [21, 35, 41, 46, 50], "ovgu": 58, "ow": 35, "own": [4, 9, 11, 13, 19, 20, 26, 35, 36, 41, 43, 46, 48, 49, 50, 51, 52, 53, 69, 77, 84, 87, 103, 116, 122], "p": [2, 12, 29, 30, 35, 43, 51, 56, 68, 69, 81, 108, 109], "p_": 38, "p_i": 119, "p_x": 38, "p_y": 38, "p_z": 38, "pa": [31, 109], "pacifi": 1, "pack": 102, "packag": [1, 22, 27, 28, 31, 58, 59, 79, 82, 102, 107], "packaging_tool": 79, "pad": [1, 103, 119], "pafilov": 1, "page": [1, 18, 20, 22, 23, 24, 26, 34, 35, 40, 43, 45, 47, 48, 50, 51, 55, 56, 57, 62, 76], "paint": 15, "pair": [1, 9, 10, 20, 38, 40, 61, 69, 74, 75, 77, 78, 80, 86, 87, 89, 90, 92, 94, 99, 100, 102, 103, 104, 109, 113, 118, 119, 123, 126], "pair_img": 61, "pair_mag": [65, 103, 104], "pair_vox_offset": [65, 103, 104], "palasubramaniam": [1, 56], "palm": 38, "panda": 12, "panfilov": [1, 56], "papadopoulo": [1, 56], "par": [1, 3, 12, 56, 59, 65, 66, 78], "par_head": 109, "parabl": 49, "parallel": [2, 27], "param": [27, 102, 103], "paramet": [1, 2, 7, 14, 34, 62, 64, 66, 68, 69, 70, 71, 72, 74, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 97, 98, 99, 100, 101, 102, 103, 104, 106, 107, 108, 109, 110, 111, 113, 114, 115, 116, 117, 118, 119, 120, 122, 123, 124, 125], "parametr": 1, "parcel": [1, 15, 65, 77], "parcel_axi": 77, "parcelsaxi": [1, 65], "parent": [8, 77, 94], "parent_hdr": 103, "parlanc": 74, "parrec": [0, 1, 3, 65, 70], "parrec2nii": [1, 65, 109], "parrec_example_imag": 3, "parrecarrayproxi": 65, "parrecerror": 65, "parrechead": 65, "parrecimag": [3, 65], "pars": [1, 37, 65, 74, 77, 84, 87, 94, 102, 109, 125], "parse_afni_head": 65, "parse_arg": 65, "parse_ascconv": 65, "parse_cifti2": 65, "parse_filenam": 65, "parse_gifti_fast": [1, 65], "parse_gifti_fil": 1, "parse_par_head": 65, "parse_slic": 65, "parser": [1, 9, 28, 65, 77, 94, 125], "part": [2, 6, 10, 11, 12, 19, 20, 26, 31, 35, 40, 60, 64, 68, 69, 70, 76, 77, 84, 86, 102, 103, 105, 109, 111, 113, 115], "part03": 102, "part2": 38, "parti": [22, 59], "partial": [9, 15, 26, 90, 109], "particip": [23, 77], "particular": [1, 2, 3, 7, 9, 10, 11, 20, 22, 26, 27, 31, 35, 38, 40, 41, 43, 58, 60, 69, 102, 105, 110, 119], "particularli": [9, 19, 26, 34], "partli": 34, "pascal": 31, "pass": [1, 2, 3, 6, 7, 9, 15, 20, 26, 27, 41, 55, 66, 68, 69, 70, 71, 72, 76, 77, 81, 82, 84, 87, 89, 90, 94, 99, 102, 106, 117, 118, 119, 120, 123], "password": 26, "past": [19, 63], "patch": [1, 6, 9, 15, 19, 22, 50, 51, 79], "path": [1, 6, 19, 20, 22, 41, 54, 55, 61, 62, 73, 74, 78, 79, 80, 84, 85, 89, 92, 97, 102, 107, 120], "path_maybe_imag": [65, 87], "path_part": 79, "pathfor": 20, "pathlib": 120, "pathlik": [1, 15, 87, 89, 92, 98], "patient": [2, 32, 33, 35, 36, 40, 58, 60, 84, 102], "patient_ag": 84, "patient_birth_d": 84, "patient_dexter": 84, "patient_height": 84, "patient_id": [69, 84, 117, 118], "patient_nam": 84, "patient_orient": 84, "patient_sex": 84, "patient_weight": 84, "patientbirthd": 35, "patientbirthtim": 35, "patientid": 35, "patientnam": 35, "pattern": 105, "paul": [1, 56, 86, 122], "paus": 45, "pc": [2, 76], "pd": 35, "pddl": [4, 58], "pdf": [2, 60, 86, 94, 102], "peculiar": 41, "peek": 119, "peek_next": 65, "pend": [14, 77, 94, 119], "pending_data": [65, 77, 94], "peopl": [2, 4, 6, 14, 41, 51, 56, 60, 69], "pep257": 19, "pep8": 19, "per": [9, 10, 11, 12, 13, 15, 30, 36, 40, 74, 77, 78, 84, 90, 92, 102, 103, 108, 109, 119], "per_slic": 10, "per_volum": 10, "perarraydict": 65, "perarraysequencedict": 65, "perceiv": 23, "perez": [1, 49, 56], "perfect": 29, "perform": [22, 28, 35, 86, 103, 104, 116, 119], "perframefunctionalgroupssequ": 102, "perframefunctionalsequ": 10, "perhap": [6, 8, 13, 20, 43], "period": [6, 33, 82], "peristimulu": 1, "perl": 26, "permiss": [57, 58], "permit": [1, 11, 35, 58, 74, 103], "permit_trunc": 109, "permut": 1, "persist": [1, 35, 103], "person": [2, 4, 19, 20, 22, 35, 42, 58], "perspect": [16, 22, 23, 60], "persuad": 20, "pet": [37, 84], "pet_withdcm": 37, "peter": [1, 56], "ph": 109, "phantom_epi_3mm_cor_20aptrans_15rlrot_sense_15_1": 58, "phantom_epi_3mm_cor_sense_8_1": 58, "phantom_epi_3mm_sag_15ap_sense_13_1": 58, "phantom_epi_3mm_sag_15fh_sense_12_1": 58, "phantom_epi_3mm_sag_15rl_sense_11_1": 58, "phantom_epi_3mm_sag_sense_7_1": 58, "phantom_epi_3mm_tra_": 58, "phantom_epi_3mm_tra_15fh_sense_9_1": 58, "phantom_epi_3mm_tra_15rl_sense_10_1": 58, "phantom_epi_3mm_tra_sense_6_1": 58, "phantom_epi_asc_clear_2_1": [58, 109], "phase": [1, 9, 12, 16, 103, 109], "phi": [2, 10], "phil": 1, "philip": [1, 10, 31, 34, 38, 56, 59, 65, 66, 102, 109], "philipp": [1, 56], "philips_achieva_testfil": 58, "philosophi": [7, 25], "php": [1, 77, 104], "phrase": 2, "physic": 35, "physician_nam": 84, "physicist": 12, "pi": [29, 86, 113, 122], "pial": [15, 103], "pick": [22, 26, 33, 43], "pickl": 1, "pictur": 23, "piec": 119, "pil": 1, "pillow": 1, "pin": 1, "pinfo": 30, "pinsard": [1, 56], "pip": [1, 22, 56, 59, 66], "pipelin": [1, 7, 9, 14], "pipermail": 119, "pipx": 22, "pitch": 86, "pitt": 9, "pix": 109, "pixar": 38, "pixdim": [1, 12, 61, 62, 69, 72, 84, 103, 104, 117, 118], "pixel": [2, 12, 32, 33, 35, 36, 40, 101, 102, 109], "pixel_arrai": [36, 38], "pixelrespresent": 40, "pixelspac": [36, 38, 40], "pjoin": [4, 79], "pkg": 107, "pkg_def": 79, "pkg_like": 107, "pkg_path": 26, "pl": 22, "place": [1, 2, 6, 7, 9, 11, 14, 20, 23, 35, 40, 68, 76, 87, 92, 94, 116, 119, 123], "placehold": [9, 42], "plai": 9, "plain": [56, 66], "plan": [20, 23, 28], "plane": [12, 30, 33, 35, 38, 40, 68, 122], "plane_separ": 84, "platform": [1, 26, 57, 76], "player": 119, "pleas": [1, 4, 5, 6, 8, 19, 22, 26, 41, 45, 52, 56, 57, 66, 81, 98, 100, 103, 108, 120], "pleasant": [2, 9], "plot": [2, 116, 122], "plot_surf": 15, "plot_surf_img": 15, "plt": [2, 36, 60, 116, 122], "plu": [26, 38, 77, 109, 122], "pm": 1, "pmc2791793": 102, "pmcid": 102, "pmid": 102, "pmod": 37, "pmod_1": 37, "pmod_pet": 37, "pn": 35, "png": [2, 57, 60], "po": [40, 87, 88, 106, 116], "point": [1, 6, 7, 8, 9, 11, 12, 15, 17, 19, 22, 25, 27, 29, 30, 33, 34, 35, 36, 38, 39, 40, 43, 52, 53, 60, 61, 62, 68, 71, 76, 77, 78, 80, 87, 90, 94, 102, 103, 104, 109, 110, 111, 114, 116, 119, 123, 126], "pointer": [1, 7, 33, 40, 52, 125], "pointset": [0, 1, 15, 65, 94], "poldrack": 1, "polici": [6, 79], "polin": [1, 56], "polit": 19, "pool": [31, 38], "poor": 103, "pop": 77, "pop_cifti2_vertic": [65, 77], "popul": [10, 102], "popular": [2, 31, 57], "port": 14, "portion": [35, 58], "posit": [1, 2, 22, 33, 35, 36, 38, 39, 40, 60, 65, 66, 70, 72, 76, 77, 86, 87, 88, 90, 91, 92, 94, 102, 103, 106, 108, 113, 115, 116, 118, 119, 122, 123, 124], "posix": [1, 85], "possibl": [1, 3, 6, 9, 10, 11, 14, 15, 16, 20, 21, 23, 26, 35, 37, 43, 49, 54, 58, 61, 62, 70, 71, 72, 73, 77, 78, 85, 86, 102, 113, 117, 123], "possibli": [15, 16, 20, 36, 43, 72, 77, 84], "post": [6, 13, 16, 19, 38, 42, 49, 56, 77, 90, 123], "post_slic": 90, "poster": 43, "posterior": [2, 12, 18, 38, 54, 60, 69, 94, 109], "potenti": [10, 92, 102, 109], "poulin": 1, "power": [76, 77], "power7": 76, "powershel": 57, "ppc": [1, 26, 76], "ppc64": 1, "pr": [1, 6, 18], "practic": [2, 6, 7, 12, 15, 26, 30, 42, 60, 105, 123], "pradeep": 1, "pragmat": 15, "pre": [1, 40, 43, 123], "preambl": 35, "preced": [95, 103], "precis": [1, 2, 6, 9, 54, 61, 62, 71, 76, 86, 94, 113, 116, 123], "predecessor": 2, "predict": [14, 80, 90], "predict_shap": 65, "prefer": [2, 4, 6, 7, 8, 9, 15, 19, 22, 38, 43, 55, 56, 62, 76, 80, 90, 94, 98, 109], "prefix": [9, 22, 26, 35, 79, 109, 120], "preliminari": [1, 43, 59, 100], "prepar": [1, 64], "prepend": [79, 102], "prescal": 123, "prescrib": 33, "presenc": [102, 103], "present": [1, 9, 15, 20, 28, 35, 40, 69, 71, 72, 76, 88, 102, 103, 106, 124], "preserv": [1, 7, 9, 10, 12, 15, 28, 62, 68, 102], "press": 86, "presum": [31, 35, 40], "pretend": 90, "pretti": [9, 42, 123, 124], "pretty_map": 65, "pretty_print": 11, "prevent": [1, 10, 42, 76, 96, 123], "previou": [1, 2, 8, 14, 20, 27, 40, 43, 56, 66, 84, 87, 109], "previous": [1, 10, 109], "previousdir": 84, "primaci": 9, "primari": 6, "primarili": [1, 12], "primer": 9, "principl": [6, 13, 15, 23], "print": [1, 2, 4, 10, 26, 40, 61, 62, 66, 73, 74, 77, 78, 86, 94, 96, 100, 102, 103, 105, 114, 123, 124], "print_git_titl": 65, "print_summari": [65, 94], "prior": [1, 16, 22, 58, 94, 109], "priorit": 19, "privat": [1, 3, 7, 9, 10, 20, 23, 28, 30, 31, 36, 37, 39, 40, 102, 126], "pro": 49, "proactiv": 1, "probabl": [2, 8, 9, 12, 14, 20, 52, 60, 72, 102, 109, 118, 123], "problem": [1, 2, 7, 8, 12, 13, 14, 16, 20, 36, 38, 43, 55, 56, 69, 72, 86, 92, 96, 105, 109, 123, 124], "problem_level": [72, 96], "problem_msg": 72, "proc": 102, "proc_fil": 65, "procedur": [1, 6, 35, 56], "proceed": 69, "process": [0, 1, 9, 10, 11, 12, 16, 17, 19, 22, 25, 28, 40, 65, 69, 71, 76, 77, 94, 102, 119, 120], "processor": 93, "procur": 58, "prod": 93, "produc": [1, 10, 11, 20, 61, 70, 94, 102, 103, 106, 109], "product": [9, 36, 38, 40, 58, 63, 68], "profil": [1, 35], "profit": 58, "program": [1, 3, 7, 27, 31, 34, 74, 78, 92], "program_help": 74, "programmat": [10, 62], "progress": [3, 6, 16, 21, 23, 43, 52], "prohibit": 27, "project": [1, 4, 6, 18, 20, 22, 23, 28, 30, 43, 45, 48, 49, 56, 58, 77, 94, 110], "promot": [1, 58, 123], "proper": [1, 22, 26], "properli": [1, 9, 58], "properti": [1, 2, 7, 11, 12, 15, 20, 55, 58, 61, 64, 69, 70, 71, 72, 74, 77, 80, 84, 87, 88, 92, 94, 99, 100, 102, 103, 104, 105, 106, 109, 110, 116, 117, 118, 119, 122, 124], "proport": [38, 102], "propos": [6, 10, 11, 13, 16, 17, 20, 25, 28, 42], "prospect": 9, "prot_dict": 102, "protect": [76, 105], "proto": 1, "protocol": [1, 43, 66, 70, 84, 87, 90, 99, 102, 106, 110, 116], "prototyp": [1, 6], "prove": [20, 76], "proven": 94, "provid": [1, 4, 6, 9, 10, 15, 16, 18, 19, 28, 35, 38, 41, 56, 58, 61, 62, 66, 75, 77, 78, 81, 87, 90, 92, 103, 105, 106, 110, 119, 120], "provision": [6, 15, 30], "prox": 70, "proxi": [1, 3, 8, 14, 15, 27, 59, 62, 70, 74, 80, 81, 84, 99, 109, 118, 121], "proxy_img": 55, "prvblk": 84, "pseudo": 16, "psl": 109, "psu": 86, "psydata": 58, "pt": [40, 68, 119], "pth": [79, 85, 89], "ptr": 102, "pub": 74, "pubimag": 34, "public": [1, 7, 20, 23, 26, 43, 119], "publish": [1, 26, 58], "pubm": 102, "pull": [1, 3, 4, 6, 13, 16, 18, 23, 25, 27, 40, 42, 43, 44, 49, 51, 52, 99, 100, 102, 109], "pure": [1, 15, 57], "purg": 1, "purpos": [15, 16, 17, 23, 25, 43, 58, 88, 123], "pursu": 23, "push": [19, 26, 43, 49, 52, 53], "put": [2, 3, 4, 9, 10, 11, 18, 22, 34, 40, 60, 96, 102, 119], "pv": 109, "px_arr": 7, "px_img": 7, "pxyz_c": 92, "py": [1, 3, 4, 10, 11, 26, 38, 40, 52, 58, 73], "py2": 26, "py311": 22, "py312": 1, "py3k": 1, "pyarg": [56, 57, 66], "pyarray_fromdimsanddata": 1, "pyc": 26, "pydicom": [1, 31, 34, 37, 57, 102, 103, 112], "pydicom_compat": [0, 1, 65], "pyi": 1, "pylab": 36, "pylib": 26, "pylint": [1, 22], "pynifti": 59, "pynifti_pst": 1, "pypi": [1, 26, 57], "pypirc": 26, "pyplot": [2, 60], "pyproject": [1, 22], "pysurf": 92, "pytest": [1, 5, 26, 56, 57, 66, 73], "pytest_": 56, "python": [1, 3, 4, 6, 7, 9, 19, 20, 22, 26, 28, 30, 34, 38, 56, 59, 61, 76, 79, 80, 81, 96, 105, 120], "python26": 26, "pythonhost": 58, "pyupgrad": 1, "pyzstd": 1, "p\u00e9rez": [1, 56], "q": [1, 2, 9, 36, 68, 86, 102, 108, 109, 113], "q1": 113, "q12": 113, "q2": 113, "q2bg": 65, "q_est": 102, "q_vector": [65, 102, 109], "qa": 1, "qaff": 103, "qfac": 62, "qform": [1, 59, 103], "qform_cod": [1, 61, 62, 103, 104], "qoffset_i": [61, 62, 103, 104], "qoffset_x": [61, 62, 103, 104], "qoffset_z": [61, 62, 103, 104], "quadup": 38, "qualifi": 35, "qualiti": 1, "qualnam": 102, "quantiti": 77, "quat": [86, 113], "quat2angle_axi": 65, "quat2eul": 65, "quat2mat": [65, 86], "quatern_b": [61, 62, 103, 104], "quatern_c": [61, 62, 103, 104], "quatern_d": [61, 62, 103, 104], "quaternion": [0, 1, 62, 65, 86, 103], "quaternion_threshold": [65, 103, 104], "quaternions_and_spatial_rot": 113, "queri": [1, 9], "question": [26, 27, 56, 66, 90], "quick": [41, 52, 55, 78], "quicker": [48, 90], "quickest": 52, "quickli": 27, "quickstart": 65, "quiet": 9, "quit": [2, 11, 35, 43], "quot": [12, 18, 35, 76, 94], "r": [1, 2, 12, 26, 29, 36, 38, 40, 43, 54, 56, 60, 61, 62, 64, 69, 70, 74, 77, 80, 84, 86, 90, 92, 94, 99, 100, 102, 106, 108, 109, 114, 118, 123], "r11": 86, "r12": 86, "r13": 86, "r23": 86, "r266": 26, "r33": 86, "r_dtype": 29, "r_prime": 29, "ra": [1, 3, 15, 54, 60, 61, 62, 68, 77, 92, 102, 108, 109, 110, 111, 116, 119, 126], "raamana": 1, "race": 1, "radian": [2, 12, 68, 77, 86], "radiog": 60, "radiolog": [2, 69, 84, 123, 126], "radiologist": [60, 69], "radiopharmaceut": 84, "rag": 1, "rais": [1, 9, 11, 13, 14, 15, 20, 23, 26, 64, 69, 71, 72, 74, 76, 77, 79, 80, 82, 87, 89, 92, 93, 94, 96, 98, 101, 102, 103, 104, 107, 108, 109, 112, 116, 118, 119, 120, 121, 123, 124], "raise_warn": 66, "raktivan": [1, 56], "ram": 5, "ran": 14, "random": [6, 54, 62], "randomli": 1, "rang": [1, 22, 30, 35, 38, 71, 76, 77, 100, 101, 109, 111, 123], "rank": 1, "rapid": 22, "rare": [9, 10, 12, 15], "rasmm": 119, "rather": [1, 2, 4, 7, 8, 9, 12, 20, 22, 30, 35, 36, 38, 40, 51, 53, 61, 64, 76, 86, 90, 99, 100, 102], "ratio": [15, 122], "rational": 6, "ravel": 15, "raw": [1, 14, 18, 35, 36, 41, 69, 84, 102, 103], "raw_data_from_fileobj": [65, 69, 84], "raymond": [58, 105], "rb": [4, 87, 88, 106], "rc": [38, 123], "rd_": 36, "rdf": 9, "rdflib": 9, "re": [1, 8, 9, 20, 23, 27, 33, 37, 38, 43, 45, 51, 52, 53, 68, 87, 103, 104, 109, 116, 123], "reach": [6, 23, 26, 35, 51, 123], "read": [1, 2, 4, 7, 8, 9, 10, 14, 15, 18, 19, 20, 23, 25, 28, 31, 34, 36, 37, 39, 43, 49, 51, 52, 53, 55, 56, 62, 64, 65, 66, 69, 70, 74, 77, 78, 80, 84, 87, 88, 90, 91, 92, 94, 98, 99, 100, 103, 104, 106, 109, 116, 117, 118, 119, 123, 124, 125], "read_annot": [1, 65], "read_data_block": 65, "read_dicom": 40, "read_fil": [36, 112], "read_geometri": [15, 65], "read_img_data": [1, 65], "read_label": [15, 65], "read_metadata": 92, "read_mlist": 65, "read_morph_data": [15, 65], "read_mosaic_dir": 65, "read_mosaic_dwi_dir": 65, "read_off_disk_somehow": 8, "read_scalar": 92, "read_seg": 65, "read_shap": 90, "read_slic": 90, "read_stamp": 92, "read_subhead": 65, "read_tag": 40, "read_zt_byte_str": 65, "readabl": [1, 9, 19, 22, 40, 43, 72, 87, 102], "readdicomfil": 40, "readdir": [65, 78], "reader": [1, 9, 60, 102], "readi": [6, 26, 43, 49, 119], "readinto": [1, 65, 106], "readm": [1, 4, 26, 31, 43, 74], "readout": 12, "readthedoc": [22, 119], "real": [1, 2, 6, 14, 16, 20, 30, 35, 36, 43, 86, 99, 113, 119], "realign": 27, "realiz": 19, "realli": [3, 4, 9, 57, 103], "rearrang": 54, "reason": [4, 6, 7, 9, 10, 12, 19, 20, 23, 28, 35, 41, 43, 60, 71, 87, 116], "rebas": 51, "rec": [1, 3, 12, 56, 59, 66, 78, 109, 123], "rec2dict": 65, "recalcul": 40, "recarrai": [84, 123], "receiv": [7, 35], "recent": [1, 9, 39, 47, 64, 69, 71, 76, 103, 107, 121, 123, 124], "recip": 25, "reclassifi": 10, "recod": [9, 65, 124], "recogn": [19, 26, 69, 72, 76, 77, 103], "recogniz": [74, 87, 92], "recommend": [1, 3, 6, 9, 14, 56, 61, 66, 77], "recomput": 105, "reconstruct": [9, 29, 33, 36, 40, 62, 102, 103, 113], "record": [1, 2, 6, 9, 19, 27, 35, 41, 77, 78, 81, 82, 101, 109, 117], "recov": [1, 29, 38], "recoveri": 43, "recurs": [10, 79], "red": [2, 77, 94], "reddi": [1, 56], "reddigari": [1, 56], "redirect": 1, "redistribut": 58, "redraw": 122, "reduc": [1, 6, 10, 22, 43, 80, 86, 113, 123], "reduct": 86, "ref": [7, 43, 119], "refactor": [1, 10, 22, 43], "refcheck": 1, "refer": [1, 7, 8, 9, 10, 12, 15, 18, 20, 22, 28, 33, 35, 38, 40, 43, 48, 53, 54, 55, 56, 58, 60, 62, 66, 69, 74, 76, 77, 80, 86, 88, 92, 99, 100, 102, 103, 109, 110, 113, 118, 119, 126], "referenc": 35, "reflect": [1, 6, 12, 20, 38, 111], "reflog": 43, "refresh": [26, 52], "refurb": 1, "refus": 3, "reg": 15, "regard": 16, "regardless": [2, 6, 18, 19, 30, 80, 112], "regent": 58, "regex": 102, "region": 77, "regist": [2, 9, 20, 26], "registr": [2, 28, 30, 35], "registri": [1, 20], "regress": 1, "regular": [2, 6, 61, 62, 69, 103, 105, 106, 117, 118], "regularli": [1, 110], "reinder": [1, 56], "reject": [6, 7, 8, 16, 23], "rejoin": 51, "rel": [1, 2, 9, 10, 33, 34, 38, 42, 43, 60, 76, 78, 79, 102, 109], "rel_path": 79, "relat": [1, 2, 3, 6, 9, 10, 11, 15, 20, 22, 23, 26, 35, 43, 51, 56, 61, 62, 77, 92, 94, 101, 102, 103, 109, 113, 115], "relationship": [2, 8, 10, 20, 35, 60, 69, 74, 84, 92, 99, 100, 103, 104, 109, 116, 117, 118], "relax": 1, "releas": [6, 19, 22, 25, 56, 58, 59, 65, 66, 78], "relev": [1, 6, 9, 10, 16, 18, 22, 26, 34, 35, 39, 40, 56, 69, 85, 99, 100, 106], "reli": [6, 27, 76], "reliabl": [38, 84], "relic": 40, "relicens": 1, "reload": 80, "relpath": 79, "remain": [1, 6, 39, 40, 43, 72, 77, 102, 119], "remaind": 38, "rememb": [38, 43, 51, 80], "remind": [19, 43, 51], "remot": [20, 26, 42, 43, 49, 51, 52, 53], "remov": [1, 10, 14, 20, 26, 31, 43, 76, 77, 81, 89, 93, 94, 102, 120], "remove_gifti_data_arrai": [65, 94], "remove_gifti_data_array_by_int": [65, 94], "renam": [1, 10, 52], "render": [6, 60], "reorder": [2, 9, 12, 54, 90, 93, 109], "reorient": [1, 111], "rep": 72, "repeat": [9, 10, 19, 22, 31, 45, 55, 73, 87, 102, 123], "repetit": [1, 9, 86, 92, 116], "repetition_tim": 9, "repetitiontim": 10, "rephras": [2, 38, 90], "replac": [1, 6, 10, 20, 22, 30, 40, 42, 72, 90, 104, 120], "replai": 43, "replic": [1, 14, 22, 111], "repo": [1, 4, 6, 26, 43, 45, 51, 52, 79], "report": [1, 22, 23, 31, 65, 66, 78, 82, 109], "report_seq": 72, "repositori": [3, 4, 6, 20, 25, 26, 44, 51, 52, 57], "repres": [2, 6, 9, 11, 15, 35, 43, 61, 68, 76, 77, 80, 82, 86, 90, 94, 102, 119, 123], "represent": [9, 43, 51, 58, 74, 76, 77, 87, 92, 103, 109, 116, 119, 123, 126], "reproduc": [19, 58], "reproduct": 15, "request": [1, 3, 4, 6, 15, 16, 23, 25, 35, 42, 43, 51, 87, 94, 109], "requir": [1, 3, 4, 5, 6, 9, 12, 15, 16, 19, 20, 23, 26, 28, 30, 35, 37, 59, 61, 66, 70, 71, 74, 81, 93, 94, 102, 103, 109, 116, 119], "resampl": [1, 15, 30, 111, 115, 116], "resample_from_to": 65, "resample_to_output": 65, "rescal": [1, 69, 70, 71, 99, 109, 116, 118], "rescale_affin": [1, 65], "rescaleintercept": [36, 40], "rescaleslop": [36, 40], "research": [28, 31], "reserv": [35, 58, 77], "reset": [40, 43, 62, 65, 71, 105], "resetmixin": 65, "reshap": [1, 7, 11, 55, 61, 62, 64, 65, 68, 69, 70, 81, 86, 87, 90, 93, 102, 103, 113, 116, 123], "reshape_dataobj": 65, "resid": 92, "resiz": 1, "reslic": 116, "resolut": [16, 18, 23, 29, 33, 38, 78], "resolv": [1, 6, 10, 15, 19, 23, 43, 81, 103], "reson": 35, "resort": [62, 102], "resourc": [5, 9, 22, 25, 26, 34, 35, 48, 50, 57], "respect": [1, 2, 6, 9, 15, 22, 26, 35, 38, 57, 58, 62, 68, 74, 77, 78, 84, 86, 93, 94, 102, 109, 119], "respond": [6, 19, 23, 35], "respons": [6, 19, 25, 35, 58, 87], "rest": [3, 9, 40, 43, 51, 109, 114], "rest_bold": 15, "restart": 90, "restor": [1, 20, 26], "restrict": [20, 35, 58, 69, 86], "restructur": [1, 22, 114], "restructuredtext": 6, "restructuredtextprim": 6, "result": [1, 2, 7, 10, 13, 14, 15, 18, 20, 22, 27, 33, 36, 38, 40, 55, 58, 60, 62, 66, 68, 69, 70, 72, 74, 76, 77, 79, 80, 84, 90, 92, 93, 94, 96, 99, 100, 102, 108, 114, 118, 119, 123, 124], "resurrect": 1, "ret": [72, 123], "retain": [1, 14, 58], "retir": [7, 35], "retriev": [1, 6, 15, 84, 87, 94, 110, 116], "return": [1, 2, 3, 7, 8, 12, 14, 15, 27, 35, 37, 40, 52, 55, 60, 61, 62, 66, 68, 69, 70, 71, 72, 74, 76, 77, 78, 79, 80, 81, 82, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 113, 114, 115, 116, 117, 118, 119, 120, 121, 123, 124], "return_spati": 116, "reus": 28, "revers": [1, 2, 8, 26, 29, 36, 54, 108, 123], "review": [1, 4, 23, 25, 26, 46], "revis": [6, 43], "reword": 43, "rewrit": 1, "rf": 22, "rgb": [1, 94], "rgba": [65, 77, 94], "rgbt": 92, "ri": 109, "richard": 1, "riddel": [1, 56], "right": [1, 2, 11, 12, 15, 18, 26, 36, 38, 40, 42, 43, 51, 54, 58, 60, 68, 69, 77, 86, 102, 109, 119, 123], "rightmost": 69, "rigid": 6, "rip": 1, "risk": 1, "rl": 109, "rm": [1, 31, 74], "ro": 84, "road": 78, "roadmap": 25, "robert": [1, 56], "roberto": [1, 7, 56], "robust": [1, 34, 113], "roi": [1, 65], "rokem": [1, 56], "role": [9, 25], "roll": 86, "roo": [1, 56], "room": [1, 2, 123], "root": [1, 4, 26, 79, 89, 102], "root_dir": 79, "rorden": [1, 31, 34, 60], "rosetta": 4, "rosetta_data": 4, "ross": [1, 56], "rotat": [1, 2, 36, 68, 86, 102, 103, 109, 113, 116], "rotate_vector": 65, "rotation_affin": 2, "rotation_matrix": [65, 86, 102, 113], "rotn": 113, "rough": 56, "round": [1, 36, 39, 76, 87, 123], "routin": [1, 7, 12, 20, 30, 37, 40, 54, 68, 69, 73, 76, 85, 86, 90, 107, 108, 111, 115, 118, 119], "row": [1, 2, 9, 12, 18, 32, 36, 40, 62, 68, 70, 77, 84, 86, 102, 108, 109, 114, 119, 123], "row_nam": 114, "rowmajorord": 94, "rowss": 36, "rpm": 57, "rr": 109, "rs_": 36, "rst": [6, 20, 26, 102, 114], "rst2html": 26, "rst_tabl": 65, "rstutil": [0, 65], "rtol": [102, 113], "ruff": 1, "rule": [1, 8, 10, 12, 23, 26, 31, 35, 49, 71, 86, 90, 123], "run": [1, 2, 4, 9, 18, 20, 22, 23, 25, 26, 27, 40, 42, 56, 57, 66, 72, 73, 76, 90, 92, 124], "run_glm": 15, "run_slic": 65, "run_spm_th": 7, "run_spm_thing_on": 7, "run_spn_th": 7, "runner": [1, 72], "runtim": [77, 103], "runtimeerror": [20, 81, 82], "ruopeng": 1, "russ": 1, "rw": [1, 51, 64, 65, 69, 74, 77, 84, 87, 92, 99, 103, 109, 118], "rz": [68, 115], "s1": [69, 84, 103, 117, 118], "s10": [69, 84, 103, 117, 118, 123], "s12": 84, "s14": 84, "s15": 104, "s16": [84, 103, 104], "s18": [69, 103, 117, 118], "s20": 84, "s24": [69, 103, 104, 117, 118], "s3": [69, 117, 118], "s32": 84, "s4": [39, 69, 103, 104, 117, 118], "s64": 39, "s8": [69, 84, 117, 118], "s80": [69, 103, 104, 117, 118], "s_": 38, "s_1": 36, "s_2": 36, "s_3": 36, "s_dtype": 29, "s_x": 38, "s_y": 38, "s_z": 38, "safe": [1, 2, 4, 9, 27, 52, 90, 120], "safe_get": 65, "safer": [1, 62], "safeti": 41, "saff": 103, "sagitt": [109, 122], "sai": [2, 6, 7, 9, 10, 12, 14, 20, 26, 29, 35, 36, 38, 40, 43, 51, 54, 55, 62, 90], "said": 6, "same": [1, 2, 3, 7, 8, 9, 10, 11, 12, 14, 15, 16, 20, 27, 28, 29, 30, 31, 35, 36, 38, 39, 40, 43, 49, 55, 61, 62, 68, 69, 70, 71, 74, 76, 77, 78, 80, 84, 86, 87, 88, 90, 92, 93, 94, 99, 100, 102, 108, 109, 111, 113, 115, 116, 118, 119, 120, 123, 124], "same_file_a": [65, 88], "samir": [1, 56], "sampl": [4, 15, 30, 32, 61, 77, 110, 116], "samplesperpixel": 40, "sampling_interv": 105, "sampling_r": 105, "samuel": [1, 56], "san": 102, "sandro": [1, 56], "sanit": 65, "satisfact": 53, "satisfi": 102, "satra": 1, "satrajit": 56, "saturdai": 59, "save": [1, 7, 8, 10, 11, 13, 27, 28, 37, 41, 42, 43, 59, 62, 64, 65, 66, 69, 73, 80, 87, 90, 92, 94, 116, 118, 123], "save_nifti": 103, "saw": [12, 55], "sc": 23, "scalar": [9, 11, 68, 71, 76, 77, 86, 90, 92, 103, 111, 113, 116, 119, 123], "scalar_arrai": 92, "scalaraxi": 65, "scale": [1, 2, 30, 32, 38, 40, 59, 61, 65, 68, 69, 70, 71, 74, 80, 84, 87, 92, 98, 99, 100, 102, 103, 109, 116, 117, 118, 123], "scale_factor": [65, 102], "scale_min_max": 1, "scaled_arr": [99, 100], "scaled_imag": 62, "scaled_img": 62, "scalefactor": [7, 14, 21, 25, 30, 69, 71, 118, 123], "scaler_dtyp": 71, "scaling_affin": 2, "scaling_need": [65, 71], "scalingerror": 65, "scalng": 14, "scan": [2, 4, 9, 18, 20, 31, 61, 62, 109], "scan_start_tim": 84, "scanner": [1, 12, 28, 31, 38, 60, 61, 62, 103, 109, 119, 126], "scanningsequ": 31, "scannum": [69, 117, 118], "scari": 51, "scene": 27, "schedul": [1, 26], "scheltienn": [1, 56], "schema": 9, "scheme": [9, 62], "school": 58, "schult": 23, "schwartz": [1, 56], "scikit": [15, 23], "scipi": [1, 15, 57, 58, 111, 115], "scl_inter": [61, 62, 103, 104, 117], "scl_slope": [61, 62, 103, 104, 117, 118], "scm": 47, "scope": [6, 28], "score": 103, "scp": 35, "scratch": 30, "screen": 60, "script": [1, 4, 26, 34], "scroll": 122, "scu": 35, "sd": 111, "sdash": 102, "sdist": 26, "sdk": 31, "search": [56, 79, 89, 91, 102], "sec": 41, "second": [1, 2, 9, 11, 12, 29, 30, 38, 51, 54, 55, 60, 74, 77, 87, 101, 102, 105, 106, 108, 113, 123], "sect_c": 102, "section": [6, 9, 16, 20, 23, 33, 35, 38, 40, 42, 43, 51, 57, 60, 62, 76, 79, 80, 102, 109], "secur": [1, 20, 35], "see": [1, 2, 3, 4, 6, 7, 8, 10, 12, 13, 14, 18, 19, 20, 22, 23, 26, 28, 30, 33, 35, 36, 37, 38, 39, 40, 41, 43, 45, 47, 48, 52, 53, 54, 55, 56, 57, 60, 61, 62, 63, 64, 66, 69, 70, 71, 74, 76, 77, 80, 82, 84, 86, 90, 92, 93, 94, 96, 99, 102, 103, 104, 108, 109, 111, 113, 119, 123, 124], "seek": [23, 40, 65, 69, 70, 74, 84, 87, 88, 90, 91, 92, 106, 109, 123], "seek_gzip_factori": 43, "seek_tel": 65, "seekabl": 87, "seem": [1, 7, 9, 12, 13, 35, 38, 39, 40, 76, 90, 94, 102, 109, 119], "seen": [12, 26, 35, 61], "segment": 90, "select": [2, 9, 12, 15, 22, 26, 29, 34, 35, 43, 56, 57, 61, 66, 77, 80, 94, 102, 103, 110, 115, 122], "self": [3, 8, 14, 15, 69, 70, 71, 72, 74, 77, 79, 82, 84, 88, 89, 92, 102, 103, 105, 106, 109, 110, 117, 118, 119, 123], "sell": 58, "semant": [1, 9, 35], "semi": 102, "send": [6, 19, 35, 52, 56, 66, 100], "sens": [2, 6, 7, 9, 12, 13, 15, 16, 27, 60, 71, 76, 90], "sensibl": [14, 43, 51, 118, 123], "sensibli": 39, "sensit": [1, 9, 23], "sent": [35, 38, 119], "sentenc": [6, 23], "sep": [57, 59, 78], "separ": [1, 2, 6, 9, 10, 11, 13, 27, 30, 35, 36, 40, 43, 60, 61, 77, 79, 80, 87, 103, 109, 118], "septa_st": 84, "septemb": 59, "seq": 119, "seq_copi": 119, "sequenc": [1, 15, 31, 35, 68, 69, 72, 76, 77, 79, 86, 87, 89, 90, 92, 93, 94, 99, 100, 101, 102, 103, 104, 108, 109, 111, 113, 114, 115, 116, 119, 122, 123], "sequencenam": 40, "sequenti": [9, 33, 103], "serg": [1, 56], "seri": [1, 9, 10, 12, 13, 18, 26, 30, 31, 33, 34, 35, 36, 39, 49, 61, 72, 76, 77, 83, 84, 94, 102, 119, 126], "serial": [9, 15, 28, 87, 94, 103, 125], "serial_numb": 84, "serializ": 1, "serializableimag": [1, 65, 77, 92, 94, 103], "series_2": 94, "series_3": 94, "series_4": 94, "series_expon": 77, "series_signatur": [65, 102], "series_start": 77, "series_step": 77, "series_unit": 77, "seriesaxi": 65, "seriesexpon": 77, "seriesinstanceuid": 40, "seriesnumb": [31, 40], "seriesstart": 77, "seriesstep": 77, "seriesunit": 77, "serious": 19, "serv": 35, "server": [20, 26, 35], "servic": [48, 58, 126], "session": 2, "session_error": [61, 62, 69, 103, 117, 118], "set": [1, 2, 4, 5, 6, 7, 9, 10, 12, 15, 18, 20, 22, 26, 27, 31, 32, 33, 34, 39, 40, 41, 42, 43, 46, 50, 51, 52, 61, 62, 64, 69, 70, 71, 72, 74, 76, 77, 78, 80, 84, 85, 86, 87, 88, 89, 90, 92, 96, 99, 100, 102, 103, 104, 105, 106, 107, 108, 109, 110, 113, 116, 117, 118, 122, 123, 124, 126], "set_": 62, "set_affin": 7, "set_data_dtyp": [1, 27, 64, 65, 69, 77, 92, 103, 116], "set_data_offset": [65, 69, 109], "set_data_shap": [64, 65, 69, 92, 103, 104, 116, 117, 118], "set_dim_info": [65, 103], "set_filenam": [61, 65, 87], "set_filespec": 64, "set_head": 7, "set_int": [65, 103], "set_labelt": 1, "set_meta": 1, "set_origin_from_affin": [65, 118], "set_posit": [65, 122], "set_printopt": [2, 54, 61, 62], "set_qform": [1, 62, 65, 103, 104], "set_sform": [62, 65, 103, 104], "set_slice_dur": [65, 103], "set_slice_tim": [65, 103], "set_slope_int": [62, 65, 69, 103, 118], "set_volume_idx": [65, 122], "set_xyzt_unit": [65, 103], "set_zoom": [65, 69, 92, 116, 118], "setattr_on_read": 1, "setfilenam": 1, "setpixdim": 1, "setqformcod": 1, "setsformcod": 1, "setter": 41, "settimeunit": 1, "setup": [1, 25, 26, 31, 107], "setup_egg": 1, "setup_modul": 107, "setuptool": [1, 26], "setuptools_scm": 1, "setxyzunit": 1, "seven": [1, 103], "sever": [6, 7, 22, 35, 39, 48, 52, 65, 72, 77, 87, 94, 119, 123, 124], "sform": [1, 59, 103], "sform_cod": [1, 61, 62, 103, 104], "sg": 1, "sh": 35, "sha1": 26, "shall": [35, 38, 58], "shallow": 88, "shame": 4, "shape": [1, 2, 9, 10, 11, 12, 15, 23, 30, 41, 55, 60, 61, 62, 64, 65, 68, 69, 70, 74, 76, 77, 78, 80, 84, 86, 90, 92, 93, 94, 95, 99, 100, 102, 103, 104, 108, 109, 110, 111, 113, 115, 116, 117, 118, 119, 123], "shape_of_imag": 9, "shape_prim": 108, "shape_zoom_affin": 65, "share": [20, 69, 70, 79, 90, 102, 117], "shared_max": 76, "shared_min": 76, "shared_rang": 65, "sharedfunctionalgroupsequ": 102, "shear": [62, 68, 103], "shebang": 1, "sheet": 49, "shell": 94, "shellsortdcm": 31, "shenton": 102, "shepherd": [6, 19], "shift": [101, 109, 119], "ship": [14, 58], "shoemak": 86, "shoot": 38, "short": [1, 6, 9, 16, 35, 40, 41, 45, 90], "short_field": 123, "shorten": 42, "shorter": 35, "shortlog": 26, "should": [1, 2, 3, 6, 7, 9, 10, 11, 12, 14, 15, 16, 19, 20, 22, 23, 26, 31, 35, 38, 39, 42, 43, 45, 48, 51, 57, 60, 61, 62, 68, 69, 70, 72, 74, 77, 79, 80, 84, 85, 86, 87, 88, 89, 90, 91, 92, 94, 97, 99, 100, 102, 103, 104, 105, 106, 108, 109, 110, 111, 112, 114, 116, 117, 118, 119, 122, 123, 124, 125], "shouldn": 6, "show": [1, 2, 41, 43, 51, 53, 60, 65, 68, 78, 81, 109, 116, 122, 123], "show_slic": 2, "shown": [2, 23, 35, 81], "shrink_data": [65, 119], "shy": 7, "si": 83, "side": [2, 6, 19, 38, 55, 60, 69, 86], "sidebar": 56, "siemen": [1, 9, 10, 32, 34, 35, 40, 65, 102], "siemenswrapp": 65, "sigma": [15, 111], "sigma2fwhm": 65, "sign": [9, 20, 26, 35, 41, 62, 94, 102, 109, 113], "signal": [1, 12, 43, 52, 84, 102, 103, 111, 118, 123], "signatur": [15, 20, 40, 71, 72, 102], "signifi": 103, "signific": [19, 28], "significand": 76, "significantli": [10, 19], "silent": [3, 14, 77, 94, 103, 125], "silli": 121, "simd": 1, "similar": [6, 7, 11, 12, 16, 26, 35, 45, 62, 76, 102], "similarli": [2, 7, 19, 38, 60, 77, 86, 94, 106], "simpl": [1, 3, 8, 9, 11, 18, 19, 20, 23, 30, 52, 62, 68, 75, 77, 79, 102, 103, 116], "simpler": [1, 2, 40, 103], "simplest": [30, 36, 42, 52, 60], "simplfii": 1, "simpli": [10, 22, 41, 77, 119], "simplifi": [1, 10, 28, 35, 36, 104], "simul": 106, "sin": [2, 86, 122], "sin_gamma": 2, "sinc": [1, 10, 20, 21, 25, 26, 43, 52, 76, 105, 119], "singl": [1, 2, 6, 9, 10, 12, 13, 15, 16, 18, 33, 35, 38, 40, 54, 61, 62, 69, 70, 72, 74, 75, 77, 80, 84, 87, 90, 92, 93, 94, 99, 100, 102, 103, 104, 109, 115, 118, 119, 122], "single_line_axis_0": 54, "single_line_axis_1": 54, "single_line_axis_2": 54, "single_mag": [65, 103, 104], "single_vox_offset": [65, 103, 104], "singular": 108, "sio": 123, "site": [31, 56, 66], "situat": [7, 9, 27, 29, 41, 43, 60, 72, 86], "six": 1, "sixteen": 77, "size": [1, 2, 4, 12, 13, 14, 18, 20, 30, 36, 38, 54, 61, 62, 65, 68, 77, 78, 84, 90, 91, 94, 99, 102, 103, 106, 109, 111, 119, 123, 125], "sizeof_hdr": [61, 62, 65, 69, 103, 104, 117, 118], "sketchi": 42, "skip": [1, 4, 20, 77, 90, 107, 112], "skip_thresh": 90, "sl": [35, 109], "slab": [36, 102], "slash": 79, "slc": 77, "slice": [1, 2, 12, 13, 18, 28, 30, 32, 33, 40, 55, 59, 60, 64, 69, 70, 73, 77, 87, 90, 93, 99, 100, 102, 103, 109, 115, 116, 119, 122, 123], "slice0": 12, "slice1": 12, "slice2len": 65, "slice2outax": 65, "slice2volum": 65, "slice_0": 2, "slice_1": 2, "slice_2": 2, "slice_aff": 115, "slice_affin": [65, 116], "slice_cod": [9, 61, 62, 103, 104], "slice_def": 64, "slice_dur": [61, 62, 69, 103, 104], "slice_end": [9, 61, 62, 103, 104], "slice_img": 13, "slice_ind": [65, 102], "slice_indic": 109, "slice_mat": 30, "slice_max": 109, "slice_min": 109, "slice_no": [30, 109], "slice_norm": [65, 102], "slice_order_cod": 103, "slice_shap": 115, "slice_spec": 70, "slice_start": [9, 61, 62, 103, 104], "slice_tim": 103, "sliceabledatadict": 65, "slicecopi": [17, 25], "sliced_arr": 90, "slicedef": 8, "sliceloc": 38, "slicenormalvector": 36, "sliceobj": [90, 99, 100, 116], "slicer": [1, 8, 9, 15, 34, 61, 65, 78, 90, 116, 122], "slicers2seg": 65, "slices_to_seri": 65, "slightli": [1, 2, 102, 104], "slope": [1, 14, 62, 65, 69, 70, 71, 102, 103, 109, 117, 118, 123], "slopearraywrit": 65, "slopeinterarraywrit": 65, "slow": [5, 12, 18, 26, 76], "slowest": [8, 9, 12, 18, 69, 90, 103, 106], "slowli": 1, "small": [1, 2, 3, 6, 10, 20, 25, 27, 35, 62, 86, 102], "smaller": [4, 15, 28, 35, 71], "smallest": [1, 76, 103, 123], "smallmatrix": [38, 40], "smax": [69, 117, 118], "smin": [69, 117, 118], "smith": [1, 56], "smooth": [1, 12, 23, 111], "smooth_imag": 65, "smoothed_img": 111, "smp": 18, "snapshot": [22, 26, 31, 57], "sniff": 87, "sniff_max": 87, "snippet": [49, 56, 58, 125], "so": [1, 2, 3, 4, 6, 7, 8, 9, 10, 12, 13, 15, 19, 20, 26, 28, 29, 30, 31, 34, 35, 36, 37, 38, 40, 43, 51, 52, 53, 54, 55, 56, 58, 61, 62, 68, 69, 71, 74, 76, 77, 78, 79, 80, 81, 84, 86, 87, 90, 92, 93, 94, 102, 103, 105, 107, 116, 118, 119, 120, 123, 124], "soc": 102, "softwar": [2, 9, 12, 20, 34, 35, 37, 56, 58, 60], "softwwar": 58, "soichi": [1, 56], "solon": 1, "solovei": [1, 56], "solut": [11, 16, 102], "solv": [2, 8, 10, 16, 20, 57], "some": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 15, 18, 21, 22, 27, 29, 30, 33, 34, 35, 36, 38, 40, 41, 42, 45, 46, 49, 51, 52, 56, 58, 60, 61, 62, 64, 66, 69, 74, 76, 77, 80, 84, 87, 90, 92, 93, 96, 99, 100, 102, 103, 104, 108, 109, 110, 116, 117, 118, 119, 120, 121, 123, 124], "some_filenam": [7, 64], "some_funct": 107, "some_imag": [27, 30, 64], "somedir": 79, "somehow": 10, "someimag": 8, "someon": [3, 6, 7, 20, 43, 51, 58, 96, 107, 126], "someones_anatomi": [2, 60], "someones_epi": 2, "someth": [4, 7, 8, 10, 19, 20, 22, 26, 27, 29, 36, 38, 42, 43, 52, 53, 57, 61, 69, 71, 90, 100, 107, 115, 116, 120, 123, 124], "sometim": [1, 2, 6, 8, 13, 19, 34, 35, 43, 54, 55, 60, 84, 90, 116], "somewhat": [2, 9, 33, 40], "somewher": [20, 52], "soon": [1, 20], "sop": [102, 126], "sopclassuid": 40, "sophist": [1, 43, 69, 103], "sorri": 38, "sort": [1, 11, 26, 38, 61, 65, 76, 79, 87, 102, 116], "sort_info": 109, "sortdicom": 31, "sorter": 22, "sound": [6, 43, 76], "sourc": [1, 4, 6, 9, 10, 15, 25, 26, 27, 31, 34, 56, 58, 59, 69, 79, 84], "source_filenam": 27, "source_img": 27, "sourceforg": 84, "space": [0, 1, 9, 12, 15, 18, 32, 33, 35, 54, 60, 61, 62, 64, 65, 68, 74, 77, 92, 94, 102, 103, 104, 108, 109, 110, 111, 116, 118, 119, 126], "spacingbetweenslic": 36, "span": [77, 94], "sparc": 76, "sparql": 9, "spatial": [2, 9, 10, 12, 60, 77, 92, 95, 103, 111, 115, 116, 122], "spatial_ax": 9, "spatial_axes_first": 65, "spatial_axi": 9, "spatialfirstslic": 65, "spatialhdrt": 116, "spatialhead": [1, 65, 69, 74, 84, 92, 99, 109], "spatialimag": [0, 1, 3, 10, 65, 69, 74, 84, 92, 93, 97, 98, 99, 103, 109, 110, 111], "spatialimgt": 116, "spatialprotocol": 65, "spatiotempor": 12, "spec": [1, 9, 15, 20, 62, 70, 77, 94, 102, 103], "special": [1, 6, 10, 15, 28, 35, 40, 58, 84, 105, 110, 123], "specif": [1, 2, 3, 6, 9, 11, 12, 14, 15, 20, 26, 28, 33, 35, 37, 38, 41, 45, 58, 61, 62, 64, 70, 74, 76, 77, 78, 84, 86, 87, 94, 98, 99, 100, 102, 103, 105, 111, 113, 116, 125], "specifi": [1, 2, 7, 8, 9, 10, 11, 12, 13, 14, 22, 35, 38, 60, 62, 68, 69, 70, 71, 74, 76, 77, 78, 79, 80, 82, 84, 86, 90, 92, 93, 94, 99, 100, 102, 103, 104, 106, 107, 109, 111, 113, 115, 116, 119, 123, 124], "specimen": 35, "spectroscopi": [35, 40], "spectrum": 61, "specul": 84, "speech": 56, "speed": [1, 3, 30, 90], "speedup": 19, "spell": [19, 22], "spellcheck": [1, 22], "sphere": 15, "sphinx": [1, 6, 57, 59], "spiderman": 38, "spline": [30, 111], "split": [2, 4, 6, 9, 10, 13, 15, 64, 68, 76, 79, 84, 89], "splitext": 89, "splitext_addext": 65, "spm": [1, 7, 9, 12, 13, 21, 25, 27, 28, 29, 32, 33, 34, 36, 38, 39, 57, 62, 69, 102, 118], "spm2": [56, 66, 117], "spm2analyz": [0, 65], "spm2analyzehead": 65, "spm2analyzeimag": 65, "spm8": [34, 39, 40], "spm94": 30, "spm99": [56, 66, 118], "spm99analyz": [0, 65], "spm99analyzehead": [65, 117], "spm99analyzeimag": [65, 117], "spm_conv_vol": 30, "spm_create_vol": 30, "spm_dicom_convert": [32, 34], "spm_dicom_dict": 32, "spm_dicom_head": [32, 34, 39], "spm_dicom_ori": [38, 40], "spm_file": 66, "spm_map_vol": 30, "spm_read_vol": 30, "spm_render_vol": 30, "spm_sample_vol": 30, "spm_slice_vol": 30, "spm_type": 30, "spm_unmap_vol": 30, "spm_vol": 30, "spm_vol_ana": 117, "spm_vol_check": 30, "spm_write_plan": 30, "spm_write_vol": 30, "spmanalyzehead": [65, 103], "spread": 10, "spring": 3, "spuriou": 26, "sq": 35, "sqrt": [36, 102, 113], "squar": [36, 40, 102, 113], "squash": 43, "squeez": 93, "squeeze_imag": 65, "squeezed_img": 93, "sr": 35, "src": [10, 26], "srow_i": [61, 62, 103, 104], "srow_x": [61, 62, 103, 104], "srow_z": [61, 62, 103, 104], "ss": [35, 109], "ssh": [1, 43, 45], "st": [1, 35, 42, 56], "stabil": [1, 6], "stabl": [1, 20, 34], "stack": 102, "stackid": 102, "stacklevel": 81, "stackoverflow": 26, "stage": [4, 43], "stai": 86, "stamp": 92, "stand": [2, 6, 124], "standard": [1, 2, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 19, 20, 22, 26, 28, 32, 33, 36, 37, 38, 40, 55, 64, 76, 77, 81, 84, 87, 89, 94, 102, 103, 105, 116, 118, 119, 120, 121, 123, 124, 126], "start": [1, 2, 4, 9, 15, 25, 26, 28, 33, 35, 37, 40, 43, 47, 48, 59, 68, 70, 77, 84, 88, 90, 92, 94, 102, 103, 109, 119, 123, 124], "start_field": [69, 117, 118], "start_ornt": 108, "start_tim": 37, "startelementhandl": [65, 77, 94, 125], "startofpixeldata": 40, "stat": [1, 15, 42, 65], "state": [6, 19, 20, 22, 26, 27, 35, 43, 57, 59, 105], "statement": [1, 35, 39, 56, 102, 120], "static": [20, 77, 92, 105], "staticmethod": 11, "statist": [1, 18, 78, 97], "statu": [6, 7, 9, 11, 12, 13, 14, 15, 16, 26, 42, 43, 49, 52, 84], "stc": [12, 15, 18], "stdin": 26, "stdlib": 22, "steer": 6, "stefan": 58, "step": [1, 14, 15, 16, 26, 30, 31, 35, 40, 44, 61, 68, 77, 90], "step_siz": [65, 119], "stephan": [1, 58], "stephen": 1, "stepwis": 12, "stereometr": 35, "stereotax": 94, "sternli": 43, "stevenson": 1, "still": [1, 2, 6, 7, 9, 12, 23, 26, 27, 35, 39, 40, 43, 61, 62, 64, 69, 111, 120], "stop": [1, 38, 43, 77, 90], "storag": [12, 20, 28, 35, 38, 69, 70, 76, 77, 87, 102, 116, 123], "storage_dtyp": 70, "store": [1, 2, 4, 7, 9, 10, 12, 14, 15, 18, 20, 27, 28, 29, 35, 36, 37, 39, 41, 60, 61, 62, 69, 74, 77, 80, 84, 87, 92, 94, 99, 100, 102, 103, 104, 105, 109, 116, 117, 118, 119, 123], "str": [1, 15, 69, 74, 77, 78, 79, 80, 81, 82, 84, 87, 88, 89, 92, 94, 98, 99, 102, 103, 104, 106, 107, 109, 110, 111, 114, 118, 119, 120, 121, 122, 123, 124, 125], "str_delim": 102, "str_io": [69, 103, 124], "straight": 119, "straightforward": [15, 43, 64, 86], "strang": [43, 96, 102], "strateg": 23, "strategi": [1, 26], "stream": [1, 33, 35, 39, 72, 78, 87, 102, 106], "streamimgt": 87, "streamlin": [0, 1, 17, 25, 65], "streamlines_fil": 11, "streamlinesfil": [11, 119], "strength": 101, "strict": [1, 9, 58, 94, 109], "strict_sort": 109, "stricter": 1, "stride": 90, "strided_arr": 90, "strided_scalar": 65, "string": [1, 9, 10, 11, 20, 30, 31, 35, 39, 40, 69, 72, 73, 77, 78, 79, 82, 85, 87, 89, 90, 91, 92, 94, 102, 103, 106, 114, 117, 118, 119, 120, 121, 123, 124, 125], "stringent": 70, "strip": [71, 91, 102, 103], "strip_shear": 103, "stroke": 60, "strong": 111, "strongli": [3, 19, 57], "struct": [9, 30, 40, 102], "structarr": [41, 65, 69, 124], "structread": 65, "structur": [1, 2, 9, 15, 30, 40, 58, 62, 71, 77, 84, 92, 103, 109, 110, 119, 123, 124, 126], "structured_array_extens": 43, "stub": 1, "stuck": 43, "studi": 35, "study_descript": 84, "study_typ": 84, "studyd": 31, "studytim": 31, "stuff": [7, 8, 10, 20, 27, 30, 43, 48, 51, 57, 64], "stutter": [1, 56], "style": [1, 6, 19, 25, 34, 60, 70, 74, 92], "su": 9, "sub": [1, 3, 10, 15, 74, 77, 84, 102, 106], "subclass": [1, 11, 77, 81, 102, 103, 110, 116, 124], "subcort": 15, "subdivid": 15, "subfunct": 40, "subhead": [10, 13, 84], "subheader_class": [65, 84], "subject": [6, 15, 35, 58, 60, 126], "subjects_dir": 15, "sublicens": 58, "submiss": 1, "submit": [1, 6, 109], "submodul": [1, 3, 5, 25, 107], "subpkg": 107, "subplot": 2, "subramaniam": [1, 56, 92], "subsampl": [58, 116], "subscrib": 56, "subscript": [56, 102], "subsequ": [7, 35, 40, 78, 102], "subset": [15, 77, 106, 109], "subspac": 15, "substant": 6, "substanti": [1, 26, 58], "substitut": [26, 58], "subtl": 30, "subtract": [40, 71, 123], "subvers": [20, 49], "success": [6, 28, 107], "successfulli": 43, "successor": 1, "succinctli": 43, "sudo": [47, 57], "suffici": [23, 103], "suffix": [26, 89, 94, 120], "suggest": [1, 3, 12, 14, 19, 22, 38, 45, 56, 57, 66, 72, 90, 94, 109, 120], "suit": [1, 11, 57, 109, 119], "suitabl": [6, 58, 106, 114], "sum": [14, 38, 40, 76], "summar": [9, 10, 23], "summari": [7, 9, 16, 21, 22, 23, 42, 46, 50, 78, 86], "summit": 42, "sun": 59, "sundai": 59, "superior": [2, 12, 18, 54, 77, 109], "supersed": [6, 16], "superset": [9, 19], "supplement": 102, "suppli": [75, 77], "support": [1, 9, 11, 12, 13, 18, 22, 23, 28, 34, 35, 41, 57, 66, 69, 72, 87, 92, 95, 100, 103, 105, 107, 109, 116, 119, 125], "supported_np_typ": 65, "supports_data_per_point": [65, 119], "supports_data_per_streamlin": [65, 119], "suppos": 43, "suppress": [1, 2, 54, 61, 62, 81], "suptitl": 2, "sure": [1, 2, 4, 6, 7, 9, 13, 26, 27, 42, 43, 80, 84, 119, 123], "surf": [15, 94], "surf_img": 94, "surfac": [1, 18, 30, 35, 65, 77, 87, 92, 94, 103], "surface_mask": [65, 77], "surface_number_of_vertic": 77, "surface_parcel": 77, "surfacefileformat": 92, "surfacenumberofvertic": 77, "surfer": 92, "surplu": 90, "surpris": 12, "surround": 6, "sustain": 19, "suter": [1, 56], "sv10": 39, "svd": 108, "svg": [1, 26], "svn": 49, "sw_hdr_data": 69, "sw_version": 84, "swap": [1, 69, 71, 92, 102, 123, 124], "swapped_cod": [69, 117, 118, 124], "swig": [1, 34], "switch": [1, 14, 43, 52], "sy": [4, 79], "syam": 56, "symbol": 113, "symmetr": [1, 102], "symmetri": 102, "sympi": [1, 86], "sync": 1, "syngo": 39, "syngodt": 39, "synonym": 1, "syntax": [7, 8, 26, 30, 35, 124], "sys_dir": 79, "sys_execut": 26, "sys_platform": 26, "sys_vers": 26, "system": [1, 3, 20, 32, 33, 35, 36, 40, 41, 54, 57, 60, 61, 62, 85, 86, 94, 102, 109, 110, 113, 119, 126], "system_typ": 84, "systemerror": 1, "systemwid": 85, "s\u00f3lon": 56, "t": [1, 2, 3, 4, 6, 7, 8, 9, 10, 12, 13, 14, 16, 19, 20, 26, 27, 29, 33, 34, 36, 37, 38, 40, 43, 44, 45, 52, 53, 55, 57, 58, 60, 61, 64, 68, 69, 76, 77, 79, 80, 81, 86, 87, 90, 92, 99, 100, 102, 103, 104, 105, 107, 108, 115, 118, 119, 123], "t0": 105, "t1": [58, 89], "t2": [58, 89], "t2_": 58, "t_arr": 108, "tab": 61, "tabl": [1, 15, 20, 35, 77, 78, 92, 94, 109, 114], "table2str": 65, "table_str": 114, "tag": [1, 9, 19, 22, 26, 32, 33, 77, 94, 102, 109, 126], "tag_for_keyword": 112, "tag_nam": 102, "tailor": 48, "tait": 86, "take": [1, 2, 4, 7, 8, 9, 10, 12, 19, 20, 23, 26, 28, 30, 36, 38, 40, 43, 52, 60, 62, 64, 69, 76, 87, 90, 102, 103, 108, 109, 116, 123], "taken": [23, 41, 58, 77, 109, 115], "tal": 18, "talairach": [2, 18, 62, 94, 103], "talk": [33, 60], "tangl": 43, "tar": 58, "tarbal": [1, 26, 57], "target": [22, 27, 78], "target_filenam": 27, "target_img": 27, "tarr": 108, "task": [28, 35, 43, 94], "taskvsbase_tstat": 15, "tast": 60, "taylor": 1, "tck": [1, 11, 65, 78], "tck2trk": 65, "tck_file": 119, "tckfile": [1, 65], "te": 92, "teach": [14, 28], "team": [9, 23, 28, 58, 78], "tear": 43, "teardown": 1, "technic": [6, 16, 49], "technologi": 16, "tell": [2, 6, 12, 27, 31, 35, 38, 42, 52, 53, 54, 65, 70, 86, 102, 106, 109, 119, 123], "tempfil": [1, 27, 64, 120], "templat": [2, 20, 23, 58, 80, 87, 89, 116], "template_dtyp": [65, 69, 84, 92, 103, 104, 117, 118, 124], "template_fnam": 89, "tempnam": 7, "tempor": [33, 60, 92], "temporari": [27, 120], "temporarili": 120, "temporarydirectori": [1, 65], "tempt": [7, 19], "temptat": 3, "ten": 94, "tend": 6, "tensor": [77, 94, 102], "terhorst": 42, "term": [2, 4, 6, 9, 10, 12, 14, 15, 19, 30, 33, 35, 38, 40, 56, 60, 66, 68, 76, 84, 90, 108, 109, 123], "termin": [35, 39, 57, 91], "terminologi": 84, "tesla": 101, "test": [1, 3, 6, 7, 12, 19, 20, 25, 26, 31, 34, 40, 41, 42, 43, 52, 54, 55, 57, 58, 61, 62, 65, 70, 77, 78, 80, 82, 84, 88, 89, 94, 100, 102, 103, 107, 109, 112, 113, 120, 123, 124], "test4d": 41, "test_bugfix": 42, "test_image_api": 3, "test_my_bug": 52, "test_my_format_name_her": 3, "test_nam": 77, "test_parrec": 3, "test_parrec_data": [3, 4], "test_proxy_api": [3, 70], "test_someth": 4, "testparrecapi": 3, "teve": 1, "text": [1, 6, 9, 18, 22, 26, 35, 42, 43, 51, 56, 60, 65, 75, 77, 103, 114, 120, 123], "textur": 15, "tf": [88, 90, 102], "tfn": 89, "thalamu": 77, "thalamus_left": 77, "than": [1, 2, 3, 6, 8, 9, 13, 20, 30, 31, 35, 38, 39, 40, 51, 52, 53, 61, 62, 68, 69, 70, 76, 77, 78, 82, 86, 87, 90, 91, 99, 100, 102, 103, 104, 109, 110, 111, 114, 119, 123], "thank": [1, 7, 11, 19, 23, 42, 52], "thei": [1, 2, 3, 4, 6, 7, 9, 10, 12, 16, 19, 20, 22, 23, 26, 27, 28, 35, 36, 38, 43, 53, 60, 62, 64, 69, 71, 72, 77, 78, 79, 80, 86, 94, 102, 103, 105, 109, 113, 116, 119, 123, 124], "theirs": 23, "them": [1, 3, 6, 7, 9, 10, 11, 16, 19, 20, 26, 27, 28, 30, 31, 35, 43, 52, 54, 60, 62, 77, 86, 89, 94, 102, 105, 119, 123], "themselv": [6, 89], "thenc": 123, "theorem": 86, "theori": [2, 58, 102], "therebi": 23, "therefor": [1, 2, 7, 9, 12, 13, 18, 19, 20, 28, 35, 36, 38, 54, 60, 61, 68, 71, 76, 79, 86, 87, 102, 109, 111, 116], "theta": [2, 86, 113], "thi": [1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19, 20, 22, 23, 24, 26, 27, 28, 29, 30, 31, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 48, 51, 52, 53, 54, 55, 56, 57, 58, 60, 61, 62, 64, 66, 68, 69, 70, 71, 72, 73, 74, 76, 77, 79, 80, 81, 84, 85, 86, 87, 88, 90, 91, 92, 93, 94, 99, 100, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 115, 116, 117, 118, 119, 120, 122, 123, 124], "thick": [2, 33, 77, 92, 109, 114], "thick_long": 114, "thin": [103, 125], "thing": [2, 6, 7, 8, 14, 20, 28, 46, 49, 52, 61, 64, 72, 76, 78, 102, 107, 120, 123], "think": [2, 4, 6, 7, 8, 20, 22, 30, 36, 38, 43, 52, 60, 76, 84], "third": [1, 2, 9, 22, 30, 36, 38, 54, 60, 77, 109, 123], "thirion": 56, "thoma": [1, 56], "those": [1, 9, 10, 13, 20, 22, 26, 33, 35, 37, 40, 41, 43, 49, 51, 56, 62, 68, 78, 80, 87, 93, 102, 103, 104, 105, 110, 119], "though": 69, "thought": [7, 8, 36, 38, 43], "thrash": 8, "thread": [1, 6, 9, 14, 16, 19, 90, 120], "threaten": 1, "three": [2, 6, 7, 9, 11, 12, 14, 15, 20, 26, 28, 35, 38, 40, 43, 61, 62, 77, 86, 94, 103, 111, 116, 119, 123], "three_to_four": 10, "threshold": [1, 76, 86, 90, 102, 108, 113, 123], "threshold_heurist": 65, "through": [1, 2, 9, 11, 12, 19, 22, 23, 26, 35, 37, 62, 76, 77, 90, 109, 119], "throw": [42, 55, 69, 90], "thu": [7, 9, 20, 26, 35, 38, 59, 69, 74, 76, 77, 84, 86, 94, 96, 102, 105, 108, 113, 119], "thual": 1, "thumb": [12, 38, 49, 86], "thursdai": 59, "thyreau": 1, "ti": [26, 92], "tie": 69, "tight": 102, "till": 103, "tim": 1, "time": [1, 6, 8, 9, 10, 13, 14, 15, 18, 20, 22, 23, 28, 31, 33, 35, 37, 38, 43, 44, 59, 60, 61, 62, 65, 69, 70, 74, 77, 80, 84, 87, 90, 91, 92, 94, 99, 100, 101, 103, 105, 106, 109, 110, 111, 116, 118, 119, 123], "time_axis_index": 12, "time_unit": 1, "timefram": 23, "timelin": 1, "timeout": 87, "timepoint": [1, 77], "timer": 66, "timeseri": [1, 77], "timestamp": 106, "timestep": 94, "ting": [1, 56], "tinypet": 84, "tip": 49, "titl": [1, 6, 16, 35, 43, 73, 114, 122], "title_head": 114, "tkr": 92, "tlrc": 74, "tm": 35, "tmp": [26, 43, 120], "tmpdir": [0, 1, 65], "tmpgpid3": 26, "to_byt": [1, 65, 87, 94], "to_cifti_brain_structure_nam": [65, 77], "to_fil": 1, "to_file_map": [65, 69, 77, 84, 87, 92, 94, 103, 116, 118], "to_filenam": [1, 15, 27, 61, 65, 87, 116], "to_fileobj": [1, 65, 71], "to_filespec": 1, "to_head": 65, "to_map": [65, 77], "to_mask": [65, 110], "to_matvec": [65, 115], "to_read": 90, "to_stream": [1, 65, 87], "to_vox_map": 111, "to_world": [65, 119], "to_xml": [1, 65, 75, 94, 125], "to_xml_clos": 1, "to_xml_open": 1, "tobyt": [69, 123], "tocent": 33, "todo": 10, "toffset": [9, 61, 62, 103, 104], "togeth": [10, 20, 35, 77], "toggl": 1, "tol": [102, 108], "toler": [1, 40, 78, 102, 108], "tom": 1, "toml": [1, 22], "tom\u00e1\u0161": [1, 56], "too": [1, 4, 10, 19, 27, 82, 90, 123], "took": [2, 54], "tool": [1, 6, 9, 20, 22, 28, 34, 37, 49, 83], "toolbox": 28, "top": [3, 4, 9, 10, 18, 36, 38, 43, 51, 56, 60, 64, 66, 103, 107, 115, 123], "toplevel": 22, "topo": 94, "topologi": [15, 94], "torig": 92, "tort": 58, "torvald": 49, "tos": 49, "tostr": [1, 125], "total": [33, 77, 90, 119], "total_nb_row": [65, 119], "tournier": 1, "toward": [1, 2, 8, 17, 25, 38, 54, 60, 69, 76, 77, 111], "tox": [1, 22, 56, 66], "tp": 39, "tpc": 84, "tr": [1, 77, 92], "traceback": [64, 69, 71, 76, 103, 107, 121, 123, 124], "track": [1, 2, 6, 7, 16, 20, 21, 22, 25, 28, 37, 39, 42, 51, 119], "tracker": [23, 28], "trackvi": [1, 11, 119], "tractgogram": 119, "tractogram": [1, 65, 78], "tractogram_fil": 65, "tractogramfil": 65, "tractogramitem": 65, "trade": 90, "tradition": 74, "trail": [89, 111, 115], "trailing_suffix": 89, "train": [101, 109], "trajectori": 2, "tran": [68, 115], "transaxial_fov": 84, "transfer": [15, 35], "transform": [1, 9, 30, 35, 38, 40, 41, 62, 68, 69, 77, 86, 90, 92, 93, 94, 102, 103, 108, 109, 110, 111, 113, 116, 118, 119, 122, 126], "transform_affin": 108, "transform_matrix": 77, "transformation_matrix_voxel_indices_ijk_to_xyz": 77, "transformationmatrixvoxelindicesijktoxyz": 77, "transformed_affin": 108, "transformed_pt": 68, "transit": 1, "translat": [1, 2, 9, 10, 15, 36, 38, 61, 68, 69, 77, 103, 109, 118, 123], "translation_affin": 2, "transm_source_typ": 84, "transmit": [35, 38], "transpar": [3, 8, 67, 77, 92], "transpos": [9, 36, 60, 102, 103, 108, 116], "transvers": [2, 109], "trap": 14, "travel": [2, 6], "travers": 10, "travi": [4, 26], "treat": [1, 10, 15, 122, 123], "tree": [20, 22, 26, 44, 79], "tremend": 19, "tri": [3, 9, 23, 40, 41, 107], "trial": [35, 84], "triangl": [1, 15, 92, 94], "triangles_2": 94, "triangles_3": 94, "triangles_4": 94, "triangles_5": 94, "triangular": [15, 92, 110], "triangularmesh": 15, "trick": [2, 12, 123], "trigger": [1, 22, 105], "trim": 90, "trip": [14, 87], "trip_msg": 107, "tripl": 77, "triplet": [15, 38, 77, 89, 110], "tripwir": [0, 1, 65, 107], "tripwireerror": [1, 65, 107], "trivial": [41, 106], "trk": [1, 11, 65, 78], "trk2tck": 65, "trk_file": 119, "trkfile": [1, 11, 65], "trkv2": 1, "trkv3": 1, "true": [1, 2, 7, 8, 9, 11, 12, 22, 27, 29, 30, 35, 36, 39, 40, 41, 42, 54, 55, 61, 62, 64, 66, 68, 69, 70, 71, 72, 73, 74, 76, 77, 78, 79, 80, 81, 82, 84, 86, 87, 88, 89, 90, 92, 93, 94, 95, 99, 100, 102, 103, 104, 106, 107, 109, 110, 111, 112, 113, 116, 117, 118, 119, 120, 121, 123, 124], "truncat": [1, 40, 109, 123], "trunk": [26, 46, 53], "trust": [20, 23], "try": [1, 3, 4, 6, 8, 9, 10, 13, 16, 20, 26, 34, 35, 38, 53, 57, 60, 62, 64, 69, 70, 72, 74, 79, 80, 84, 87, 90, 92, 94, 99, 100, 102, 109, 116, 117, 118, 121, 123], "try_branch": 26, "tstat": 15, "ttime": 109, "tue": 59, "tuesdai": 59, "tupl": [1, 11, 15, 69, 70, 72, 74, 77, 80, 84, 87, 88, 89, 90, 92, 94, 99, 100, 102, 103, 106, 107, 108, 109, 110, 116, 118, 119, 122, 123], "turbo": 109, "turn": [2, 6, 7, 9, 22, 36, 43, 77], "tutori": [1, 19, 23, 50, 56], "tvec": 113, "twice": 105, "twin": 40, "twine": 26, "two": [2, 4, 6, 7, 9, 12, 20, 26, 28, 31, 35, 36, 38, 39, 40, 54, 62, 64, 74, 77, 80, 94, 102, 103, 109, 110, 113, 114, 122, 123, 124], "txt": [26, 31, 79, 120], "ty": [74, 80, 89, 99, 100, 107, 109, 116, 123], "type": [1, 3, 7, 8, 10, 11, 12, 13, 14, 15, 16, 21, 29, 30, 33, 35, 38, 39, 41, 43, 52, 55, 57, 61, 62, 64, 65, 67, 69, 70, 71, 72, 74, 76, 77, 78, 80, 81, 82, 84, 87, 89, 92, 94, 95, 98, 99, 100, 102, 103, 104, 106, 110, 113, 116, 117, 118, 119, 123, 124], "type_info": 65, "typeerror": [1, 72, 87, 116, 123], "typeofpatientid": 35, "types_ext": 89, "types_filenam": 65, "types_fnam": 89, "typesfilenameserror": 65, "typic": [7, 10, 12, 15, 28, 68, 77, 89, 102, 107, 123], "typo": [1, 23, 43], "u": [1, 2, 4, 8, 9, 10, 12, 20, 25, 26, 35, 36, 38, 43, 49, 51, 52, 55, 61, 70, 71, 84, 86, 87, 99, 102, 108, 109], "u1": [97, 103, 104], "u2": 84, "u4": [84, 123], "ubb": 18, "ubuntu": [47, 59, 79], "ufunc": [1, 42], "ugli": 8, "ui": 35, "uid": 33, "uint": [76, 123], "uint32": 39, "uint8": [1, 30, 39, 41, 69, 71, 76, 103], "uk": 9, "ul": 35, "ulp": 65, "ulp_val": 76, "ultim": 6, "ulvers": 10, "umass_anonym": 58, "umassm": 58, "un": 35, "unabl": 62, "unalt": 90, "unanim": 23, "unassign": 35, "unboundlocalerror": 1, "uncach": [7, 14, 59, 65, 80], "uncertain": 94, "uncertainti": 27, "unchang": [55, 61, 80, 103, 116], "unclos": 1, "uncomfort": 20, "uncompress": [1, 20, 106], "undefin": [35, 62, 71, 76, 123], "under": [1, 4, 56, 58, 66, 109], "underli": [2, 7, 8, 12, 14, 90, 93, 102, 111, 123, 124], "underlin": [73, 114], "understand": [9, 23, 30, 34, 35, 40, 102], "understood": 19, "undocu": 1, "unexpect": 1, "unfortun": [1, 2, 12, 84], "unicod": [1, 77], "unicodedecodeerror": 103, "unidesign": 58, "unifi": 1, "uniformli": 22, "uniformtimeseri": 105, "unimport": 10, "uninitialis": 62, "uninterest": 4, "union": [9, 35], "uniq": 26, "uniqu": [9, 20, 33, 35, 51, 77, 109], "unit": [1, 2, 9, 12, 37, 38, 40, 42, 60, 65, 68, 76, 77, 102, 103, 113], "unit_gradi": 102, "unittest": 1, "univers": [2, 58], "unix": [79, 85], "unknown": [1, 9, 35, 38, 41, 61, 62, 94, 103, 119, 124], "unless": [1, 4, 9, 14, 19, 20, 23, 26, 64, 74, 96, 102, 118], "unlik": [6, 9, 14, 23, 62, 111], "unlimit": 35, "unload": 1, "unmodifi": [1, 3, 52, 93, 123], "unnecessari": [1, 103], "unnecessarili": 1, "unoffici": 22, "unord": 106, "unpack": [10, 20, 36, 57, 60, 65], "unproxi": [7, 8], "unproxy_if_this_is_a_proxy_do_nothing_otherwis": 7, "unreach": 1, "unread": 1, "unrel": 9, "unreserv": 35, "unscal": [1, 36, 69, 116, 117, 123], "unscaled_data": 116, "unset": 118, "unsign": [1, 35, 40, 76, 94, 109, 111], "unspecifi": [33, 35, 36, 76, 103], "unstabl": [20, 86], "unsupport": [83, 116], "until": [1, 8, 26, 35, 55, 80, 82, 90, 103], "untouch": 119, "untrack": 43, "untrigg": 105, "unus": [35, 39, 61, 66, 84], "unused1": [39, 69, 117, 118], "unused2": 39, "unused_str": [62, 104], "unusu": [1, 6], "unwant": 43, "up": [1, 2, 3, 4, 6, 8, 9, 12, 13, 14, 16, 19, 20, 22, 23, 26, 30, 35, 39, 42, 46, 49, 50, 52, 57, 61, 62, 69, 76, 77, 82, 86, 90, 94, 103, 122, 123], "upcast": [29, 123], "upcom": [7, 26], "updat": [1, 4, 5, 6, 11, 19, 22, 26, 42, 46, 50, 57, 68, 102, 103, 104, 116, 119], "update_affin": 103, "update_cach": 65, "update_head": [65, 77, 103, 116], "updateqformfromquarternion": 1, "upgrad": 1, "upk": 102, "upload": [1, 26], "upon": [1, 120], "upper": [38, 123], "upr_true_thr": 84, "upstream": [1, 26, 42, 43, 49, 51, 52], "urgent": 12, "uri": [9, 20], "url": [1, 6, 9, 16, 20, 43, 53, 63, 87], "urllib": 87, "us": [1, 3, 6, 8, 10, 11, 14, 16, 19, 20, 21, 22, 23, 25, 26, 27, 28, 31, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 47, 49, 53, 54, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 68, 69, 70, 71, 74, 75, 76, 77, 78, 79, 80, 81, 84, 85, 86, 87, 88, 90, 91, 92, 94, 96, 98, 99, 100, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126], "usabl": [1, 22], "usag": [1, 6, 12, 35, 56], "user": [1, 3, 4, 6, 7, 9, 10, 11, 12, 14, 16, 19, 20, 22, 23, 26, 27, 35, 43, 49, 52, 53, 56, 57, 64, 72, 75, 77, 79, 85, 92, 103, 105], "user_process_cod": 84, "usernam": 26, "userwarn": [1, 81], "usr": 79, "usual": [2, 3, 10, 12, 26, 34, 35, 36, 43, 55, 61, 68, 77, 80, 87, 102, 107, 108, 109, 111, 116, 118], "ut": 35, "utf": [6, 94, 103, 125], "util": [1, 12, 65, 68, 73, 76, 79, 90, 91, 92, 98, 101, 105, 108, 109, 114, 122, 123], "v": [1, 2, 9, 13, 30, 42, 53, 84, 86, 97, 109, 113, 122, 123, 126], "v0": 68, "v1_ax1": 68, "v2": [1, 47, 61, 62, 86], "v_i": 9, "val": [30, 75, 76], "val1": 102, "val2": 102, "val_fmt": 114, "valentin": 56, "valid": [1, 9, 10, 19, 22, 35, 40, 59, 62, 69, 72, 74, 77, 79, 87, 92, 102, 103, 108, 113, 116, 117, 118, 123], "valid_ext": [65, 69, 74, 77, 84, 87, 92, 94, 99, 103, 106, 109], "valu": [1, 2, 4, 6, 9, 10, 11, 12, 14, 15, 18, 19, 22, 23, 29, 30, 31, 36, 37, 38, 39, 40, 41, 55, 60, 61, 62, 65, 66, 68, 69, 70, 71, 72, 74, 75, 76, 77, 78, 79, 80, 84, 87, 89, 90, 92, 94, 97, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 111, 113, 114, 115, 116, 117, 118, 119, 123, 124, 126], "value_set": [65, 123], "valueerror": [1, 14, 68, 77, 92, 93, 101, 103, 109, 119, 124], "van": [1, 56, 58], "vanish": 1, "var": [26, 100], "vari": [3, 9, 34, 109, 119], "variabl": [1, 5, 19, 35, 39, 40, 70, 79, 84, 85, 100, 106, 119, 124], "varianc": 1, "variant": [1, 70, 103, 104, 106, 117, 118], "variat": 10, "varieti": 102, "variou": [1, 10, 22, 26, 30, 34, 38, 41, 62, 75, 76, 79, 94, 103, 106, 124], "varoquaux": [1, 56], "vast": 10, "vc": [1, 22, 30], "vdash": [86, 113], "ve": [7, 8, 9, 12, 33, 34, 35, 43, 51, 52, 53, 60], "vec": [86, 113], "vecs2": 86, "vector": [1, 2, 9, 15, 18, 30, 36, 37, 38, 40, 68, 77, 86, 92, 102, 103, 104, 109, 111, 113, 118, 123], "vega": 1, "vendor": [28, 65], "vendor_from_priv": 65, "venki": [1, 56], "verbos": [65, 66, 77, 94, 125], "verbose_level": 78, "verg": 6, "veri": [1, 4, 6, 8, 10, 14, 20, 26, 35, 38, 42, 45, 60, 72, 77, 84, 103, 109, 113], "verif": [20, 35], "verifi": [20, 69, 117, 118, 119], "version": [1, 3, 6, 7, 8, 12, 14, 22, 24, 26, 31, 36, 38, 39, 40, 43, 56, 58, 59, 61, 66, 71, 76, 77, 79, 80, 81, 82, 90, 92, 93, 94, 98, 107, 108, 109, 112, 117, 118, 120, 122, 124], "version_compar": 82, "version_str": 82, "versioneddatasourc": 65, "versu": 12, "vertex": [1, 15, 77, 92, 94], "vertex_indic": [65, 77], "vertexindic": 77, "vertic": [1, 15, 18, 38, 77, 92, 103, 110], "vet": 1, "via": [1, 3, 4, 6, 23, 34, 41, 43, 56, 57, 61, 66, 69, 88, 90, 102, 109, 116, 117, 118, 119, 124], "viabl": 79, "victorovich": 1, "video": 49, "view": [1, 2, 6, 12, 26, 60, 63, 69, 109, 117, 118, 119, 122], "viewdoc": 86, "viewer": [0, 1, 60, 63, 65, 116], "vim": 42, "vincent": [1, 56], "virtualenv": [1, 26, 57], "visibl": 19, "visibledeprecationwarn": 65, "visit": [1, 35], "vista": 13, "visual": [28, 43], "viviani": 7, "vm": [35, 39], "vmp": 18, "vmr": 18, "vo": [1, 56], "voi": 35, "voic": 23, "void": [69, 72, 103], "vol": 30, "vol0": [12, 61], "vol1": [12, 55], "vol_is_ful": 65, "vol_no": 109, "vol_numb": 65, "volext": 1, "vols_ad": [69, 117, 118], "volum": [1, 2, 13, 18, 30, 36, 38, 55, 61, 65, 74, 77, 78, 83, 84, 87, 92, 97, 102, 103, 109, 111, 115, 122], "volume_dimens": 77, "volume_info": 92, "volume_mask": [65, 77], "volume_parcel": 77, "volume_shap": [65, 77], "volumedimens": 77, "volumeerror": 65, "volumetr": [15, 77, 116], "volumeutil": [0, 1, 65, 124], "volunt": 19, "vote": 23, "vouch": 19, "vox2out_vox": 65, "vox2ra": 92, "vox2ras_tkr": 1, "vox_offset": [61, 62, 69, 103, 104, 117, 118], "vox_siz": 68, "vox_unit": [69, 117, 118], "vox_z": 30, "voxel": [1, 12, 30, 32, 33, 36, 59, 61, 62, 68, 69, 74, 77, 78, 84, 92, 97, 99, 100, 102, 103, 104, 108, 109, 110, 111, 115, 116, 117, 118, 119, 123, 126], "voxel_arrai": 36, "voxel_data": 54, "voxel_indices_ijk": [65, 77], "voxel_ord": [1, 11, 65, 119], "voxel_s": [11, 65, 102, 111, 115, 119], "voxel_to_rasmm": [65, 119], "voxelindicesijk": 77, "voxelmm": 119, "voxels": 92, "voxmm": 119, "vr": [9, 35, 39, 102], "vtc": [12, 18], "vtk": 11, "vtx_idc": 15, "vx2": 1, "vx2q": 1, "w": [1, 22, 74, 86, 106, 113], "w2": 113, "w2_thresh": 113, "wa": [1, 2, 6, 7, 10, 19, 20, 26, 27, 28, 31, 35, 38, 40, 43, 58, 61, 72, 77, 78, 84, 86, 94, 106, 107, 118, 119], "wado": 35, "wael": [1, 56], "wai": [1, 2, 3, 4, 6, 7, 9, 10, 11, 13, 16, 18, 20, 23, 26, 28, 35, 36, 38, 40, 42, 43, 48, 49, 51, 52, 55, 56, 57, 58, 59, 60, 62, 65, 69, 76, 87, 90, 102, 105, 123], "wait": [55, 87], "wall": 15, "waller": [1, 56], "walt": 58, "wang": [1, 56], "want": [2, 3, 4, 8, 9, 10, 12, 13, 14, 20, 26, 27, 28, 30, 35, 36, 38, 39, 40, 42, 44, 46, 52, 53, 54, 55, 56, 57, 61, 62, 74, 77, 80, 81, 90, 105, 109, 116, 120, 123, 124], "warehous": 26, "wari": 19, "warmli": 52, "warn": [1, 7, 14, 40, 79, 81, 82, 102, 109, 119], "warn_class": 82, "warn_messag": [65, 81], "warning_class": [81, 82], "warning_rec": 81, "warrant": 110, "warranti": 58, "wassermann": [1, 56], "water": [101, 109], "water_fat_shift": 101, "waveform": 35, "wb": [15, 87, 92, 106], "wbspec": 15, "wc": 30, "wdiff": 42, "we": [1, 2, 3, 4, 6, 7, 8, 9, 10, 12, 13, 14, 15, 19, 20, 22, 23, 26, 27, 28, 29, 30, 33, 34, 35, 36, 38, 39, 40, 41, 43, 45, 52, 53, 54, 55, 56, 57, 60, 61, 62, 64, 66, 68, 69, 71, 72, 76, 77, 78, 79, 80, 81, 84, 85, 86, 87, 89, 90, 91, 92, 93, 94, 99, 100, 102, 103, 104, 105, 106, 107, 108, 109, 112, 113, 115, 117, 118, 119, 120, 121, 123, 124], "web": [6, 9, 19, 26, 35, 38], "websit": [9, 22, 23, 56, 57, 86], "webster": 42, "wed": 59, "wednesdai": 59, "week": [23, 42], "weekli": 1, "weight": 15, "welcom": [9, 19], "well": [1, 3, 4, 6, 8, 9, 12, 14, 15, 16, 20, 22, 34, 41, 42, 43, 48, 68, 85, 90, 103], "well_counter_corr_factor": 84, "wellcom": 30, "went": 43, "were": [1, 6, 12, 16, 22, 40, 60, 64, 77, 78, 87, 89, 94, 102, 103, 105, 109], "westin": 102, "what": [2, 4, 7, 9, 10, 11, 16, 19, 20, 22, 29, 35, 36, 38, 40, 43, 53, 54, 56, 57, 60, 61, 62, 69, 72, 74, 77, 78, 80, 84, 90, 92, 96, 102, 107, 111, 112, 115, 120, 124], "whatev": 39, "whatsnew": 81, "wheel": 1, "when": [1, 2, 3, 4, 6, 8, 9, 11, 12, 13, 14, 15, 19, 20, 22, 23, 26, 27, 29, 30, 35, 36, 37, 40, 41, 42, 43, 52, 55, 56, 61, 62, 66, 68, 69, 70, 71, 74, 76, 77, 78, 79, 80, 81, 82, 86, 87, 89, 90, 94, 99, 100, 102, 103, 105, 106, 107, 108, 109, 111, 116, 119, 120, 123], "whenc": 106, "whenev": [7, 92, 109, 119], "where": [1, 2, 6, 7, 9, 12, 13, 14, 15, 16, 18, 19, 20, 25, 26, 27, 29, 30, 35, 36, 38, 40, 43, 49, 52, 58, 60, 61, 64, 68, 69, 70, 72, 74, 76, 77, 78, 79, 80, 81, 84, 86, 87, 89, 90, 91, 92, 93, 102, 103, 106, 108, 109, 111, 113, 115, 116, 119, 122, 123], "wherea": [20, 36, 40, 62, 68, 119, 124], "wherebi": 124, "wherev": [6, 20], "whether": [1, 2, 3, 6, 7, 8, 12, 19, 20, 21, 25, 26, 34, 35, 40, 58, 69, 70, 71, 74, 76, 77, 79, 80, 84, 86, 87, 90, 92, 99, 100, 103, 106, 110, 112, 116, 117, 118, 119, 123, 124], "which": [1, 2, 3, 4, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19, 20, 23, 27, 29, 30, 33, 35, 36, 40, 42, 43, 60, 61, 62, 64, 69, 70, 71, 72, 74, 76, 77, 78, 79, 80, 81, 84, 86, 87, 88, 89, 90, 92, 94, 96, 98, 102, 103, 104, 105, 106, 108, 109, 111, 113, 114, 115, 116, 117, 118, 119, 120, 122, 123, 124], "which_analyze_typ": 1, "while": [10, 15, 19, 22, 23, 26, 40, 41, 43, 61, 77, 83, 91, 103, 109, 120], "white": [2, 15, 103], "whitespac": 77, "who": [4, 6, 16, 23, 26, 41, 42, 51, 52, 56, 105], "whole": [1, 7, 8, 9, 11, 12, 35, 38, 39, 55, 86, 90, 99, 100], "whole_aff": 115, "whole_affin": 2, "whom": 58, "whose": [1, 15, 28, 94], "why": [2, 9, 16, 43, 52, 61, 76], "wide": [1, 10, 100], "wider": 6, "width": [12, 76, 78, 104], "wiki": [9, 20, 31, 76, 86, 113], "wikipedia": [2, 38, 76, 86, 113], "wild": [9, 84], "wildli": 15, "win32": 26, "window": [1, 26, 47, 57, 76, 85, 109], "wip": 6, "wipe": 120, "wish": [1, 13, 35, 62], "withdrawn": [6, 16], "within": [2, 6, 9, 20, 22, 34, 35, 39, 58, 69, 71, 76, 77, 86, 90, 94, 102], "without": [1, 2, 4, 6, 7, 9, 10, 11, 15, 16, 19, 20, 23, 26, 35, 41, 43, 58, 61, 69, 70, 72, 76, 79, 86, 89, 102, 103, 112, 116, 117, 119], "wobble_spe": 84, "wolfram": [86, 113], "won": [7, 20, 33], "word": [2, 7, 22, 35, 42, 43, 90], "work": [1, 2, 3, 4, 9, 10, 13, 14, 15, 19, 20, 21, 22, 23, 26, 27, 28, 30, 31, 32, 40, 41, 43, 48, 49, 52, 55, 56, 57, 58, 59, 60, 61, 66, 68, 69, 76, 77, 87, 90, 102, 112, 115, 116, 118, 120], "work_list": 40, "workaround": 1, "workbench": 28, "worker": 27, "workflow": [1, 19, 22, 23, 46, 48, 50, 52], "working_typ": 65, "workon": 26, "world": [2, 3, 20, 29, 30, 41, 54, 61, 62, 68, 69, 74, 84, 92, 99, 100, 102, 103, 104, 109, 110, 111, 116, 117, 118, 119, 126], "worri": [7, 43, 102], "worth": [8, 19, 35, 40], "would": [1, 2, 4, 7, 8, 9, 10, 11, 12, 13, 14, 16, 20, 23, 27, 29, 30, 35, 37, 43, 60, 64, 69, 78, 80, 81, 87, 90, 94, 102, 103, 105, 107, 108, 109, 116, 119, 123, 124], "wouldn": 16, "wpic": 9, "wrap": [1, 10, 34, 99, 100, 102, 111, 123, 124], "wrapper": [1, 10, 65, 103, 124, 125], "wrapper_from_data": 65, "wrapper_from_fil": 65, "wrappererror": 65, "wrapperprecisionerror": 65, "wrapstruct": [0, 1, 65, 69, 84, 103], "wrapstructerror": 65, "writabl": [1, 87], "write": [1, 2, 4, 6, 9, 10, 11, 14, 18, 20, 22, 25, 29, 30, 35, 36, 37, 42, 43, 45, 51, 52, 53, 56, 64, 65, 66, 69, 71, 72, 77, 84, 87, 88, 91, 92, 94, 103, 104, 106, 117, 118, 119, 120, 123, 124], "write0": 123, "write_annot": 65, "write_curv": 92, "write_geometri": [15, 65], "write_morph_data": [15, 65], "write_rais": [65, 72], "write_slic": 64, "write_text": 120, "write_to": [65, 87, 103, 124], "write_zero": 65, "writeabl": 90, "writeablebuff": 106, "writeftr_to": [65, 92], "writehdr_to": [65, 92], "writer": [9, 71, 106], "writererror": 65, "written": [1, 2, 9, 12, 16, 23, 29, 31, 34, 35, 37, 40, 43, 58, 62, 69, 71, 74, 87, 92, 94, 99, 100, 103, 104, 106, 109, 116, 117, 118, 122], "wrong": [1, 43, 89], "wstr": [92, 103, 124], "wstr1": 124, "wstr2": 124, "wt": 120, "wtype": 123, "www": [1, 9, 18, 20, 31, 34, 38, 77, 79, 86, 92, 94, 104, 119], "wxyz": 113, "x": [1, 2, 6, 9, 12, 15, 18, 20, 23, 26, 30, 36, 38, 40, 47, 61, 69, 76, 77, 81, 86, 92, 94, 102, 105, 106, 109, 110, 113, 115, 116, 122, 123], "x00": [61, 62], "x00x00": 40, "x64": [1, 22], "x86": 22, "x_": 38, "x_flip": 123, "x_x": 38, "x_y": 38, "x_z": 38, "xa30": 1, "xarrai": 28, "xb": 106, "xcede": 9, "xdist": 1, "xform": [1, 62, 68, 94], "xformspac": 94, "xmedcon": 84, "xml": [1, 9, 75, 77, 87, 94, 109, 125], "xmlbasedhead": 65, "xmlparser": [65, 77, 94], "xmlserializ": [65, 75, 77, 94], "xmlutil": [0, 65], "xr": 31, "xra": 92, "xrai": 12, "xrang": 10, "xred": 86, "xrot": 86, "xspace": 12, "xx": [35, 39], "xx00": 35, "xxff": 35, "xyz": [2, 18, 38, 77, 86, 103, 113], "xyz_unit": 1, "xyzt_unit": [1, 12, 41, 61, 62, 103, 104], "y": [2, 9, 12, 18, 30, 38, 40, 55, 69, 77, 86, 92, 94, 102, 105, 109, 110, 113, 116, 122], "y_": 38, "y_highres001": 12, "y_x": 38, "y_y": 38, "y_z": 38, "yannick": [1, 56], "yarik": 1, "yarikopt": [4, 23, 58], "yaroslav": [1, 23, 56, 58], "yaw": 86, "ye": 109, "year": [14, 26, 28], "yellow": [2, 42], "yet": [2, 7, 14, 20, 26, 31, 34, 61, 80, 81, 102, 109], "yet_another_imag": 30, "yield": [77, 119], "yml": 26, "yoh": 1, "you": [1, 2, 3, 4, 6, 8, 9, 10, 13, 14, 20, 22, 26, 27, 30, 33, 34, 35, 37, 38, 41, 42, 44, 45, 46, 48, 49, 51, 52, 53, 54, 55, 56, 57, 61, 62, 63, 64, 69, 70, 71, 74, 76, 77, 80, 81, 84, 86, 87, 100, 102, 103, 104, 105, 106, 108, 109, 114, 115, 116, 117, 118, 119, 120, 123, 124], "your": [4, 6, 9, 14, 19, 20, 22, 25, 26, 42, 46, 49, 50, 51, 52, 59, 69, 79, 86, 94, 119], "yourdomain": [42, 52], "yourself": [4, 19, 30, 43, 45, 53, 62], "yra": 92, "yred": 86, "yrot": 86, "yspace": 12, "yum": 47, "yuri": [1, 42], "yyyi": [6, 16], "z": [2, 12, 18, 30, 32, 36, 40, 68, 69, 77, 86, 92, 94, 102, 103, 109, 110, 113, 116, 122], "z_dir_co": 40, "z_same_indic": 40, "zaytsev": 42, "zen": 3, "zenodo": [1, 26, 56, 66], "zero": [1, 2, 4, 9, 36, 38, 40, 62, 64, 68, 69, 71, 76, 77, 84, 86, 91, 97, 102, 103, 108, 111, 113, 116, 123], "zibi": 26, "zip": [1, 20, 26], "zipfil": 20, "zlib": [4, 20], "znzlib": 1, "zoom": [1, 2, 68, 69, 84, 92, 99, 100, 103, 104, 116, 123], "zra": 92, "zred": 86, "zrot": 86, "zsind": 40, "zspace": 12, "zst": [73, 89, 106], "zstd": 1, "zstd_def": [65, 106], "zstd_dict": 106, "zvi": [1, 56], "\u00e9tienn": 1}, "titles": ["API Documentation", "NiBabel Development Changelog", "Coordinate systems and affines", "How to add a new image format to nibabel", "Adding test data", "Advanced Testing", "BIAP 0 - Purpose and process", "BIAP1 - Towards immutable images", "BIAP2 - Slicecopy", "BIAP3 - A JSON nifti header extension", "BIAP4 - Merging nibabel and dcmstack", "BIAP5 - A streamlines converter", "BIAP6 - Identifying image axes", "BIAP7 - Loading multiple images", "BIAP8 - Always load image data as floating point", "BIAP9 - The Coordinate Image API", "BIAP X \u2014 Template and Instructions", "BIAPs", "BrainVoyager file formats", "Core Developer Guide", "Principles of data package", "Developer discussions", "NiBabel Developer Guidelines", "Governance and Decision Making", "The nibabel image object", "Developer documentation page", "A guide to making a nibabel release", "Keeping track of whether images have been modified since load", "Roadmap", "Scalefactors and intercepts", "Image use-cases in SPM", "dcm2nii algorithms", "DICOM concepts and implementations", "DICOM fields", "DICOM information", "Introduction to DICOM", "Siemens mosaic format", "DICOM Tags in the NIfTI Header", "Defining the DICOM orientation", "Siemens format DICOM with CSA header", "SPM DICOM conversion", "Getting Started", "Configure git", "Development workflow", "Following the latest source", "Making your own copy (fork) of nibabel", "Git for development", "Install git", "Introduction", "git resources", "Working with nibabel source code", "Maintainer workflow", "Making a patch", "Set up your fork", "Image voxel orientation", "Images and memory", "NiBabel", "Installation", "Copyright and Licenses", "NiBabel Manual", "Radiological vs neurological conventions", "Nibabel images", "Working with NIfTI images", "IPython notebooks for Nibabel project", "Relationship between images and io implementations", "API Reference", "nibabel", "_compression", "affines", "analyze", "arrayproxy", "arraywriters", "batteryrunners", "benchmarks", "brikhead", "caret", "casting", "cifti2", "cmdline", "data", "dataobj_images", "deprecated", "deprecator", "dft", "ecat", "environment", "eulerangles", "filebasedimages", "fileholders", "filename_parser", "fileslice", "fileutils", "freesurfer", "funcs", "gifti", "imageclasses", "imageglobals", "imagestats", "loadsave", "minc1", "minc2", "mriutils", "nicom", "nifti1", "nifti2", "onetime", "openers", "optpkg", "orientations", "parrec", "pointset", "processing", "pydicom_compat", "quaternions", "rstutils", "spaces", "spatialimages", "spm2analyze", "spm99analyze", "streamlines", "tmpdirs", "tripwire", "viewers", "volumeutils", "wrapstruct", "xmlutils", "General tutorials"], "titleterms": {"0": [1, 6], "1": [1, 8], "11": 1, "12": 1, "13": 1, "14": 1, "15": 1, "16": 1, "17": 1, "18": 1, "2": [1, 8, 15, 77], "20": 1, "2006": 1, "20061114": 1, "2007": 1, "20070214": 1, "20070220": 1, "20070301": 1, "20070315": 1, "20070425": 1, "20070803": 1, "20070905": 1, "20070917": 1, "20070930": 1, "2008": 1, "20080624": 1, "20080630": 1, "20080710": 1, "20081017": 1, "2009": 1, "20090205": 1, "20090303": 1, "2010": 1, "20100412": 1, "20100706": 1, "2011": 1, "2012": 1, "2014": 1, "2015": 1, "2016": 1, "2017": 1, "2018": 1, "2019": 1, "2020": 1, "2022": 1, "2023": 1, "2024": 1, "22": 1, "23": 1, "24": 1, "25": 1, "26": 1, "27": 1, "28": 1, "3": 1, "30": 1, "31": 1, "3d": [10, 13, 38], "3rd": 58, "4": 1, "4d": [10, 13], "4th": 12, "5": 1, "6": 1, "7": 1, "8": 1, "9": 1, "A": [3, 9, 11, 19, 26, 35, 51], "And": 23, "If": 4, "In": [14, 42, 43, 47, 52, 53], "It": 13, "The": [2, 3, 9, 12, 15, 23, 24, 35, 43, 61, 62, 69], "There": 116, "_compress": 67, "able_int_typ": 76, "about": 72, "abstract": [9, 16, 23], "abstractclassmethod": 119, "accept": 6, "access": 10, "account": 45, "acknowledg": [19, 23], "acquisition_tim": 9, "ad": 4, "adapt_affin": 111, "add": [3, 13, 14], "advanc": [5, 49], "aff2axcod": 108, "affin": [2, 38, 40, 62, 68], "affineerror": 68, "afniarrayproxi": 74, "afnihead": 74, "afniheadererror": 74, "afniimag": 74, "afniimageerror": 74, "again": 38, "alert_future_error": 81, "algorithm": 31, "alias": 42, "align": 60, "allow": 8, "alphabet": 0, "also": 9, "altern": [9, 16], "alwai": [2, 14], "an": [2, 13, 15], "analyz": 69, "analyzehead": 69, "analyzeimag": 69, "angle_axis2eul": 86, "angle_axis2mat": 113, "angle_axis2quat": 113, "ap": 78, "api": [0, 1, 3, 15, 28, 65], "append_diag": 68, "appli": [2, 9], "apply_affin": 68, "apply_orient": 108, "apply_read_sc": 123, "apr": 1, "april": 1, "ar": [2, 116], "are_values_differ": 78, "arrai": [2, 7, 55, 61], "array_from_fil": 123, "array_sequ": 119, "array_to_fil": 123, "arraylik": 70, "arrayproxi": 70, "arraysequ": 119, "arraywrit": 71, "as_closest_canon": 93, "as_int": 76, "ascconv": 102, "ascconvparseerror": 102, "ask": 43, "assert": 7, "assign2atom": 102, "associ": 12, "atom": 102, "attribut": 35, "aug": 1, "august": 1, "authent": 20, "author": 56, "autosummari": 58, "ax": [2, 12, 18, 60, 77], "axcodes2ornt": 108, "axi": [9, 12, 28, 77], "axis_mean": 9, "b2q": 102, "back": 62, "background": [7, 8, 9, 12, 13, 14, 15, 28, 35], "backward": 16, "batteryrunn": 72, "becom": 6, "been": 27, "bench": 66, "bench_array_to_fil": 73, "bench_arrayproxy_sl": 73, "bench_fileslic": 73, "bench_finite_rang": 73, "bench_load_sav": 73, "benchmark": 73, "best_float": 76, "best_write_scale_ftyp": 123, "better_float_of": 123, "between": [2, 64], "biap": [6, 16, 17, 23], "biap1": 7, "biap2": 8, "biap3": 9, "biap4": 10, "biap5": 11, "biap6": 12, "biap7": 13, "biap8": 14, "biap9": 15, "bomber": 79, "bombererror": 79, "brainmodelaxi": 77, "brainvoyag": 18, "branch": 43, "brikhead": 74, "bug": 1, "bundl": 20, "butil": 73, "bv": 18, "cach": [55, 61], "cachingerror": 83, "calc_slicedef": 90, "calculate_dwell_tim": 101, "call": 8, "can": [3, 10, 12, 13], "canonical_slic": 90, "caret": 75, "caretmetadata": 75, "case": [7, 9, 15, 30], "cast": 76, "castingerror": 76, "categor": 40, "ceil_exact": 76, "chang": [1, 19, 43, 51], "changelog": [1, 22], "check": [40, 51, 55, 72, 124], "checklist": 26, "choos": 62, "cifti": [15, 77], "cifti2": 77, "cifti2_ax": 77, "cifti2brainmodel": 77, "cifti2extens": 77, "cifti2head": 77, "cifti2headererror": 77, "cifti2imag": 77, "cifti2label": 77, "cifti2labelt": 77, "cifti2matrix": 77, "cifti2matrixindicesmap": 77, "cifti2metadata": 77, "cifti2namedmap": 77, "cifti2parcel": 77, "cifti2pars": 77, "cifti2surfac": 77, "cifti2transformationmatrixvoxelindicesijktoxyz": 77, "cifti2vertexindic": 77, "cifti2vertic": 77, "cifti2volum": 77, "cifti2voxelindicesijk": 77, "citat": [56, 66], "classifi": 10, "clear_cach": 83, "clone": 53, "close": 19, "cmdline": 78, "code": [3, 20, 22, 44, 50, 58, 62], "column": 38, "commit": [22, 43, 51], "commun": [22, 23], "compar": 20, "compat": 16, "compil": 31, "concat_imag": 93, "concaten": 119, "concept": 32, "conduct": 19, "configur": [42, 45], "conform": [78, 111], "conjug": 113, "consid": 43, "consist": 124, "contain": 9, "contributor": [23, 56], "convent": [12, 60], "convers": [0, 40], "convert": [11, 78], "coordin": [2, 15, 38], "coordinatearrai": 110, "copi": [7, 8, 44, 45], "copy_file_map": 88, "copyright": 58, "core": [19, 23], "council": 23, "count_nonzero_voxel": 97, "creat": [45, 77], "create_arraysequences_from_gener": 119, "csa": 39, "csa1": 39, "csa2": 39, "csaerror": 102, "csaread": 102, "csareaderror": 102, "current": [12, 14, 15], "data": [2, 4, 10, 14, 15, 20, 28, 35, 36, 38, 40, 58, 61, 62, 79, 109, 116], "dataerror": [79, 119], "dataobj_imag": 80, "dataobjimag": 80, "datasourc": 79, "datasource_or_bomb": 79, "datawarn": 119, "dcm2nii": 31, "dcmmetaextens": 10, "dcmstack": 10, "debian": [20, 57], "decemb": 1, "decis": 23, "decode_value_from_nam": 119, "default": 62, "defin": 38, "definit": [35, 38], "delet": 43, "depend": 20, "deprec": [1, 81, 82], "deriv": 38, "descript": 16, "desiderata": [15, 20], "design": 7, "detail": [14, 16, 42, 43, 47, 52, 53, 61], "detect": 10, "detect_format": 119, "deterministicgzipfil": 106, "develop": [1, 19, 21, 22, 23, 25, 43, 46, 52, 57], "dft": 83, "dfterror": 83, "dicom": [10, 32, 33, 34, 35, 36, 37, 38, 39, 40], "dicom_test": 112, "dicomf": 78, "dicomread": 102, "dicomreaderror": 102, "dicomwrapp": 102, "dictionari": 35, "diff": 78, "differ": [13, 20], "difficult": 10, "dims": 35, "discoveri": 20, "discuss": [16, 21], "displai": 60, "display_diff": 78, "distinguish": 12, "do": [7, 43], "document": [0, 1, 22, 25, 56], "dot_reduc": 68, "doubt": 4, "download": 56, "dtypemapp": 123, "dummy_fus": 78, "dwiparam": 102, "each": 39, "easi": 61, "ecat": 84, "ecathead": 84, "ecatimag": 84, "ecatimagearrayproxi": 84, "ecatsubhead": 84, "edit": 43, "editor": 42, "element": [9, 35], "email": 42, "encode_value_in_nam": 119, "enforc": 12, "enhanc": [1, 23], "entiti": 35, "environ": 85, "error": 78, "errorlevel": 96, "euler2angle_axi": 86, "euler2mat": 86, "euler2quat": 86, "eulerangl": 86, "exampl": [2, 62, 77], "expireddeprecationerror": 82, "explor": 43, "express": 28, "extens": [9, 58], "extensionwarn": 119, "exts2par": 109, "ey": 113, "fall": 62, "fanci": [8, 42], "featur": [1, 43], "feb": 1, "februari": 1, "few": 51, "field": [9, 33, 35, 119], "file": [0, 4, 13, 18, 33, 35, 40, 61, 109, 116], "filebasedhead": 87, "filebasedimag": 87, "filehandl": 78, "filehold": 88, "fileholdererror": 88, "fileish": 106, "filename_pars": 89, "fileslic": 90, "fileutil": 91, "fill_slic": 90, "fillposit": 113, "filterdwiiso": 102, "filtermultistack": 102, "final": 40, "find": 12, "find_data_dir": 79, "find_private_sect": 102, "finite_rang": 123, "first": 40, "fix": 1, "flip_axi": 108, "float": [0, 14], "float_to_int": 76, "floatingerror": 76, "floor_exact": 76, "floor_log2": 76, "fname_ext_ul_cas": 123, "follow": 44, "footnot": 6, "fork": [45, 53], "format": [0, 3, 6, 13, 15, 18, 20, 35, 36, 39, 69, 109], "formula": 38, "four": 12, "four_to_thre": 93, "fourth": 12, "frame": 33, "framefilt": 102, "freesurf": 92, "fri": 1, "fridai": 1, "from": [2, 9, 13, 15, 20, 36, 38, 43, 52], "from_index_map": 77, "from_matvec": 68, "func": 93, "function": [13, 30], "further": 19, "fuse": 78, "futur": 11, "futurewarningmixin": 81, "fwhm2sigma": 111, "gener": [9, 12, 15, 109, 126], "geometr": 15, "get": [36, 38, 41, 44, 57, 61], "get_acq_mat_txt": 102, "get_affine_from_refer": 119, "get_affine_rasmm_to_trackvi": 119, "get_affine_trackvis_to_rasmm": 119, "get_b_matrix": 102, "get_b_valu": 102, "get_csa_head": 102, "get_data_diff": 78, "get_data_hash_diff": 78, "get_data_path": 79, "get_fdata": [14, 55], "get_frame_ord": 84, "get_g_vector": 102, "get_headers_diff": 78, "get_home_dir": 85, "get_ice_dim": 102, "get_info": 66, "get_n_mosa": 102, "get_nipy_system_dir": 85, "get_nipy_user_dir": 85, "get_obj_dtyp": 70, "get_opt_pars": 78, "get_scalar": 102, "get_series_framenumb": 84, "get_slice_norm": 102, "get_slope_int": 71, "get_studi": 83, "get_vector": 102, "gifti": 94, "gifticoordsystem": 94, "giftidataarrai": 94, "giftiimag": 94, "giftiimagepars": 94, "giftilabel": 94, "giftilabelt": 94, "giftimetadata": 94, "giftinvpair": 94, "giftiparseerror": 94, "git": [22, 42, 46, 47, 49], "github": [43, 45], "give": 2, "go": 4, "good": 19, "govern": 23, "grid": 110, "gridindic": 110, "guessed_image_typ": 98, "guid": [19, 22, 26], "guidelin": 22, "hasdtyp": 116, "have": [12, 27], "have_binary128": 76, "hdf5bunch": 100, "header": [6, 9, 11, 37, 39, 61, 62, 69, 119], "headerdataerror": 116, "headererror": 119, "headertypeerror": 116, "headerwarn": 119, "help": 3, "helper": 0, "histori": [43, 51], "hook": 22, "how": [3, 4, 6, 19], "i": [6, 15, 35, 38], "id": 20, "idea": 20, "identifi": 12, "imag": [0, 2, 3, 7, 9, 10, 12, 13, 14, 15, 24, 27, 30, 33, 34, 54, 55, 61, 62, 64, 109], "imageclass": 95, "imagedataerror": 116, "imagefileerror": 87, "imageglob": 96, "imageopen": 106, "imagestat": 97, "immut": 7, "impact": 16, "implement": [9, 14, 16, 27, 32, 64], "improv": 10, "in_memori": 55, "independ": 10, "index": 57, "inform": [34, 35, 109], "ingivendirectori": 120, "instal": [20, 47, 56, 57, 66], "instancestackerror": 83, "instanti": 20, "instead": 55, "instruct": 16, "int_ab": 76, "int_scinter_ftyp": 123, "int_to_float": 76, "integ": 0, "integr": 51, "integra": 40, "intemporarydirectori": 120, "intercept": 29, "interfac": 116, "intern": 18, "introduc": 2, "introduct": [35, 48], "inv_ornt_aff": 108, "invers": [2, 113], "io": [64, 92], "io_orient": 108, "ipython": 63, "is_array_sequ": 119, "is_data_dict": 119, "is_fanc": 90, "is_lazy_dict": 119, "is_mosa": 102, "is_ndarray_of_int_or_bool": 119, "is_proxi": 70, "is_support": 119, "is_tripwir": 121, "issu": [7, 10, 11, 19, 20], "isunit": 113, "item": [39, 40], "j": 38, "januari": 1, "json": 9, "jul": 1, "jun": 1, "june": 1, "keep": [10, 27], "kept": 15, "keyword": 55, "l": 78, "label": [12, 28], "labelaxi": 77, "labeledwrapstruct": 124, "latest": 44, "layout": [22, 60], "lazydict": 119, "lazytractogram": 119, "learn": 9, "length": [35, 40], "level": 13, "licens": [4, 56, 58, 66], "limitednifti2head": 77, "link": 53, "list": [38, 56, 66], "load": [7, 13, 14, 27, 61, 98, 103, 104, 119], "load_multi": 13, "loadsav": 98, "local": 44, "log": 42, "loggingoutputsuppressor": 96, "long": [5, 51], "longdouble_lte_float64": 76, "longdouble_precision_improv": 76, "lossless_slic": 78, "m": 40, "mai": 1, "mail": [56, 66], "main": 78, "maintain": 51, "mainten": [1, 6], "make": [23, 26, 40, 43, 45, 52], "make_array_writ": 71, "make_datasourc": 79, "make_dt_cod": 123, "manag": 20, "manipul": 10, "manual": [49, 59], "map": [2, 38], "mapping": 124, "mar": 1, "march": 1, "mask_volum": 97, "master": 43, "mat": 40, "mat2eul": 86, "mat2quat": 113, "matrix": [2, 63], "maxim": 7, "memori": [7, 55], "merg": [10, 19, 22, 42, 43], "mess": 43, "messag": 35, "meta": 10, "metadata": [9, 10, 15, 20], "method": [8, 14, 30], "mgherror": 92, "mghformat": 92, "mghheader": 92, "mghimag": 92, "might": 43, "minc1": 99, "minc1fil": 99, "minc1head": 99, "minc1imag": 99, "minc2": 100, "minc2fil": 100, "minc2head": 100, "minc2imag": 100, "mincerror": 99, "minchead": 99, "mincimagearrayproxi": 99, "minim": 7, "mirror": 43, "miscellan": 0, "mni_icbm152_t1_tal_nlin_asym_09a": 58, "model": 15, "modif": 31, "modifi": [7, 27], "modul": [73, 77, 78, 92, 94, 102, 119], "moduleproxi": 81, "mon": 1, "mondai": 1, "more": [12, 43, 72], "mosaic": 36, "mosaic_to_nii": 102, "mosaicwrapp": 102, "motiv": [10, 11, 16, 20, 27], "move": 52, "mrierror": 101, "mriutil": 101, "much": 4, "mult": 113, "multi": 33, "multi_affin": 9, "multiframewrapp": 102, "multipl": [13, 35], "must": 9, "name": [2, 9, 20, 42], "nearest_pos_semi_def": 102, "nearly_equival": 113, "nest": 10, "netcdf": 58, "neurolog": 60, "new": [1, 3, 43, 77], "next": 13, "nibabel": [1, 2, 3, 4, 10, 12, 22, 24, 26, 45, 50, 56, 58, 59, 61, 63, 66], "nicom": 102, "nifti": [9, 10, 37, 62], "nifti1": 103, "nifti1dicomextens": 103, "nifti1extens": 103, "nifti1head": 103, "nifti1imag": 103, "nifti1pair": 103, "nifti1pairhead": 103, "nifti2": 104, "nifti2head": 104, "nifti2imag": 104, "nifti2pair": 104, "nifti2pairhead": 104, "nifti_dx": 78, "niftiextens": [10, 103], "none_or_clos": 102, "norm": 113, "note": [69, 74], "notebook": 63, "nov": 1, "novalu": 102, "novemb": 1, "nrrd": 9, "nt_str": 102, "obj_from_atom": 102, "object": [8, 24, 35, 61], "obliqu": 68, "oct": 1, "octob": 1, "off": 64, "often": 9, "ok_float": 76, "on_powerpc": 76, "one_lin": 109, "onetim": 105, "onli": [3, 19], "onlin": 49, "open": [4, 40, 106], "optimize_read_slic": 90, "optimize_slic": 90, "option": [8, 13], "optional_packag": 107, "optpkg": 107, "order": 33, "orderedset": 58, "orient": [36, 38, 54, 108, 109], "orientationerror": 108, "ornt2axcod": 108, "ornt_transform": 108, "orthogon": 63, "orthoslicer3d": 122, "other": [2, 15, 43], "out": 38, "output": [2, 42], "overview": [10, 11, 18, 42, 43, 47, 52, 53], "own": 45, "packag": [20, 57], "page": [25, 49], "pair": 35, "par": [58, 109], "parcelsaxi": 77, "parrec": 109, "parrec2nii": 78, "parrecarrayproxi": 109, "parrecerror": 109, "parrechead": 109, "parrecimag": 109, "parse_afni_head": 74, "parse_arg": 78, "parse_ascconv": 102, "parse_cifti2": 77, "parse_filenam": 89, "parse_gifti_fast": 94, "parse_par_head": 109, "parse_slic": 78, "parti": 58, "pass": 40, "patch": 52, "patient": 38, "peek_next": 119, "peopl": 43, "perarraydict": 119, "perarraysequencedict": 119, "philip": [40, 58], "philosophi": 3, "pinstanc": 20, "pip": 57, "pixel": 38, "plan": 10, "plot": 15, "point": [2, 14], "pointset": 110, "possibl": [12, 27, 40], "pre": 22, "preambl": 6, "predict_shap": 90, "prefer": 14, "preliminari": 62, "pretty_map": 123, "principl": [9, 20], "print_git_titl": 73, "privat": 35, "proc_fil": 78, "process": [6, 23, 111], "project": 63, "promin": 15, "propos": [7, 9, 14, 15, 23], "provid": 20, "proxi": [7, 55, 61], "prundl": 20, "pull": 19, "purpos": 6, "push": 51, "pydicom_compat": 112, "pynifti": 1, "python": 57, "q2bg": 102, "q_vector": 9, "qform": 62, "quat2angle_axi": 113, "quat2eul": 86, "quat2mat": 113, "quaternion": 113, "queri": 20, "question": [7, 8, 9], "quickstart": 66, "ra": 2, "radiolog": 60, "rang": 28, "read": [3, 40, 102], "read_annot": 92, "read_data_block": 94, "read_geometri": 92, "read_img_data": 98, "read_label": 92, "read_mlist": 84, "read_morph_data": 92, "read_mosaic_dir": 102, "read_mosaic_dwi_dir": 102, "read_seg": 90, "read_subhead": 84, "read_zt_byte_str": 91, "rebas": 43, "rec": 58, "rec2dict": 123, "recip": 3, "recod": 123, "recov": 43, "refer": [0, 2, 6, 65, 105], "relat": 16, "relationship": 64, "releas": [1, 20, 26], "relev": 15, "repo": 53, "report": 72, "repositori": [22, 43, 53], "represent": 35, "repris": 56, "request": 19, "requir": 57, "resample_from_to": 111, "resample_to_output": 111, "rescale_affin": 68, "resetmixin": 105, "reshape_dataobj": 70, "resolut": [6, 7], "resort": 40, "resourc": [19, 49], "respons": 23, "return": 13, "review": [3, 6, 19, 43], "revis": 20, "rewrit": 43, "roadmap": 28, "roi": 78, "role": 23, "rotat": 63, "rotate_vector": 113, "row": 38, "rst_tabl": 114, "rstutil": 114, "run": 5, "run_slic": 73, "safe_get": 78, "same": 13, "sampl": 34, "sanit": 78, "saturdai": 1, "save": [55, 61, 98, 103, 104, 119], "scalaraxi": 77, "scale": [36, 62], "scalefactor": 29, "scalingerror": 71, "scanner": 2, "scope": 16, "second": 40, "see": 9, "seek_tel": 123, "sep": 1, "separ": [15, 20], "septemb": 1, "sequenc": [10, 13, 40], "seri": [2, 51], "serializableimag": 87, "seriesaxi": 77, "servic": 35, "set": [35, 38, 45, 53], "setup": 5, "sever": [43, 116], "sform": 62, "shape": 13, "shape_zoom_affin": 123, "share": 43, "shared_rang": 76, "should": [4, 8], "siemen": [36, 39], "siemenswrapp": 102, "sigma2fwhm": 111, "sign": 64, "sinc": 27, "singl": [4, 43], "slice": [8, 9, 10, 31, 36, 38, 61], "slice0": 8, "slice2len": 90, "slice2outax": 90, "slice2volum": 115, "sliceabledatadict": 119, "slicecopi": 8, "slicers2seg": 90, "slices_to_seri": 102, "slopearraywrit": 71, "slopeinterarraywrit": 71, "small": 4, "smooth": 15, "smooth_imag": 111, "solut": 12, "some": [13, 20, 31, 43], "someon": 2, "sop": 35, "sort": [31, 40, 109], "sourc": [20, 22, 44, 50, 57], "space": [2, 38, 115], "spatial": 28, "spatial_axes_first": 95, "spatialfirstslic": 116, "spatialhead": 116, "spatialimag": 116, "spatialprotocol": 116, "specif": 10, "speed": 7, "sphinx": 58, "splitext_addext": 89, "spm": [30, 40], "spm2analyz": 117, "spm2analyzehead": 117, "spm2analyzeimag": 117, "spm99analyz": 118, "spm99analyzehead": 118, "spm99analyzeimag": 118, "spm_dicom_convert": 40, "spm_dicom_dict": 40, "spm_dicom_head": 40, "spmanalyzehead": 118, "sq": 40, "squeeze_imag": 93, "standard": 35, "start": [3, 39, 41], "stat": 78, "state": 55, "statu": [8, 10], "steer": 23, "step": 13, "store": 13, "streamlin": [11, 119], "strided_scalar": 90, "structread": 102, "structur": 35, "style": 22, "subject": 2, "submodul": 4, "subsampl": 15, "summari": [14, 20, 27, 43, 49, 64], "sun": 1, "sundai": 1, "support": [15, 56], "supported_np_typ": 116, "surfac": [15, 28], "system": [0, 2, 38], "table2str": 78, "tag": [20, 31, 35, 37, 39, 40], "tck": 119, "tck2trk": 78, "tckfile": 119, "templat": [6, 16], "temporarydirectori": 120, "terminologi": [15, 20], "test": [4, 5, 22, 56, 66], "than": 12, "thing": 43, "threshold_heurist": 90, "through": 10, "thu": 1, "thursdai": 1, "ti": 10, "tick": 28, "time": [12, 55], "tmpdir": 120, "to_head": 77, "to_matvec": 68, "top": 13, "toward": 7, "track": [10, 27], "tractogram": 119, "tractogram_fil": 119, "tractogramfil": 119, "tractogramitem": 119, "transform": [2, 28], "tripwir": 121, "tripwireerror": 121, "trk": 119, "trk2tck": 78, "trkfile": 119, "trunk": [43, 51], "tue": 1, "tuesdai": 1, "tutori": [49, 126], "type": [6, 9, 18, 40, 109], "type_info": 76, "types_filenam": 89, "typesfilenameserror": 89, "u": 3, "ubuntu": 57, "ulp": 76, "uncach": 55, "understand": 19, "unpack": 102, "up": [43, 45, 53], "updat": [43, 44], "update_cach": 83, "upstream": 53, "us": [2, 4, 7, 9, 12, 13, 15, 30, 55], "usag": 16, "usecas": 20, "user": 42, "usual": 9, "util": [0, 78, 94, 102, 119], "v": 60, "valid": [20, 57], "valu": 35, "vari": 10, "vendor": 102, "vendor_from_priv": 102, "verbos": 78, "version": [9, 20, 57], "versioneddatasourc": 79, "view": [7, 8], "viewer": 122, "visibledeprecationwarn": 81, "vol_is_ful": 109, "vol_numb": 109, "volum": [9, 10, 12, 31, 40], "volumeerror": 83, "volumeutil": 123, "vox2out_vox": 115, "voxel": [2, 38, 40, 54, 60], "voxel_s": 68, "vr": 40, "wai": [61, 116], "want": [7, 43], "wed": 1, "wednesdai": 1, "what": [6, 8], "when": [7, 10], "where": 3, "whether": 27, "work": [11, 16, 38, 50, 62], "workflow": [6, 43, 49, 51], "working_typ": 123, "world": [35, 60], "wrapper": 102, "wrapper_from_data": 102, "wrapper_from_fil": 102, "wrappererror": 102, "wrapperprecisionerror": 102, "wrapstruct": 124, "wrapstructerror": 124, "write": [3, 40, 116], "write_annot": 92, "write_geometri": 92, "write_morph_data": 92, "write_zero": 123, "writererror": 71, "x": 16, "xmlbasedhead": 125, "xmlparser": 125, "xmlserializ": 125, "xmlutil": 125, "you": [7, 19, 43], "your": [3, 43, 45, 53, 57], "z": 38}}) \ No newline at end of file diff --git a/tools/bisect_nose.py b/tools/bisect_nose.py deleted file mode 100755 index 7036e0b9cc..0000000000 --- a/tools/bisect_nose.py +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/env python -"""Utility for git-bisecting nose failures -""" -DESCRIP = 'Check nose output for given text, set sys exit for git bisect' -EPILOG = """ -Imagine you've just detected a nose test failure. The failure is in a -particular test or test module - here 'test_analyze.py'. The failure *is* in -git branch ``main-master`` but it *is not* in tag ``v1.6.1``. Then you can -bisect with something like:: - - git co main-master - git bisect start HEAD v1.6.1 -- - git bisect run /path/to/bisect_nose.py nibabel/tests/test_analyze.py:TestAnalyzeImage.test_str - -You might well want to test that:: - - nosetests nibabel/tests/test_analyze.py:TestAnalyzeImage.test_str - -works as you expect first. - -Let's say instead that you prefer to recognize the failure with an output -string. Maybe this is because there are lots of errors but you are only -interested in one of them, or because you are looking for a Segmentation fault -instead of a test failure. Then:: - - git co main-master - git bisect start HEAD v1.6.1 -- - git bisect run /path/to/bisect_nose.py --error-txt='HeaderDataError: data dtype "int64" not recognized' nibabel/tests/test_analyze.py - -where ``error-txt`` is in fact a regular expression. - -You will need 'argparse' installed somewhere. This is in the system libraries -for python 2.7 and python 3.2 onwards. - -We run the tests in a temporary directory, so the code you are testing must be -on the python path. -""" -import os -import re -import shutil -import sys -import tempfile -from argparse import ArgumentParser, RawDescriptionHelpFormatter -from functools import partial -from subprocess import PIPE, CalledProcessError, Popen, check_call - -caller = partial(check_call, shell=True) -popener = partial(Popen, stdout=PIPE, stderr=PIPE, shell=True) - -# git bisect exit codes -UNTESTABLE = 125 -GOOD = 0 -BAD = 1 - - -def call_or_untestable(cmd): - try: - caller(cmd) - except CalledProcessError: - sys.exit(UNTESTABLE) - - -def main(): - parser = ArgumentParser( - description=DESCRIP, epilog=EPILOG, formatter_class=RawDescriptionHelpFormatter - ) - parser.add_argument('test_path', type=str, help='Path to test') - parser.add_argument('--error-txt', type=str, help='regular expression for error of interest') - parser.add_argument('--clean', action='http://github.com/store_true', help='Clean git tree before running tests') - parser.add_argument('--build', action='http://github.com/store_true', help='Build git tree before running tests') - # parse the command line - args = parser.parse_args() - path = os.path.abspath(args.test_path) - if args.clean: - print('Cleaning') - call_or_untestable('git clean -fxd') - if args.build: - print('Building') - call_or_untestable('python setup.py build_ext -i') - cwd = os.getcwd() - tmpdir = tempfile.mkdtemp() - try: - os.chdir(tmpdir) - print('Testing') - proc = popener('nosetests ' + path) - stdout, stderr = proc.communicate() - finally: - os.chdir(cwd) - shutil.rmtree(tmpdir) - if args.error_txt: - regex = re.compile(args.error_txt) - if regex.search(stderr): - sys.exit(BAD) - sys.exit(GOOD) - sys.exit(proc.returncode) - - -if __name__ == '__main__': - main() diff --git a/tools/dicomfs.wsgi b/tools/dicomfs.wsgi deleted file mode 100644 index bd2480a647..0000000000 --- a/tools/dicomfs.wsgi +++ /dev/null @@ -1,260 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# Copyright (C) 2011 Christian Haselgrove - -import cgi -import sys -import traceback -import urllib -from functools import partial - -import jinja2 - -from nibabel import dft - -# this is the directory containing the DICOM data, or None for all cached data -BASE_DIR = '/path/to/DICOM' -BASE_DIR = None - -# default setting for whether to follow symlinks in BASE_DIR. Python 2.5 only -# accepts False for this setting, Python >= 2.6 accepts True or False -FOLLOWLINKS = False - -# Define routine to get studies -studies_getter = partial(dft.get_studies, followlinks=FOLLOWLINKS) - - -def html_unicode(u): - return cgi.escape(u.encode('utf-8')) - - -template_env = jinja2.Environment(autoescape=True) -template_env.filters['urlquote'] = urllib.quote - -index_template = """data - -Home -
    -
    -{% for p in patients|sort %} - Patient: {{ p }} -
    - {% if patients[p]|length == 1 %} - 1 study - {% else %} - {{ patients[p]|length }} studies - {% endif %} -
    -{% endfor %} - - -""" - -patient_template = """data - -Home -> Patient {{ studies[0].patient_name_or_uid() }} -
    -
    -Patient name: {{ studies[0].patient_name }} -
    -Patient ID: {{ studies[0].patient_id }} -
    -Patient birth date: {{ studies[0].patient_birth_date }} -
    -Patient sex: {{ studies[0].patient_sex }} -
    -
      -{% for s in studies %} -
    • Study {{ s.uid }}
    • -
        -
      • Date: {{ s.date }}
      • -
      • Time: {{ s.time }}
      • -
      • Comments: {{ s.comments }}
      • -
      • Series: {{ s.series|length }}
      • -{% endfor %} -
      - - -""" - -patient_date_time_template = """ -data - -Home -> Patient {{ study.patient_name_or_uid() }} -> Study {{ study.date}} {{ study.time }} -
      -
      -Patient name: {{ study.patient_name }} -
      -Study UID: {{ study.uid }} -
      -Study date: {{ study.date }} -
      -Study time: {{ study.time }} -
      -Study comments: {{ study.comments }} -{% if study.series|length == 0 %} -
      - No series. -{% else %} -
        - {% for s in study.series %} -
      • Series {{ s.number }} (NIfTI)
      • -
          -
        • Series UID: {{ s.uid }}
        • -
        • Series description: {{ s.description }}
        • -
        • Series dimensions: {{ s.rows }}x{{ s.columns }}x{{ s.storage_instances|length }}
        • -
        - - {% endfor %} -
      -{% endif %} - - -""" - - -class HandlerError: - def __init__(self, status, output): - self.status = status - self.output = output - return - - -def application(environ, start_response): - try: - (status, c_type, output) = handler(environ) - except HandlerError as exc: - status = exc.status - output = exc.output - c_type = 'text/plain' - except: - (exc_type, exc_value, exc_traceback) = sys.exc_info() - lines = traceback.format_exception(exc_type, exc_value, exc_traceback) - status = '500 Internal Server Error' - output = ''.join(lines) - c_type = 'text/plain' - response_headers = [('Content-Type', c_type), ('Content-Length', str(len(output)))] - if c_type == 'image/nifti': - response_headers.append(('Content-Disposition', 'attachment; filename=image.nii')) - start_response(status, response_headers) - return [output] - - -def handler(environ): - if environ['PATH_INFO'] == '' or environ['PATH_INFO'] == '/': - return ('200 OK', 'text/html', index(environ)) - parts = environ['PATH_INFO'].strip('/').split('/') - if len(parts) == 1: - return ('200 OK', 'text/html', patient(parts[0])) - if len(parts) == 2: - return ('200 OK', 'text/html', patient_date_time(parts[0], parts[1])) - if len(parts) == 4: - if parts[3] == 'nifti': - return ('200 OK', 'image/nifti', nifti(parts[0], parts[1], parts[2])) - elif parts[3] == 'png': - return ('200 OK', 'image/png', png(parts[0], parts[1], parts[2])) - raise HandlerError('404 Not Found', '%s not found\n' % environ['PATH_INFO']) - - -def study_cmp(a, b): - if a.date < b.date: - return -1 - if a.date > b.date: - return 1 - if a.time < b.time: - return -1 - if a.time > b.time: - return 1 - return 0 - - -def index(environ): - patients = {} - for s in studies_getter(BASE_DIR): - patients.setdefault(s.patient_name_or_uid(), []).append(s) - template = template_env.from_string(index_template) - return template.render(patients=patients).encode('utf-8') - - -def patient(patient): - studies = [s for s in studies_getter() if s.patient_name_or_uid() == patient] - if len(studies) == 0: - raise HandlerError('404 Not Found', 'patient %s not found\n' % patient) - studies.sort(study_cmp) - template = template_env.from_string(patient_template) - return template.render(studies=studies).encode('utf-8') - - -def patient_date_time(patient, date_time): - study = None - for s in studies_getter(): - if s.patient_name_or_uid() != patient: - continue - if date_time != '{}_{}'.format(s.date, s.time): - continue - study = s - break - if study is None: - raise HandlerError('404 Not Found', 'study not found') - template = template_env.from_string(patient_date_time_template) - return template.render(study=study).encode('utf-8') - - -def nifti(patient, date_time, scan): - study = None - for s in studies_getter(): - if s.patient_name_or_uid() != patient: - continue - if date_time != '{}_{}'.format(s.date, s.time): - continue - study = s - break - if study is None: - raise HandlerError('404 Not Found', 'study not found') - ser = None - for series in s.series: - if series.number != scan: - continue - ser = series - break - if ser is None: - raise HandlerError('404 Not Found', 'series not found') - return ser.as_nifti() - - -def png(patient, date_time, scan): - study = None - for s in studies_getter(): - if s.patient_name_or_uid() != patient: - continue - if date_time != '{}_{}'.format(s.date, s.time): - continue - study = s - break - if study is None: - raise HandlerError('404 Not Found', 'study not found') - ser = None - for series in s.series: - if series.number != scan: - continue - ser = series - break - if ser is None: - raise HandlerError('404 Not Found', 'series not found') - index = len(ser.storage_instances) / 2 - return ser.as_png(index, True) - - -if __name__ == '__main__': - import wsgiref.simple_server - - httpd = wsgiref.simple_server.make_server('', 8080, application) - httpd.serve_forever() - -# eof diff --git a/tools/gitwash_dumper.py b/tools/gitwash_dumper.py deleted file mode 100755 index 7472658ecd..0000000000 --- a/tools/gitwash_dumper.py +++ /dev/null @@ -1,242 +0,0 @@ -#!/usr/bin/env python -"""Checkout gitwash repo into directory and do search replace on name""" - -import fnmatch -import glob -import os -import re -import shutil -import sys -import tempfile -from optparse import OptionParser -from os.path import join as pjoin -from subprocess import call - -verbose = False - - -def clone_repo(url, branch): - cwd = os.getcwd() - tmpdir = tempfile.mkdtemp() - try: - cmd = f'git clone {url} {tmpdir}' - call(cmd, shell=True) - os.chdir(tmpdir) - cmd = f'git checkout {branch}' - call(cmd, shell=True) - except: - shutil.rmtree(tmpdir) - raise - finally: - os.chdir(cwd) - return tmpdir - - -def cp_files(in_path, globs, out_path): - try: - os.makedirs(out_path) - except OSError: - pass - out_fnames = [] - for in_glob in globs: - in_glob_path = pjoin(in_path, in_glob) - for in_fname in glob.glob(in_glob_path): - out_fname = in_fname.replace(in_path, out_path) - pth, _ = os.path.split(out_fname) - if not os.path.isdir(pth): - os.makedirs(pth) - shutil.copyfile(in_fname, out_fname) - out_fnames.append(out_fname) - return out_fnames - - -def filename_search_replace(sr_pairs, filename, backup=False): - """Search and replace for expressions in files""" - in_txt = open(filename, 'rt').read(-1) - out_txt = in_txt[:] - for in_exp, out_exp in sr_pairs: - in_exp = re.compile(in_exp) - out_txt = in_exp.sub(out_exp, out_txt) - if in_txt == out_txt: - return False - open(filename, 'wt').write(out_txt) - if backup: - open(filename + '.bak', 'wt').write(in_txt) - return True - - -def copy_replace( - replace_pairs, repo_path, out_path, cp_globs=('*',), rep_globs=('*',), renames=() -): - out_fnames = cp_files(repo_path, cp_globs, out_path) - renames = [(re.compile(in_exp), out_exp) for in_exp, out_exp in renames] - fnames = [] - for rep_glob in rep_globs: - fnames += fnmatch.filter(out_fnames, rep_glob) - if verbose: - print('\n'.join(fnames)) - for fname in fnames: - filename_search_replace(replace_pairs, fname, False) - for in_exp, out_exp in renames: - new_fname, n = in_exp.subn(out_exp, fname) - if n: - os.rename(fname, new_fname) - break - - -def make_link_targets( - proj_name, user_name, repo_name, known_link_fname, out_link_fname, url=None, ml_url=None -): - """Check and make link targets - - If url is None or ml_url is None, check if there are links present for these - in `known_link_fname`. If not, raise error. The check is: - - Look for a target `proj_name`. - Look for a target `proj_name` + ' mailing list' - - Also, look for a target `proj_name` + 'github'. If this exists, don't write - this target into the new file below. - - If we are writing any of the url, ml_url, or github address, then write new - file with these links, of form: - - .. _`proj_name` - .. _`proj_name`: url - .. _`proj_name` mailing list: url - """ - link_contents = open(known_link_fname, 'rt').readlines() - have_url = not url is None - have_ml_url = not ml_url is None - have_gh_url = None - for line in link_contents: - if not have_url: - match = re.match(r'..\s+_%s:\s+' % proj_name, line) - if match: - have_url = True - if not have_ml_url: - match = re.match(r'..\s+_`%s mailing list`:\s+' % proj_name, line) - if match: - have_ml_url = True - if not have_gh_url: - match = re.match(r'..\s+_`%s github`:\s+' % proj_name, line) - if match: - have_gh_url = True - if not have_url or not have_ml_url: - raise RuntimeError('Need command line or known project and / or mailing list URLs') - lines = [] - if not url is None: - lines.append(f'.. _{proj_name}: {url}\n') - if not have_gh_url: - gh_url = f'https://github.com/%7Buser_name%7D/%7Brepo_name%7D/n' - lines.append(f'.. _`{proj_name} github`: {gh_url}\n') - if not ml_url is None: - lines.append(f'.. _`{proj_name} mailing list`: {ml_url}\n') - if len(lines) == 0: - # Nothing to do - return - # A neat little header line - lines = [f'.. {proj_name}\n'] + lines - out_links = open(out_link_fname, 'wt') - out_links.writelines(lines) - out_links.close() - - -USAGE = """ - -If not set with options, the repository name is the same as the - -If not set with options, the main github user is the same as the -repository name.""" - - -GITWASH_CENTRAL = 'git://github.com/matthew-brett/gitwash.git' -GITWASH_BRANCH = 'master' - - -def main(): - parser = OptionParser() - parser.set_usage(parser.get_usage().strip() + USAGE) - parser.add_option( - '--repo-name', dest='repo_name', help='repository name - e.g. nitime', metavar='REPO_NAME' - ) - parser.add_option( - '--github-user', - dest='main_gh_user', - help='github username for main repo - e.g fperez', - metavar='MAIN_GH_USER', - ) - parser.add_option( - '--gitwash-url', - dest='gitwash_url', - help=f'URL to gitwash repository - default {GITWASH_CENTRAL}', - default=GITWASH_CENTRAL, - metavar='GITWASH_URL', - ) - parser.add_option( - '--gitwash-branch', - dest='gitwash_branch', - help=f'branch in gitwash repository - default {GITWASH_BRANCH}', - default=GITWASH_BRANCH, - metavar='GITWASH_BRANCH', - ) - parser.add_option( - '--source-suffix', - dest='source_suffix', - help="suffix of ReST source files - default '.rst'", - default='.rst', - metavar='SOURCE_SUFFIX', - ) - parser.add_option( - '--project-url', - dest='project_url', - help='URL for project web pages', - default=None, - metavar='PROJECT_URL', - ) - parser.add_option( - '--project-ml-url', - dest='project_ml_url', - help='URL for project mailing list', - default=None, - metavar='PROJECT_ML_URL', - ) - (options, args) = parser.parse_args() - if len(args) < 2: - parser.print_help() - sys.exit() - out_path, project_name = args - if options.repo_name is None: - options.repo_name = project_name - if options.main_gh_user is None: - options.main_gh_user = options.repo_name - repo_path = clone_repo(options.gitwash_url, options.gitwash_branch) - try: - copy_replace( - ( - ('PROJECTNAME', project_name), - ('REPONAME', options.repo_name), - ('MAIN_GH_USER', options.main_gh_user), - ), - repo_path, - out_path, - cp_globs=(pjoin('gitwash', '*'),), - rep_globs=('*.rst',), - renames=((r'\.rst$', options.source_suffix),), - ) - make_link_targets( - project_name, - options.main_gh_user, - options.repo_name, - pjoin(out_path, 'gitwash', 'known_projects.inc'), - pjoin(out_path, 'gitwash', 'this_project.inc'), - options.project_url, - options.project_ml_url, - ) - finally: - shutil.rmtree(repo_path) - - -if __name__ == '__main__': - main() diff --git a/tools/install_python.ps1 b/tools/install_python.ps1 deleted file mode 100644 index 45f0410d96..0000000000 --- a/tools/install_python.ps1 +++ /dev/null @@ -1,93 +0,0 @@ -# Sample script to install Python and pip under Windows -# Authors: Olivier Grisel, Jonathan Helmus and Kyle Kastner -# License: CC0 1.0 Universal: https://creativecommons.org/publicdomain/zero/1.0/ - -$MINICONDA_URL = "https://repo.continuum.io/miniconda/" -$BASE_URL = "https://www.python.org/ftp/python/" - - -function DownloadMiniconda ($python_version, $platform_suffix) { - $webclient = New-Object System.Net.WebClient - if ($python_version -eq "3.4") { - $filename = "Miniconda3-3.5.5-Windows-" + $platform_suffix + ".exe" - } else { - $filename = "Miniconda-3.5.5-Windows-" + $platform_suffix + ".exe" - } - $url = $MINICONDA_URL + $filename - - $basedir = $pwd.Path + "\" - $filepath = $basedir + $filename - if (Test-Path $filename) { - Write-Host "Reusing" $filepath - return $filepath - } - - # Download and retry up to 3 times in case of network transient errors. - Write-Host "Downloading" $filename "from" $url - $retry_attempts = 2 - for($i=0; $i -lt $retry_attempts; $i++){ - try { - $webclient.DownloadFile($url, $filepath) - break - } - Catch [Exception]{ - Start-Sleep 1 - } - } - if (Test-Path $filepath) { - Write-Host "File saved at" $filepath - } else { - # Retry once to get the error message if any at the last try - $webclient.DownloadFile($url, $filepath) - } - return $filepath -} - - -function InstallMiniconda ($python_version, $architecture, $python_home) { - Write-Host "Installing Python" $python_version "for" $architecture "bit architecture to" $python_home - if (Test-Path $python_home) { - Write-Host $python_home "already exists, skipping." - return $false - } - if ($architecture -eq "32") { - $platform_suffix = "x86" - } else { - $platform_suffix = "x86_64" - } - $filepath = DownloadMiniconda $python_version $platform_suffix - Write-Host "Installing" $filepath "to" $python_home - $install_log = $python_home + ".log" - $args = "/S /D=$python_home" - Write-Host $filepath $args - Start-Process -FilePath $filepath -ArgumentList $args -Wait -Passthru - if (Test-Path $python_home) { - Write-Host "Python $python_version ($architecture) installation complete" - } else { - Write-Host "Failed to install Python in $python_home" - Get-Content -Path $install_log - Exit 1 - } -} - - -function InstallMinicondaPip ($python_home) { - $pip_path = $python_home + "\Scripts\pip.exe" - $conda_path = $python_home + "\Scripts\conda.exe" - if (-not(Test-Path $pip_path)) { - Write-Host "Installing pip..." - $args = "install --yes pip" - Write-Host $conda_path $args - Start-Process -FilePath "$conda_path" -ArgumentList $args -Wait -Passthru - } else { - Write-Host "pip already installed." - } -} - - -function main () { - InstallMiniconda $env:PYTHON_VERSION $env:PYTHON_ARCH $env:PYTHON - InstallMinicondaPip $env:PYTHON -} - -main diff --git a/tools/make_tarball.py b/tools/make_tarball.py deleted file mode 100755 index b49a1f276a..0000000000 --- a/tools/make_tarball.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python -"""Simple script to create a tarball with proper git info. -""" - -import os - -import commands -from toollib import c, cd - -tag = commands.getoutput('git describe') -base_name = f'nibabel-{tag}' -tar_name = f'{base_name}.tgz' - -# git archive is weird: Even if I give it a specific path, it still won't -# archive the whole tree. It seems the only way to get the whole tree is to cd -# to the top of the tree. There are long threads (since 2007) on the git list -# about this and it still doesn't work in a sensible way... - -start_dir = os.getcwd() -cd('..') -c(f'git archive --format=tar --prefix={base_name}/ HEAD | gzip > {tar_name}') -c(f'mv {tar_name} tools/') diff --git a/tools/markdown_release_notes.py b/tools/markdown_release_notes.py deleted file mode 100644 index cdae474f51..0000000000 --- a/tools/markdown_release_notes.py +++ /dev/null @@ -1,140 +0,0 @@ -#!/usr/bin/env python -import re -import sys -from collections import defaultdict -from functools import cache -from operator import call -from pathlib import Path - -from sphinx.ext.intersphinx import fetch_inventory - -CHANGELOG = Path(__file__).parent.parent / 'Changelog' - -# Match release lines like "5.2.0 (Monday 11 December 2023)" -RELEASE_REGEX = re.compile(r"""((?:\d+)\.(?:\d+)\.(?:\d+)) \(\w+ \d{1,2} \w+ \d{4}\)$""") - - -class MockConfig: - intersphinx_timeout: int | None = None - tls_verify = False - tls_cacerts: str | dict[str, str] | None = None - user_agent: str = '' - - -@call -class MockApp: - srcdir = '' - config = MockConfig() - - -fetch_inv = cache(fetch_inventory) - - -def get_intersphinx(obj): - module = obj.split('.', 1)[0] - - registry = defaultdict(lambda: 'https://docs.python.org/3') - registry.update( - numpy='https://numpy.org/doc/stable', - ) - - base_url = registry[module] - - inventory = fetch_inv(MockApp, '', f'{base_url}/objects.inv') - # Check py: first, then whatever - for objclass in sorted(inventory, key=lambda x: not x.startswith('py:')): - if obj in inventory[objclass]: - return f'{base_url}/{inventory[objclass][obj][2]}' - raise ValueError("Couldn't lookup {obj}") - - -def main(): - version = sys.argv[1] - output = sys.argv[2] - if output == '-': - output = sys.stdout - else: - output = open(output, 'w') - - release_notes = [] - in_release_notes = False - - with open(CHANGELOG) as f: - for line in f: - match = RELEASE_REGEX.match(line) - if match: - if in_release_notes: - break - in_release_notes = match.group(1) == version - next(f) # Skip the underline - continue - - if in_release_notes: - release_notes.append(line) - - # Drop empty lines at start and end - while release_notes and not release_notes[0].strip(): - release_notes.pop(0) - while release_notes and not release_notes[-1].strip(): - release_notes.pop() - - # Join lines - release_notes = ''.join(release_notes) - - # Remove line breaks when they are followed by a space - release_notes = re.sub(r'\n +', ' ', release_notes) - - # Replace pr/ with # for GitHub - release_notes = re.sub(r'pr/(\d+)', r'#\1', release_notes) - - # Replace :mod:`package.X` with [package.X](...) - release_notes = re.sub( - r':mod:`nibabel\.(.*)`', - r'[nibabel.\1](https://nipy.org/nibabel/reference/nibabel.\1.html)', - release_notes, - ) - # Replace :class/func/attr:`package.module.X` with [package.module.X](...) - release_notes = re.sub( - r':(?:class|func|attr):`(nibabel\.\w*)(\.[\w.]*)?\.(\w+)`', - r'[\1\2.\3](https://nipy.org/nibabel/reference/\1.html#\1\2.\3)', - release_notes, - ) - release_notes = re.sub( - r':(?:class|func|attr):`~(nibabel\.\w*)(\.[\w.]*)?\.(\w+)`', - r'[\3](https://nipy.org/nibabel/reference/\1.html#\1\2.\3)', - release_notes, - ) - # Replace :meth:`package.module.class.X` with [package.module.class.X](...) - release_notes = re.sub( - r':meth:`(nibabel\.[\w.]*)\.(\w+)\.(\w+)`', - r'[\1.\2.\3](https://nipy.org/nibabel/reference/\1.html#\1.\2.\3)', - release_notes, - ) - release_notes = re.sub( - r':meth:`~(nibabel\.[\w.]*)\.(\w+)\.(\w+)`', - r'[\3](https://nipy.org/nibabel/reference/\1.html#\1.\2.\3)', - release_notes, - ) - # Replace ::`` with intersphinx lookup - for ref in re.findall(r'(:[^:]*:`~?\w[\w.]+\w`)', release_notes): - objclass, tilde, module, obj = re.match(r':([^:]*):`(~?)([\w.]+)\.(\w+)`', ref).groups() - url = get_intersphinx(f'{module}.{obj}') - mdlink = f'[{"" if tilde else module}{obj}]({url})' - release_notes = release_notes.replace(ref, mdlink) - # Replace RST links with Markdown links - release_notes = re.sub(r'`([^<`]*) <([^>]*)>`_+', r'[\1](\2)', release_notes) - - def python_doc(match): - module = match.group(1) - name = match.group(2) - return f'[{name}](https://docs.python.org/3/library/{module.lower()}.html#{module}.{name})' - - release_notes = re.sub(r':meth:`~([\w.]+)\.(\w+)`', python_doc, release_notes) - - with output: - output.write('## Release notes\n\n') - output.write(release_notes) - - -if __name__ == '__main__': - main() diff --git a/tools/mpkg_wrapper.py b/tools/mpkg_wrapper.py deleted file mode 100644 index f5f059b28d..0000000000 --- a/tools/mpkg_wrapper.py +++ /dev/null @@ -1,31 +0,0 @@ -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""Simple wrapper to use setuptools extension bdist_mpkg with NiBabel -distutils setup.py. - -This script is a minimal version of a wrapper script shipped with the -bdist_mpkg package. -""" - -__docformat__ = 'restructuredtext' - -import sys - - -def main(): - del sys.argv[0] - sys.argv.insert(1, 'bdist_mpkg') - g = dict(globals()) - g['__file__'] = sys.argv[0] - g['__name__'] = '__main__' - exec(open(sys.argv[0]).read(), g, g) - - -if __name__ == '__main__': - main() diff --git a/tools/prep_zenodo.py b/tools/prep_zenodo.py deleted file mode 100755 index 06b2dbf828..0000000000 --- a/tools/prep_zenodo.py +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env python3 -import json -from pathlib import Path -from subprocess import PIPE, run - -import git - -skip = {'nibotmi'} - - -def decommify(name): - return ' '.join(name.split(', ')[::-1]) - - -git_root = Path(git.Repo('.', search_parent_directories=True).working_dir) -zenodo_file = git_root / '.zenodo.json' - -zenodo = json.loads(zenodo_file.read_text()) if zenodo_file.exists() else {} - -orig_creators = zenodo.get('creators', []) -creator_map = {decommify(creator['name']): creator for creator in orig_creators} - -shortlog = run(['git', 'shortlog', '-ns'], stdout=PIPE) -counts = [line.split('\t', 1)[::-1] for line in shortlog.stdout.decode().split('\n') if line] - -commit_counts = {} -for committer, commits in counts: - commit_counts[committer] = commit_counts.get(committer, 0) + int(commits) - -# Stable sort: -# Number of commits in reverse order -# Ties broken by alphabetical order of first name -committers = [ - committer for committer, _ in sorted(commit_counts.items(), key=lambda x: (-x[1], x[0])) -] - -creators = [ - creator_map.get(committer, {'name': committer}) - for committer in committers - if committer not in skip -] - -zenodo['creators'] = creators -zenodo_file.write_text(json.dumps(zenodo, indent=2, sort_keys=True) + '\n') diff --git a/tools/profile b/tools/profile deleted file mode 100755 index cc13d773bc..0000000000 --- a/tools/profile +++ /dev/null @@ -1,127 +0,0 @@ -#!/usr/bin/python -# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- -# ex: set sts=4 ts=4 sw=4 et: -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -# -# See COPYING file distributed along with the NiBabel package for the -# copyright and license terms. -# -### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## -"""""" - -__docformat__ = 'restructuredtext' - -import os -import sys -from os import path - -if __name__ == '__main__': - - usage = ( - """Usage: %s [options] ... - """ - % sys.argv[0] - ) - - # default options - convert2kcache = True - displaykcachegrinder = True - printstats = False - pfilename = None - pstatsfilename = None - profilelines = True - profilelevel = 10 # how many most hungry to list in stats - run = True # either to run profiling at all - - removed = sys.argv.pop(0) - - if not len(sys.argv): - print(usage) - sys.exit(1) - - while sys.argv[0].startswith('-'): - if sys.argv[0] in ['-l', '--level']: - profilelevel = int(sys.argv[1]) - sys.argv.pop(0) - elif sys.argv[0] in ['-o', '--output-file']: - pfilename = sys.argv[1] - sys.argv.pop(0) - elif sys.argv[0] in ['-O', '--output-statsfile']: - pstatsfilename = sys.argv[1] - sys.argv.pop(0) - elif sys.argv[0] in ['-s', '--stats']: - printstats = True - convert2kcache = False - displaykcachegrinder = False - elif sys.argv[0] in ['-n', '--no-run']: - run = False - elif sys.argv[0] in ['-P', '--no-profilelines']: - profilelines = False - elif sys.argv[0] in ['-K', '--no-kcache']: - convert2kcache = False - displaykcachegrinder = False - else: - print(usage) - sys.exit(1) - sys.argv.pop(0) - - cmdname = sys.argv[0] - dirname = path.dirname(cmdname) - (root, ext) = path.splitext(path.basename(cmdname)) - - sys.path.append(dirname) - - # now do profiling - try: - import hotshot - except ImportError: - raise RuntimeError('No hotshot') - - if pfilename is None: - pfilename = cmdname + '.prof' - - if run: - exec(f'import {root} as runnable') - - if not 'main' in runnable.__dict__: - print(f'OOPS: file/module {cmdname} has no function main defined') - sys.exit(1) - - prof = hotshot.Profile(pfilename, lineevents=profilelines) - - try: - # actually return values are never setup - # since unittest.main sys.exit's - results = prof.runcall(runnable.main) - except SystemExit: - pass - - print(f'Saving profile data into {pfilename}') - prof.close() - - if printstats or pstatsfilename: - import hotshot.stats - - print('Loading profile file to print statistics') - stats = hotshot.stats.load(pfilename) - if printstats: - stats.strip_dirs() - stats.sort_stats('time', 'calls') - stats.print_stats(profilelevel) - if pstatsfilename: - stats.dump_stats(pstatsfilename) - - kfilename = pfilename + '.kcache' - if convert2kcache: - cmd = 'hotshot2calltree -o %s %s' % (kfilename, pfilename) - if os.system(cmd): - print('!!! Make sure to install kcachegrind-converters ;-)') - sys.exit(1) - - if displaykcachegrinder: - if os.system('kcachegrind %s' % kfilename): - print('!!! Make sure to install kcachegrind ;-)') - sys.exit(1) - -else: - print('Go away -- nothing to look here for as a module') diff --git a/tools/refresh_readme.py b/tools/refresh_readme.py deleted file mode 100755 index 0567a994ba..0000000000 --- a/tools/refresh_readme.py +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env python -"""Refresh README.rst file from long description - -Should be run from nibabel root (containing setup.py) -""" - -import os -import runpy - -readme_lines = [] -with open('README.rst', 'rt') as fobj: - for line in fobj: - readme_lines.append(line) - if line.startswith('.. Following contents should be'): - break - else: - raise ValueError('Expected comment not found') - -rel = runpy.run_path(os.path.join('nibabel', 'info.py')) - -readme = ''.join(readme_lines) + '\n' + rel['long_description'] - -with open('README.rst', 'wt') as fobj: - fobj.write(readme) - -print('Done') diff --git a/tools/update_requirements.py b/tools/update_requirements.py deleted file mode 100755 index 13709b22e8..0000000000 --- a/tools/update_requirements.py +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env python3 -import sys -from pathlib import Path - -try: - import tomllib -except ImportError: - import tomli as tomllib - -if sys.version_info < (3, 6): - print('This script requires Python 3.6 to work correctly') - sys.exit(1) - -repo_root = Path(__file__).parent.parent -pyproject_toml = repo_root / 'pyproject.toml' -reqs = repo_root / 'requirements.txt' -min_reqs = repo_root / 'min-requirements.txt' -doc_reqs = repo_root / 'doc-requirements.txt' - -with open(pyproject_toml, 'rb') as fobj: - config = tomllib.load(fobj) -requirements = config['project']['dependencies'] -doc_requirements = config['project']['optional-dependencies']['doc'] - -script_name = Path(__file__).relative_to(repo_root) - -lines = [f'# Auto-generated by {script_name}', ''] - -# Write requirements -lines[1:-1] = requirements -reqs.write_text('\n'.join(lines)) - -# # Write minimum requirements -# lines[1:-1] = [req.replace('>=', '==').replace('~=', '==') for req in requirements] -# min_reqs.write_text('\n'.join(lines)) -print(f"To update {min_reqs.name}, use `uv pip compile` (see comment at top of file).") - -# Write documentation requirements -lines[1:-1] = ['-r requirements.txt'] + doc_requirements -doc_reqs.write_text('\n'.join(lines)) diff --git a/tools/upload-gh-pages.sh b/tools/upload-gh-pages.sh deleted file mode 100755 index d6f622429a..0000000000 --- a/tools/upload-gh-pages.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/bash -# Upload website to gh-pages -USAGE="$0 []" -HTML_DIR=$1 -if [ -z "$HTML_DIR" ]; then - echo $USAGE - exit 1 -fi -if [ ! -e "$HTML_DIR/index.html" ]; then - echo "$HTML_DIR does not contain an index.html" - exit 1 -fi -if [ -d "$HTML_DIR/.git" ]; then - echo "$HTML_DIR already contains a .git directory" - exit 1 -fi -PROJECT=$2 -if [ -z "$PROJECT" ]; then - echo $USAGE - exit 1 -fi -ORGANIZATION=$3 -if [ -z "$ORGANIZATION" ]; then - ORGANIZATION=nipy -fi -upstream_repo="git@github.com:$ORGANIZATION/$PROJECT" -cd $HTML_DIR -git init -git checkout -b gh-pages -git add * -# A nojekyll file is needed to tell github that this is *not* a jekyll site: -touch .nojekyll -git add .nojekyll -git commit -a -m "Documentation build - no history" -git remote add origin $upstream_repo -git push origin gh-pages --force -rm -rf .git # Yes diff --git a/tools/valgrind-python b/tools/valgrind-python deleted file mode 100755 index 9dfc490dea..0000000000 --- a/tools/valgrind-python +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -path=$(dirname $0) - -valgrind --tool=memcheck --leak-check=full --suppressions=$path/valgrind-python.supp python $* diff --git a/tools/valgrind-python.supp b/tools/valgrind-python.supp deleted file mode 100644 index 4679470082..0000000000 --- a/tools/valgrind-python.supp +++ /dev/null @@ -1,439 +0,0 @@ -# -# This is a valgrind suppression file that should be used when using valgrind. -# -# Here's an example of running valgrind: -# -# cd python/dist/src -# valgrind --tool=memcheck --suppressions=Misc/valgrind-python.supp \ -# ./python -E -tt ./Lib/test/regrtest.py -u bsddb,network -# -# You must edit Objects/obmalloc.c and uncomment Py_USING_MEMORY_DEBUGGER -# to use the preferred suppressions with Py_ADDRESS_IN_RANGE. -# -# If you do not want to recompile Python, you can uncomment -# suppressions for PyObject_Free and PyObject_Realloc. -# -# See Misc/README.valgrind for more information. - -# all tool names: Addrcheck,Memcheck,cachegrind,helgrind,massif -{ - ADDRESS_IN_RANGE/Invalid read of size 4 - Memcheck:Addr4 - fun:Py_ADDRESS_IN_RANGE -} - -{ - ADDRESS_IN_RANGE/Invalid read of size 4 - Memcheck:Value4 - fun:Py_ADDRESS_IN_RANGE -} - -{ - ADDRESS_IN_RANGE/Invalid read of size 8 (x86_64 aka amd64) - Memcheck:Value8 - fun:Py_ADDRESS_IN_RANGE -} - -{ - ADDRESS_IN_RANGE/Conditional jump or move depends on uninitialised value - Memcheck:Cond - fun:Py_ADDRESS_IN_RANGE -} - -# -# Leaks (including possible leaks) -# Hmmm, I wonder if this masks some real leaks. I think it does. -# Will need to fix that. -# - -{ - Handle PyMalloc confusing valgrind (possibly leaked) - Memcheck:Leak - fun:realloc - fun:_PyObject_GC_Resize - fun:COMMENT_THIS_LINE_TO_DISABLE_LEAK_WARNING -} - -{ - Handle PyMalloc confusing valgrind (possibly leaked) - Memcheck:Leak - fun:malloc - fun:_PyObject_GC_New - fun:COMMENT_THIS_LINE_TO_DISABLE_LEAK_WARNING -} - -{ - Handle PyMalloc confusing valgrind (possibly leaked) - Memcheck:Leak - fun:malloc - fun:_PyObject_GC_NewVar - fun:COMMENT_THIS_LINE_TO_DISABLE_LEAK_WARNING -} - -# -# Non-python specific leaks -# - -{ - Handle pthread issue (possibly leaked) - Memcheck:Leak - fun:calloc - fun:allocate_dtv - fun:_dl_allocate_tls_storage - fun:_dl_allocate_tls -} - -{ - Handle pthread issue (possibly leaked) - Memcheck:Leak - fun:memalign - fun:_dl_allocate_tls_storage - fun:_dl_allocate_tls -} - -{ - ADDRESS_IN_RANGE/Invalid read of size 4 - Memcheck:Addr4 - fun:PyObject_Free -} - -{ - ADDRESS_IN_RANGE/Invalid read of size 4 - Memcheck:Value4 - fun:PyObject_Free -} - -{ - ADDRESS_IN_RANGE/Conditional jump or move depends on uninitialised value - Memcheck:Cond - fun:PyObject_Free -} - -{ - ADDRESS_IN_RANGE/Invalid read of size 4 - Memcheck:Addr4 - fun:PyObject_Realloc -} - -{ - ADDRESS_IN_RANGE/Invalid read of size 4 - Memcheck:Value4 - fun:PyObject_Realloc -} - -{ - ADDRESS_IN_RANGE/Conditional jump or move depends on uninitialised value - Memcheck:Cond - fun:PyObject_Realloc -} - -### -### All the suppressions below are for errors that occur within libraries -### that Python uses. The problems to not appear to be related to Python's -### use of the libraries. -### - -{ - Generic ubuntu ld problems - Memcheck:Addr8 - obj:/lib/ld-2.4.so - obj:/lib/ld-2.4.so - obj:/lib/ld-2.4.so - obj:/lib/ld-2.4.so -} - -{ - Generic gentoo ld problems - Memcheck:Cond - obj:/lib/ld-2.3.4.so - obj:/lib/ld-2.3.4.so - obj:/lib/ld-2.3.4.so - obj:/lib/ld-2.3.4.so -} - -{ - DBM problems, see test_dbm - Memcheck:Param - write(buf) - fun:write - obj:/usr/lib/libdb1.so.2 - obj:/usr/lib/libdb1.so.2 - obj:/usr/lib/libdb1.so.2 - obj:/usr/lib/libdb1.so.2 - fun:dbm_close -} - -{ - DBM problems, see test_dbm - Memcheck:Value8 - fun:memmove - obj:/usr/lib/libdb1.so.2 - obj:/usr/lib/libdb1.so.2 - obj:/usr/lib/libdb1.so.2 - obj:/usr/lib/libdb1.so.2 - fun:dbm_store - fun:dbm_ass_sub -} - -{ - DBM problems, see test_dbm - Memcheck:Cond - obj:/usr/lib/libdb1.so.2 - obj:/usr/lib/libdb1.so.2 - obj:/usr/lib/libdb1.so.2 - fun:dbm_store - fun:dbm_ass_sub -} - -{ - DBM problems, see test_dbm - Memcheck:Cond - fun:memmove - obj:/usr/lib/libdb1.so.2 - obj:/usr/lib/libdb1.so.2 - obj:/usr/lib/libdb1.so.2 - obj:/usr/lib/libdb1.so.2 - fun:dbm_store - fun:dbm_ass_sub -} - -{ - GDBM problems, see test_gdbm - Memcheck:Param - write(buf) - fun:write - fun:gdbm_open - -} - -{ - ZLIB problems, see test_gzip - Memcheck:Cond - obj:/lib/libz.so.1.2.3 - obj:/lib/libz.so.1.2.3 - fun:deflate -} - -{ - Avoid problems w/readline doing a putenv and leaking on exit - Memcheck:Leak - fun:malloc - fun:xmalloc - fun:sh_set_lines_and_columns - fun:_rl_get_screen_size - fun:_rl_init_terminal_io - obj:/lib/libreadline.so.4.3 - fun:rl_initialize -} - -### -### These occur from somewhere within the SSL, when running -### test_socket_sll. They are too general to leave on by default. -### -###{ -### somewhere in SSL stuff -### Memcheck:Cond -### fun:memset -###} -###{ -### somewhere in SSL stuff -### Memcheck:Value4 -### fun:memset -###} -### -###{ -### somewhere in SSL stuff -### Memcheck:Cond -### fun:MD5_Update -###} -### -###{ -### somewhere in SSL stuff -### Memcheck:Value4 -### fun:MD5_Update -###} - -# -# All of these problems come from using test_socket_ssl -# -{ - from test_socket_ssl - Memcheck:Cond - fun:BN_bin2bn -} - -{ - from test_socket_ssl - Memcheck:Cond - fun:BN_num_bits_word -} - -{ - from test_socket_ssl - Memcheck:Value4 - fun:BN_num_bits_word -} - -{ - from test_socket_ssl - Memcheck:Cond - fun:BN_mod_exp_mont_word -} - -{ - from test_socket_ssl - Memcheck:Cond - fun:BN_mod_exp_mont -} - -{ - from test_socket_ssl - Memcheck:Param - write(buf) - fun:write - obj:/usr/lib/libcrypto.so.0.9.7 -} - -{ - from test_socket_ssl - Memcheck:Cond - fun:RSA_verify -} - -{ - from test_socket_ssl - Memcheck:Value4 - fun:RSA_verify -} - -{ - from test_socket_ssl - Memcheck:Value4 - fun:DES_set_key_unchecked -} - -{ - from test_socket_ssl - Memcheck:Value4 - fun:DES_encrypt2 -} - -{ - from test_socket_ssl - Memcheck:Cond - obj:/usr/lib/libssl.so.0.9.7 -} - -{ - from test_socket_ssl - Memcheck:Value4 - obj:/usr/lib/libssl.so.0.9.7 -} - -{ - from test_socket_ssl - Memcheck:Cond - fun:BUF_MEM_grow_clean -} - -{ - from test_socket_ssl - Memcheck:Cond - fun:memcpy - fun:ssl3_read_bytes -} - -{ - from test_socket_ssl - Memcheck:Cond - fun:SHA1_Update -} - -{ - from test_socket_ssl - Memcheck:Value4 - fun:SHA1_Update -} - -# custom suppressions for yoh - -{ - - Memcheck:Cond - obj:/lib/ld-2.7.so - obj:/lib/ld-2.7.so - obj:/lib/ld-2.7.so - obj:/lib/ld-2.7.so - obj:/lib/ld-2.7.so -} - -{ - - Memcheck:Addr4 - obj:/lib/ld-2.7.so - obj:/lib/ld-2.7.so - obj:/lib/ld-2.7.so - obj:/lib/ld-2.7.so - obj:/lib/ld-2.7.so - obj:/lib/ld-2.7.so - obj:/lib/i686/cmov/libdl-2.7.so - obj:/lib/ld-2.7.so - obj:/lib/i686/cmov/libdl-2.7.so - fun:dlopen - fun:_PyImport_GetDynLoadFunc - fun:_PyImport_LoadDynamicModule -} - -{ - - Memcheck:Cond - obj:/lib/ld-2.7.so - obj:/lib/ld-2.7.so - obj:/lib/ld-2.7.so - obj:/lib/ld-2.7.so - obj:/lib/i686/cmov/libdl-2.7.so - obj:/lib/ld-2.7.so - obj:/lib/i686/cmov/libdl-2.7.so - fun:dlopen - fun:_PyImport_GetDynLoadFunc - fun:_PyImport_LoadDynamicModule - obj:/usr/bin/python2.4 - obj:/usr/bin/python2.4 -} - - -{ - - Memcheck:Cond - obj:/lib/ld-2.7.so - obj:/lib/ld-2.7.so - obj:/lib/ld-2.7.so - obj:/lib/i686/cmov/libdl-2.7.so - obj:/lib/ld-2.7.so - obj:/lib/i686/cmov/libdl-2.7.so - fun:dlopen - fun:_PyImport_GetDynLoadFunc - fun:_PyImport_LoadDynamicModule - obj:/usr/bin/python2.4 - obj:/usr/bin/python2.4 - obj:/usr/bin/python2.4 -} - - - -{ - - Memcheck:Addr4 - obj:/lib/ld-2.7.so - obj:/lib/ld-2.7.so - obj:/lib/ld-2.7.so - obj:/lib/ld-2.7.so - obj:/lib/ld-2.7.so - obj:/lib/ld-2.7.so - obj:/lib/ld-2.7.so - obj:/lib/ld-2.7.so - obj:/lib/ld-2.7.so - obj:/lib/i686/cmov/libdl-2.7.so - obj:/lib/ld-2.7.so - obj:/lib/i686/cmov/libdl-2.7.so -} diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 42ec48a6b6..0000000000 --- a/tox.ini +++ /dev/null @@ -1,211 +0,0 @@ -# This file encodes a lot of our intended support range, as well as some -# details about dependency availability. -# -# The majority of the information is contained in tox.envlist and testenv.deps. -[tox] -requires = - tox>=4 - tox-uv -envlist = - # No preinstallations - py3{9,10,11,12,13,13t}-none - # Minimum Python with minimum deps - py39-min - # Run full and pre dependencies against all archs - py3{9,10,11,12,13,13t}-{full,pre}-{x86,x64,arm64} - install - doctest - style - typecheck -skip_missing_interpreters = true - -# Configuration that allows us to split tests across GitHub runners effectively -[gh-actions] -python = - 3.9: py39 - 3.10: py310 - 3.11: py311 - 3.12: py312 - 3.13: py313 - 3.13t: py313t - -[gh-actions:env] -DEPENDS = - none: none - pre: pre - full: full, install - min: min - -ARCH = - x64: x64 - x86: x86 - arm64: arm64 - -[testenv] -description = Pytest with coverage -labels = test -pip_pre = - pre: true -pass_env = - # getpass.getuser() sources for Windows: - LOGNAME - USER - LNAME - USERNAME - # Environment variables we check for - NIPY_EXTRA_TESTS - # Pass user color preferences through - PY_COLORS - FORCE_COLOR - NO_COLOR - CLICOLOR - CLICOLOR_FORCE - # uv needs help in this case - py313t-x86: UV_PYTHON -set_env = - pre: PIP_EXTRA_INDEX_URL=https://pypi.anaconda.org/scientific-python-nightly-wheels/simple - pre: UV_INDEX=https://pypi.anaconda.org/scientific-python-nightly-wheels/simple - pre: UV_INDEX_STRATEGY=unsafe-best-match - py313t: PYTHONGIL={env:PYTHONGIL:0} -extras = - test - - # Simple, thanks Hugo and Paul - !none: dicomfs - !none: indexed_gzip - - # Minimum dependencies - min: minc2 - min: spm - min: viewers - min: zstd - - # Matplotlib has wheels for everything except win32 (x86) - {full,pre}-{x,arm}64: viewers - - # Nightly, but not released cp313t wheels for: scipy - # When released, remove the py3* line and add full to the pre line - py3{9,10,11,12,13}-full-{x,arm}64: spm - pre-{x,arm}64: spm - - # No cp313t wheels for: h5py, pyzstd - py3{9,10,11,12,13}-{full,pre}-{x,arm}64: minc2 - py3{9,10,11,12,13}-{full,pre}-{x,arm}64: zstd - - # win32 (x86) wheels still exist for scipy+py39 - py39-full-x86: spm - -deps = - pre: pydicom @ git+https://github.com/pydicom/pydicom.git@main - -uv_resolution = - min: lowest-direct - -commands = - pytest --doctest-modules --doctest-plus \ - --cov nibabel --cov-report xml:cov.xml \ - --junitxml test-results.xml \ - --durations=20 --durations-min=1.0 \ - --pyargs nibabel {posargs:-n auto} - -[testenv:install] -description = Install and verify import succeeds -labels = test -deps = -extras = -commands = - python -c "import nibabel; print(nibabel.__version__)" - -[testenv:docs] -description = Build documentation site -labels = docs -allowlist_externals = make -extras = doc -commands = - make -C doc html - -[testenv:doctest] -description = Run doctests in documentation site -labels = docs -allowlist_externals = make -extras = - doc - test -commands = - make -C doc doctest - -[testenv:style] -description = Check our style guide -labels = check -deps = - ruff>=0.3.0 -skip_install = true -commands = - ruff check --diff nibabel - ruff format --diff nibabel - -[testenv:style-fix] -description = Auto-apply style guide to the extent possible -labels = pre-release -deps = - ruff -skip_install = true -commands = - ruff check --fix nibabel - ruff format nibabel - -[testenv:spellcheck] -description = Check spelling -labels = check -deps = - codespell[toml] -skip_install = true -commands = - codespell . {posargs} - -[testenv:typecheck] -description = Check type consistency -labels = check -deps = - mypy - pytest - types-setuptools - types-Pillow - pydicom - numpy - pyzstd - importlib_resources - typing_extensions -skip_install = true -commands = - mypy nibabel - -[testenv:build{,-strict}] -labels = - check - pre-release -deps = - build - twine -skip_install = true -set_env = - build-strict: PYTHONWARNINGS=error -commands = - python -m build - python -m twine check dist/* - -[testenv:publish] -depends = build -labels = release -deps = - twine -skip_install = true -commands = - python -m twine upload dist/* - -[testenv:zenodo] -deps = gitpython -labels = pre-release -skip_install = true -commands = - python tools/prep_zenodo.py diff --git a/tutorials.html b/tutorials.html new file mode 100644 index 0000000000..5087368955 --- /dev/null +++ b/tutorials.html @@ -0,0 +1,175 @@ + + + + + + + + Neuroimaging in Python — NiBabel 5.4.0.dev1+g3b1c7b37 documentation + + + + + + + + + + + + + + + +
      +
      + +
      +
      +

      NiBabel

      +

      Access a cacophony of neuro-imaging file formats

      +
      +
      + + + + + + + + \ No newline at end of file