faceoff-mri 1.9.8__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. faceoff_mri-1.9.8/.dockerignore +11 -0
  2. faceoff_mri-1.9.8/.github/workflows/ci.yml +130 -0
  3. faceoff_mri-1.9.8/.github/workflows/container.yml +94 -0
  4. faceoff_mri-1.9.8/.github/workflows/release.yml +108 -0
  5. faceoff_mri-1.9.8/.gitignore +9 -0
  6. faceoff_mri-1.9.8/CITATION.cff +11 -0
  7. faceoff_mri-1.9.8/Dockerfile +57 -0
  8. faceoff_mri-1.9.8/GLOSSARY.md +20 -0
  9. faceoff_mri-1.9.8/LICENCE +21 -0
  10. faceoff_mri-1.9.8/PKG-INFO +86 -0
  11. faceoff_mri-1.9.8/README.md +71 -0
  12. faceoff_mri-1.9.8/archive/FaceOff.sh +319 -0
  13. faceoff_mri-1.9.8/bundle_template.py +32 -0
  14. faceoff_mri-1.9.8/docs/citation.md +135 -0
  15. faceoff_mri-1.9.8/docs/development.md +38 -0
  16. faceoff_mri-1.9.8/docs/images/faceoff_examples.gif +0 -0
  17. faceoff_mri-1.9.8/docs/images/results_deface.gif +0 -0
  18. faceoff_mri-1.9.8/docs/images/results_reface.gif +0 -0
  19. faceoff_mri-1.9.8/docs/installation.md +97 -0
  20. faceoff_mri-1.9.8/docs/outputs.md +22 -0
  21. faceoff_mri-1.9.8/docs/results.md +61 -0
  22. faceoff_mri-1.9.8/docs/template.md +15 -0
  23. faceoff_mri-1.9.8/docs/usage.md +79 -0
  24. faceoff_mri-1.9.8/faceoff/COPYING-MNI +11 -0
  25. faceoff_mri-1.9.8/faceoff/__init__.py +1 -0
  26. faceoff_mri-1.9.8/faceoff/__main__.py +5 -0
  27. faceoff_mri-1.9.8/faceoff/ants.py +366 -0
  28. faceoff_mri-1.9.8/faceoff/cli.py +480 -0
  29. faceoff_mri-1.9.8/faceoff/deface.py +534 -0
  30. faceoff_mri-1.9.8/faceoff/images.py +15 -0
  31. faceoff_mri-1.9.8/faceoff/inputs.py +93 -0
  32. faceoff_mri-1.9.8/faceoff/masking.py +128 -0
  33. faceoff_mri-1.9.8/faceoff/messages.py +131 -0
  34. faceoff_mri-1.9.8/faceoff/qc.py +215 -0
  35. faceoff_mri-1.9.8/faceoff/regions.py +76 -0
  36. faceoff_mri-1.9.8/faceoff/registration.py +288 -0
  37. faceoff_mri-1.9.8/faceoff/reorient.py +169 -0
  38. faceoff_mri-1.9.8/faceoff/scratch.py +43 -0
  39. faceoff_mri-1.9.8/faceoff/template.py +114 -0
  40. faceoff_mri-1.9.8/faceoff/template_files.py +77 -0
  41. faceoff_mri-1.9.8/licences/ANTs-COPYING.txt +202 -0
  42. faceoff_mri-1.9.8/licences/ITK-LICENSE.txt +202 -0
  43. faceoff_mri-1.9.8/licences/ITK-NOTICE.txt +15 -0
  44. faceoff_mri-1.9.8/pyproject.toml +45 -0
  45. faceoff_mri-1.9.8/scripts/check_dist.py +59 -0
  46. faceoff_mri-1.9.8/tests/conftest.py +107 -0
  47. faceoff_mri-1.9.8/tests/test_ants.py +314 -0
  48. faceoff_mri-1.9.8/tests/test_check_dist.py +48 -0
  49. faceoff_mri-1.9.8/tests/test_cli.py +312 -0
  50. faceoff_mri-1.9.8/tests/test_deface.py +865 -0
  51. faceoff_mri-1.9.8/tests/test_dockerfile.py +23 -0
  52. faceoff_mri-1.9.8/tests/test_images.py +21 -0
  53. faceoff_mri-1.9.8/tests/test_inputs.py +78 -0
  54. faceoff_mri-1.9.8/tests/test_masking.py +154 -0
  55. faceoff_mri-1.9.8/tests/test_messages.py +40 -0
  56. faceoff_mri-1.9.8/tests/test_qc.py +76 -0
  57. faceoff_mri-1.9.8/tests/test_regions.py +38 -0
  58. faceoff_mri-1.9.8/tests/test_registration.py +188 -0
  59. faceoff_mri-1.9.8/tests/test_reorient.py +167 -0
  60. faceoff_mri-1.9.8/tests/test_scratch.py +24 -0
  61. faceoff_mri-1.9.8/tests/test_template.py +118 -0
  62. faceoff_mri-1.9.8/tests/test_versions.py +23 -0
  63. faceoff_mri-1.9.8/uv.lock +596 -0
@@ -0,0 +1,11 @@
1
+ # Allowlist: only what the build needs. testdata/ holds a real scan with a face and must never
2
+ # end up in an image.
3
+ *
4
+ !pyproject.toml
5
+ !uv.lock
6
+ !README.md
7
+ !LICENCE
8
+ !bundle_template.py
9
+ !faceoff
10
+ !licences
11
+ **/__pycache__
@@ -0,0 +1,130 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [master]
6
+ pull_request:
7
+ workflow_dispatch:
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ concurrency:
13
+ group: ci-${{ github.ref }}
14
+ # Only pull requests are cancelled by a newer push; a run on master may be tagging a release.
15
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
16
+
17
+ jobs:
18
+ quality:
19
+ name: Quality checks
20
+ runs-on: ubuntu-latest
21
+ steps:
22
+ - uses: actions/checkout@v7
23
+
24
+ - name: Install uv
25
+ uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
26
+ with:
27
+ enable-cache: true
28
+
29
+ - name: Install dependencies
30
+ run: uv sync --locked
31
+
32
+ - name: Check formatting
33
+ run: uv run ruff format --check
34
+
35
+ - name: Lint
36
+ run: uv run ruff check
37
+
38
+ - name: Type-check
39
+ run: uv run mypy .
40
+
41
+ test:
42
+ name: Test Python ${{ matrix.python }}
43
+ runs-on: ubuntu-latest
44
+ strategy:
45
+ fail-fast: false
46
+ matrix:
47
+ python: ["3.12", "3.13", "3.14"]
48
+
49
+ steps:
50
+ - uses: actions/checkout@v7
51
+
52
+ - name: Install uv
53
+ uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
54
+ with:
55
+ enable-cache: true
56
+
57
+ - name: Install dependencies
58
+ run: uv sync --locked --python ${{ matrix.python }}
59
+
60
+ - name: Test
61
+ run: uv run --python ${{ matrix.python }} pytest
62
+
63
+ package:
64
+ name: Build and check the package
65
+ runs-on: ubuntu-latest
66
+ steps:
67
+ - uses: actions/checkout@v7
68
+
69
+ - name: Install uv
70
+ uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
71
+
72
+ # The wheel build downloads the five template files from Zenodo and bundles them.
73
+ - name: Build
74
+ run: uv build --no-sources --out-dir dist
75
+
76
+ - name: Check what is inside
77
+ run: python3 scripts/check_dist.py dist
78
+
79
+ - name: Install the wheel in a clean environment and run it
80
+ run: |
81
+ uv venv --python 3.13 "$RUNNER_TEMP/clean"
82
+ uv pip install --python "$RUNNER_TEMP/clean/bin/python" dist/*.whl
83
+ cd "$RUNNER_TEMP"
84
+ clean/bin/faceoff --version
85
+ clean/bin/python - <<'PY'
86
+ from faceoff.template import Template
87
+ from faceoff.template_files import is_complete
88
+
89
+ folder = Template.default().folder
90
+ assert "site-packages" in str(folder), folder
91
+ assert is_complete(folder), folder
92
+ PY
93
+
94
+ tag-release:
95
+ name: Tag a version bump
96
+ # A new version in pyproject.toml, merged to master and green, is released: the tag starts
97
+ # the image, the GitHub release (which Zenodo archives) and PyPI.
98
+ needs: [quality, test, package]
99
+ if: github.event_name == 'push' && github.ref == 'refs/heads/master'
100
+ runs-on: ubuntu-latest
101
+ permissions:
102
+ contents: write
103
+ actions: write
104
+ steps:
105
+ - uses: actions/checkout@v7
106
+ with:
107
+ fetch-depth: 0
108
+
109
+ - name: Tag and start the release workflows if pyproject.toml's version is new
110
+ env:
111
+ GH_TOKEN: ${{ github.token }}
112
+ run: |
113
+ version="$(python3 -c 'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])')"
114
+ tag="v${version}"
115
+ if git rev-parse -q --verify "refs/tags/${tag}" >/dev/null; then
116
+ echo "Tag ${tag} already exists; nothing to do."
117
+ exit 0
118
+ fi
119
+ # A version below an existing tag is a mistake (a revert, a typo), not a release.
120
+ latest="$(git tag --list 'v*' | sort -V | tail -n 1)"
121
+ if [[ -n "${latest}" && "$(printf '%s\n%s\n' "${latest}" "${tag}" | sort -V | tail -n 1)" != "${tag}" ]]; then
122
+ echo "Version ${version} is lower than the existing tag ${latest}; not tagging." >&2
123
+ exit 1
124
+ fi
125
+ git tag "${tag}"
126
+ git push origin "${tag}"
127
+ # A tag pushed with the default token does not start other workflows'
128
+ # `on: push: tags` (GitHub's safeguard against loops), so start them explicitly.
129
+ gh workflow run release.yml --ref "${tag}"
130
+ gh workflow run container.yml --ref "${tag}"
@@ -0,0 +1,94 @@
1
+ name: Container image
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+ pull_request:
7
+ paths:
8
+ - Dockerfile
9
+ - .dockerignore
10
+ - pyproject.toml
11
+ - uv.lock
12
+ - licences/**
13
+ - faceoff/**
14
+ - bundle_template.py
15
+ - .github/workflows/container.yml
16
+ workflow_dispatch:
17
+
18
+ permissions:
19
+ contents: read
20
+
21
+ env:
22
+ # Lower case is required by GHCR, so this is not built from github.repository.
23
+ IMAGE: ghcr.io/srikash/faceoff
24
+
25
+ jobs:
26
+ docker:
27
+ name: Build, check and push the Docker image
28
+ runs-on: ubuntu-latest
29
+ permissions:
30
+ contents: read
31
+ packages: write
32
+ outputs:
33
+ version: ${{ steps.version.outputs.version }}
34
+ steps:
35
+ - uses: actions/checkout@v7
36
+
37
+ - name: Work out the version and check a tag matches pyproject.toml
38
+ id: version
39
+ run: |
40
+ version="$(python3 -c 'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])')"
41
+ if [[ "${GITHUB_REF_TYPE}" == "tag" && "${GITHUB_REF_NAME#v}" != "${version}" ]]; then
42
+ echo "Tag ${GITHUB_REF_NAME} does not match version ${version} in pyproject.toml." >&2
43
+ exit 1
44
+ fi
45
+ echo "version=${version}" >> "${GITHUB_OUTPUT}"
46
+
47
+ - name: Set up Docker Buildx
48
+ uses: docker/setup-buildx-action@v3
49
+
50
+ # The build downloads the template files from Zenodo, as the release build does.
51
+ - name: Build (not pushed yet)
52
+ uses: docker/build-push-action@v6
53
+ with:
54
+ context: .
55
+ platforms: linux/amd64
56
+ load: true
57
+ tags: faceoff:check
58
+ cache-from: type=gha
59
+ cache-to: type=gha,mode=max
60
+
61
+ - name: Check the image
62
+ run: |
63
+ docker run --rm faceoff:check --version | grep -F "${{ steps.version.outputs.version }}"
64
+ docker run --rm --entrypoint python faceoff:check -c '
65
+ from faceoff.template import Template
66
+ from faceoff.template_files import is_complete
67
+ folder = Template.default().folder
68
+ assert "site-packages" in str(folder) and is_complete(folder), folder'
69
+ scans="$(docker run --rm --entrypoint sh faceoff:check -c "find / -xdev \( -name '*.nii' -o -name '*.nii.gz' \) -not -path '*/nibabel/tests/*'" | wc -l)"
70
+ test "${scans}" -eq 5 || { echo "Expected only the 5 template files, found ${scans} images." >&2; exit 1; }
71
+ docker run --rm --entrypoint antsRegistration faceoff:check --version | grep -F "ANTs Version"
72
+ # Every ANTs tool FaceOff runs is there and finds all its libraries.
73
+ docker run --rm --entrypoint sh faceoff:check -c '
74
+ for t in antsAI antsRegistration antsApplyTransforms ConvertTransformFile; do
75
+ path="$(command -v "$t")" || { echo "$t is missing" >&2; exit 1; }
76
+ if ldd "$path" | grep -q "not found"; then echo "$t is missing a library" >&2; exit 1; fi
77
+ done'
78
+
79
+ - name: Log in to GHCR
80
+ if: github.event_name != 'pull_request'
81
+ uses: docker/login-action@v3
82
+ with:
83
+ registry: ghcr.io
84
+ username: ${{ github.actor }}
85
+ password: ${{ secrets.GITHUB_TOKEN }}
86
+
87
+ # Push the image that was just checked, not a second build of it.
88
+ - name: Push
89
+ if: github.ref_type == 'tag'
90
+ run: |
91
+ for tag in "${{ steps.version.outputs.version }}" latest; do
92
+ docker tag faceoff:check "${IMAGE}:${tag}"
93
+ docker push "${IMAGE}:${tag}"
94
+ done
@@ -0,0 +1,108 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+ workflow_dispatch:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ build:
13
+ name: Build the package
14
+ runs-on: ubuntu-latest
15
+ outputs:
16
+ name: ${{ steps.meta.outputs.name }}
17
+ version: ${{ steps.meta.outputs.version }}
18
+ steps:
19
+ - uses: actions/checkout@v7
20
+
21
+ - name: Install uv
22
+ uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
23
+
24
+ - name: Read the name and version, and check a tag matches pyproject.toml
25
+ id: meta
26
+ run: |
27
+ read -r name version < <(python3 -c 'import tomllib; p = tomllib.load(open("pyproject.toml", "rb"))["project"]; print(p["name"], p["version"])')
28
+ if [[ "${GITHUB_REF_TYPE}" == "tag" && "${GITHUB_REF_NAME#v}" != "${version}" ]]; then
29
+ echo "Tag ${GITHUB_REF_NAME} does not match version ${version} in pyproject.toml." >&2
30
+ exit 1
31
+ fi
32
+ echo "name=${name}" >> "${GITHUB_OUTPUT}"
33
+ echo "version=${version}" >> "${GITHUB_OUTPUT}"
34
+
35
+ - name: Test
36
+ run: uv run --locked pytest
37
+
38
+ # The wheel build downloads the five template files from Zenodo and bundles them.
39
+ - name: Build
40
+ run: uv build --no-sources --out-dir dist
41
+
42
+ - name: Check what is inside
43
+ run: python3 scripts/check_dist.py dist
44
+
45
+ - uses: actions/upload-artifact@v4
46
+ with:
47
+ name: dist
48
+ path: dist/
49
+
50
+ github-release:
51
+ name: Create the GitHub release
52
+ # The release is what Zenodo archives.
53
+ if: github.ref_type == 'tag'
54
+ needs: build
55
+ runs-on: ubuntu-latest
56
+ permissions:
57
+ contents: write
58
+ steps:
59
+ - uses: actions/download-artifact@v4
60
+ with:
61
+ name: dist
62
+ path: dist/
63
+
64
+ - name: Create the release if it does not exist yet
65
+ env:
66
+ GH_TOKEN: ${{ github.token }}
67
+ run: |
68
+ if gh release view "${GITHUB_REF_NAME}" --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then
69
+ echo "Release ${GITHUB_REF_NAME} already exists; nothing to do."
70
+ else
71
+ gh release create "${GITHUB_REF_NAME}" dist/* \
72
+ --repo "${GITHUB_REPOSITORY}" --verify-tag --generate-notes \
73
+ --title "FaceOff ${GITHUB_REF_NAME#v}"
74
+ fi
75
+
76
+ pypi:
77
+ name: Publish to PyPI
78
+ # Needs the trusted publisher and the pypi environment (docs/development.md).
79
+ if: github.ref_type == 'tag'
80
+ needs: build
81
+ runs-on: ubuntu-latest
82
+ environment: pypi
83
+ permissions:
84
+ contents: read
85
+ id-token: write
86
+ steps:
87
+ - name: Install uv
88
+ uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
89
+
90
+ - uses: actions/download-artifact@v4
91
+ with:
92
+ name: dist
93
+ path: dist/
94
+
95
+ - name: Skip if this version is already on PyPI
96
+ id: check
97
+ run: |
98
+ status="$(curl --silent --output /dev/null --write-out '%{http_code}' \
99
+ "https://pypi.org/pypi/${{ needs.build.outputs.name }}/${{ needs.build.outputs.version }}/json")"
100
+ case "${status}" in
101
+ 200) echo "Already on PyPI; skipping."; echo "skip=true" >> "${GITHUB_OUTPUT}" ;;
102
+ 404) echo "skip=false" >> "${GITHUB_OUTPUT}" ;;
103
+ *) echo "Unexpected PyPI answer ${status}." >&2; exit 1 ;;
104
+ esac
105
+
106
+ - name: Publish
107
+ if: steps.check.outputs.skip != 'true'
108
+ run: uv publish dist/*
@@ -0,0 +1,9 @@
1
+ __pycache__/
2
+ faceoff/data/
3
+ dist/
4
+ tempData/
5
+ testdata/
6
+ HANDOFF.md
7
+ registration_review/
8
+ *.sif
9
+ .venv/
@@ -0,0 +1,11 @@
1
+ cff-version: 1.2.0
2
+ message: "If you use this software, please cite it as below."
3
+ authors:
4
+ - family-names: Kashyap
5
+ given-names: Sriranga
6
+ orcid: https://orcid.org/0000-0003-0307-9773
7
+ title: "FaceOff"
8
+ version: 1.9.8
9
+ date-released: 2026-11-07
10
+ license: MIT
11
+ repository-code: https://github.com/srikash/FaceOff
@@ -0,0 +1,57 @@
1
+ # syntax=docker/dockerfile:1
2
+ #
3
+ # FaceOff with ANTs inside: run it on a folder of scans without installing anything else.
4
+ #
5
+ # docker run --rm --init --user "$(id -u):$(id -g)" -v "$PWD":/data ghcr.io/srikash/faceoff -i scan.nii.gz
6
+ #
7
+ # The image is amd64 only, because the ANTs image it takes ANTs from is.
8
+
9
+ # Keep in step with ANTS_IMAGE in faceoff/ants.py (a test checks this).
10
+ ARG ANTS_IMAGE=antsx/ants:2.6.5
11
+
12
+ FROM ghcr.io/astral-sh/uv:0.12.10 AS uv
13
+
14
+ # Take only what FaceOff runs from the ANTs image: the four tools and the libraries they load
15
+ # (about 130 MB of the 340 MB in /opt/ants).
16
+ FROM ${ANTS_IMAGE} AS ants
17
+ RUN set -eu; \
18
+ mkdir -p /ants/bin /ants/lib; \
19
+ for tool in antsAI antsRegistration antsApplyTransforms ConvertTransformFile; do \
20
+ cp "/opt/ants/bin/${tool}" /ants/bin/; \
21
+ ldd "/opt/ants/bin/${tool}" | awk '/=> \/opt\/ants/ {print $3}' >> /tmp/libs.txt; \
22
+ done; \
23
+ test -s /tmp/libs.txt; \
24
+ sort -u /tmp/libs.txt | xargs -I{} cp -L {} /ants/lib/; \
25
+ test "$(ls /ants/lib | wc -l)" -gt 20
26
+
27
+ FROM python:3.13-slim-bookworm AS builder
28
+ COPY --from=uv /uv /uvx /bin/
29
+ ENV UV_COMPILE_BYTECODE=1 \
30
+ UV_LINK_MODE=copy \
31
+ UV_PYTHON_DOWNLOADS=never \
32
+ UV_PROJECT_ENVIRONMENT=/opt/venv
33
+ WORKDIR /src
34
+ COPY pyproject.toml uv.lock ./
35
+ RUN uv sync --locked --no-dev --no-install-project
36
+ # A normal (not editable) install builds the wheel, which downloads the five template files
37
+ # from Zenodo and bundles them, so the image needs no download when it runs.
38
+ COPY README.md LICENCE bundle_template.py ./
39
+ COPY faceoff ./faceoff
40
+ RUN uv sync --locked --no-dev --no-editable
41
+
42
+ # Same Debian release as the ANTs image, so its libraries match.
43
+ FROM python:3.13-slim-bookworm
44
+ LABEL org.opencontainers.image.title="FaceOff" \
45
+ org.opencontainers.image.description="Deface MRI scans with ANTs and a modified MNI152 template" \
46
+ org.opencontainers.image.source="https://github.com/srikash/FaceOff" \
47
+ org.opencontainers.image.licenses="MIT AND Apache-2.0"
48
+ COPY --from=ants /ants /opt/ants
49
+ COPY licences /usr/share/doc/faceoff/licences
50
+ COPY --from=builder /opt/venv /opt/venv
51
+ COPY --from=builder /src/faceoff/COPYING-MNI /usr/share/doc/faceoff/licences/MNI-COPYING.txt
52
+ # The copied libraries are all ITK's and ANTs' own (libitk*-5.4), none named like a system
53
+ # library, so putting their folder on the library path cannot shadow Python's.
54
+ ENV PATH="/opt/venv/bin:/opt/ants/bin:${PATH}" \
55
+ LD_LIBRARY_PATH=/opt/ants/lib
56
+ WORKDIR /data
57
+ ENTRYPOINT ["faceoff"]
@@ -0,0 +1,20 @@
1
+ # Glossary
2
+
3
+ The words FaceOff's code and documentation use, and what they mean.
4
+
5
+ - **Deface mask** (`_defaceMask`): 1 where the scan is kept, 0 where it was removed (the face, and the eyes with `--enucleate` or `--renucleate`).
6
+ - **Face mask**: the template's mask of the face, 1 over the region to remove. It is moved onto each scan by the registration.
7
+ - **Eye mask**: the template's mask of the eyes, 1 over the eyes. It is used with `--enucleate` and `--renucleate`.
8
+ - **Reface**: fill the removed face with the template's face, scaled to the scan's brightness (`--reface`).
9
+ - **Enucleate**: remove the eyes as well as the face (`--enucleate`).
10
+ - **Renucleate**: remove the eyes and fill them with the template's eyes (`--renucleate`; `--reface --enucleate` does the same, plus the face).
11
+ - **Fill**: put template voxels into a removed region (the face, the eyes, or both). The filled image is written as `_refaced`.
12
+ - **Regions** (`faceoff/regions.py`): works out once, from `--reface`, `--enucleate`, `--renucleate`, and `--syn`, which regions are removed, which are filled, and which registration moves each mask. Every other part of FaceOff asks it.
13
+ - **Affine registration**: rigid, then affine, fitted inside the brain with mutual information (MI). It is always run, and moves the face mask by default.
14
+ - **Nonlinear brain registration** (`--syn`): SyN over the whole head with MI, then inside the brain with cross-correlation (CC). With `--syn` it moves the face mask instead of the affine.
15
+ - **Nonlinear face registration**: SyN over the face with MI, starting from the registrations above. It moves the eye mask, and the template face and eyes that fill the removed regions.
16
+ - **Stage** (`AFFINE`, `NONLINEAR_BRAIN`, `NONLINEAR_FACE`): which of those three registrations moves a template image onto the scan. Each image is moved by one ANTs call with the whole chain of transforms, so it is resampled once.
17
+ - **Template orientation** (`_tal_` files): the scan rotated to line up with the template by changing only its header; the voxels are not resampled.
18
+ - **Extra images** (`--extra-images`): other images from the same session and scanner space (other MP2RAGE images, GRE echoes). They get the same masks, moved onto their own voxel grid.
19
+ - **Scan to deface** (`faceoff/inputs.py`): one scan and its extra images, with every file already checked. All files given are checked before the first scan starts, and every problem is listed at once.
20
+ - **Stand-in registration** (`tests/conftest.py`): used only by the tests. It moves template images with a known affine, without ANTs.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020-2026 Sriranga Kashyap
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,86 @@
1
+ Metadata-Version: 2.5
2
+ Name: faceoff-mri
3
+ Version: 1.9.8
4
+ Summary: MRI defacing tool using ANTs registration (your own install, Docker, or Apptainer) to an MNI152 template
5
+ License-Expression: MIT
6
+ License-File: LICENCE
7
+ Requires-Python: >=3.12
8
+ Requires-Dist: click<9,>=8.1
9
+ Requires-Dist: nibabel>=5
10
+ Requires-Dist: numpy>=1.26
11
+ Requires-Dist: pillow>=10
12
+ Requires-Dist: rich-click<2,>=1.9
13
+ Requires-Dist: rich<15,>=13
14
+ Description-Content-Type: text/markdown
15
+
16
+ # FaceOff [![Version](https://img.shields.io/badge/version-1.9.8-purple.svg)](https://github.com/srikash/FaceOff/releases/tag/v1.9.8) [![Licence](https://img.shields.io/badge/licence-MIT-orange.svg)](LICENCE) [![DOI](https://zenodo.org/badge/307797054.svg)](https://zenodo.org/badge/latestdoi/307797054)
17
+
18
+ Deface anatomical MRI scans with ANTs. FaceOff removes the face, and the eyes if you ask, and can put the template's face and eyes back.
19
+
20
+ - Reorients to template orientation and crops in the same run, with no interpolation.
21
+ - `--extra-images` (MP2RAGE, GRE echoes) and `--batch` (many scans).
22
+ - A QC page with every run.
23
+ - ANTs from your PATH, Docker, or Apptainer.
24
+
25
+ <p align="center">
26
+ <img src="https://gitlab.com/skash/images-and-gifs/-/raw/master/face-off/faceoff.gif" alt="Face swap scene from the film Face/Off">
27
+ <br>
28
+ <em>Face/Off (1997)</em>
29
+ </p>
30
+
31
+ ## Install
32
+
33
+ You need Python 3.12 or newer, and ANTs (2.3.3 or newer, tested with 2.6.5) on your PATH, Docker, or Apptainer (or Singularity).
34
+
35
+ ```bash
36
+ pip install "git+https://github.com/srikash/FaceOff.git"
37
+ faceoff --help
38
+ ```
39
+
40
+ The package will be called `faceoff-mri` on PyPI; `pip install faceoff-mri` works once the first release is published. The name `faceoff` on PyPI is a different program, so if you ran `pip install faceoff` earlier, run `pip uninstall faceoff` first.
41
+
42
+ A container image with ANTs inside is described in [docs/installation.md](docs/installation.md).
43
+
44
+ Conda, clusters, and Apptainer: [docs/installation.md](docs/installation.md).
45
+
46
+ ## Quick start
47
+
48
+ ```bash
49
+ faceoff -i sub-01_T1w.nii.gz # deface and reorient
50
+ faceoff -i sub-01_T1w.nii.gz --reface # also put a face back
51
+ faceoff -i sub-01_T1w.nii.gz --enucleate # remove the eyes too
52
+ faceoff -i sub-01_T1w.nii.gz --renucleate # remove the eyes and put template eyes back
53
+ faceoff --batch sub-*_T1w.nii.gz # several scans, one after another
54
+ faceoff -i sub-01_UNIT1.nii.gz --extra-images sub-01_inv-*_MP2RAGE.nii.gz # MP2RAGE or GRE echoes
55
+ ```
56
+
57
+ Outputs go next to the input: [docs/outputs.md](docs/outputs.md).
58
+
59
+ ## Example outputs
60
+
61
+ ![Sagittal sweep of FaceOff's outputs: defaced, with the eyes removed, refaced, and refaced with the template's eyes](docs/images/faceoff_examples.gif)
62
+
63
+ The template's face and eyes are shown in turbo.
64
+
65
+ ## How it compares
66
+
67
+ FaceOff, [pydeface](https://github.com/poldracklab/pydeface) 2.1.0, and AFNI's `afni_refacer2` on the same 1 mm MPRAGE, 12 cores:
68
+
69
+ | | **FaceOff** | pydeface | afni_refacer2 |
70
+ | --- | --- | --- | --- |
71
+ | Deface | **12 s** | 2 min 35 s | 2 min 54 s |
72
+ | Reface | **56 s** | not available | 2 min 59 s |
73
+ | Remove the eyes | **47 s** (`--enucleate`)<br>**59 s** with `--reface` | not available | not available |
74
+
75
+ - Median of 3 runs. FaceOff's reorientation to template orientation comes for free, with no interpolation.
76
+ - [Results](docs/results.md): the three tools' outputs side by side.
77
+
78
+ **Planned:** an option to use AFNI's refacer template and shell in place of FaceOff's own template.
79
+
80
+ ## More
81
+
82
+ [Installation](docs/installation.md) · [Usage](docs/usage.md) · [Outputs](docs/outputs.md) · [Results](docs/results.md) · [Template](docs/template.md) · [Development](docs/development.md) · [Citation](docs/citation.md)
83
+
84
+ ## Licence and citation
85
+
86
+ MIT licence; the template files keep the [MNI licence](faceoff/COPYING-MNI). If you use FaceOff, please cite it, the MNI ICBM152 template, and ANTs: [docs/citation.md](docs/citation.md).
@@ -0,0 +1,71 @@
1
+ # FaceOff [![Version](https://img.shields.io/badge/version-1.9.8-purple.svg)](https://github.com/srikash/FaceOff/releases/tag/v1.9.8) [![Licence](https://img.shields.io/badge/licence-MIT-orange.svg)](LICENCE) [![DOI](https://zenodo.org/badge/307797054.svg)](https://zenodo.org/badge/latestdoi/307797054)
2
+
3
+ Deface anatomical MRI scans with ANTs. FaceOff removes the face, and the eyes if you ask, and can put the template's face and eyes back.
4
+
5
+ - Reorients to template orientation and crops in the same run, with no interpolation.
6
+ - `--extra-images` (MP2RAGE, GRE echoes) and `--batch` (many scans).
7
+ - A QC page with every run.
8
+ - ANTs from your PATH, Docker, or Apptainer.
9
+
10
+ <p align="center">
11
+ <img src="https://gitlab.com/skash/images-and-gifs/-/raw/master/face-off/faceoff.gif" alt="Face swap scene from the film Face/Off">
12
+ <br>
13
+ <em>Face/Off (1997)</em>
14
+ </p>
15
+
16
+ ## Install
17
+
18
+ You need Python 3.12 or newer, and ANTs (2.3.3 or newer, tested with 2.6.5) on your PATH, Docker, or Apptainer (or Singularity).
19
+
20
+ ```bash
21
+ pip install "git+https://github.com/srikash/FaceOff.git"
22
+ faceoff --help
23
+ ```
24
+
25
+ The package will be called `faceoff-mri` on PyPI; `pip install faceoff-mri` works once the first release is published. The name `faceoff` on PyPI is a different program, so if you ran `pip install faceoff` earlier, run `pip uninstall faceoff` first.
26
+
27
+ A container image with ANTs inside is described in [docs/installation.md](docs/installation.md).
28
+
29
+ Conda, clusters, and Apptainer: [docs/installation.md](docs/installation.md).
30
+
31
+ ## Quick start
32
+
33
+ ```bash
34
+ faceoff -i sub-01_T1w.nii.gz # deface and reorient
35
+ faceoff -i sub-01_T1w.nii.gz --reface # also put a face back
36
+ faceoff -i sub-01_T1w.nii.gz --enucleate # remove the eyes too
37
+ faceoff -i sub-01_T1w.nii.gz --renucleate # remove the eyes and put template eyes back
38
+ faceoff --batch sub-*_T1w.nii.gz # several scans, one after another
39
+ faceoff -i sub-01_UNIT1.nii.gz --extra-images sub-01_inv-*_MP2RAGE.nii.gz # MP2RAGE or GRE echoes
40
+ ```
41
+
42
+ Outputs go next to the input: [docs/outputs.md](docs/outputs.md).
43
+
44
+ ## Example outputs
45
+
46
+ ![Sagittal sweep of FaceOff's outputs: defaced, with the eyes removed, refaced, and refaced with the template's eyes](docs/images/faceoff_examples.gif)
47
+
48
+ The template's face and eyes are shown in turbo.
49
+
50
+ ## How it compares
51
+
52
+ FaceOff, [pydeface](https://github.com/poldracklab/pydeface) 2.1.0, and AFNI's `afni_refacer2` on the same 1 mm MPRAGE, 12 cores:
53
+
54
+ | | **FaceOff** | pydeface | afni_refacer2 |
55
+ | --- | --- | --- | --- |
56
+ | Deface | **12 s** | 2 min 35 s | 2 min 54 s |
57
+ | Reface | **56 s** | not available | 2 min 59 s |
58
+ | Remove the eyes | **47 s** (`--enucleate`)<br>**59 s** with `--reface` | not available | not available |
59
+
60
+ - Median of 3 runs. FaceOff's reorientation to template orientation comes for free, with no interpolation.
61
+ - [Results](docs/results.md): the three tools' outputs side by side.
62
+
63
+ **Planned:** an option to use AFNI's refacer template and shell in place of FaceOff's own template.
64
+
65
+ ## More
66
+
67
+ [Installation](docs/installation.md) · [Usage](docs/usage.md) · [Outputs](docs/outputs.md) · [Results](docs/results.md) · [Template](docs/template.md) · [Development](docs/development.md) · [Citation](docs/citation.md)
68
+
69
+ ## Licence and citation
70
+
71
+ MIT licence; the template files keep the [MNI licence](faceoff/COPYING-MNI). If you use FaceOff, please cite it, the MNI ICBM152 template, and ANTs: [docs/citation.md](docs/citation.md).