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.
- faceoff_mri-1.9.8/.dockerignore +11 -0
- faceoff_mri-1.9.8/.github/workflows/ci.yml +130 -0
- faceoff_mri-1.9.8/.github/workflows/container.yml +94 -0
- faceoff_mri-1.9.8/.github/workflows/release.yml +108 -0
- faceoff_mri-1.9.8/.gitignore +9 -0
- faceoff_mri-1.9.8/CITATION.cff +11 -0
- faceoff_mri-1.9.8/Dockerfile +57 -0
- faceoff_mri-1.9.8/GLOSSARY.md +20 -0
- faceoff_mri-1.9.8/LICENCE +21 -0
- faceoff_mri-1.9.8/PKG-INFO +86 -0
- faceoff_mri-1.9.8/README.md +71 -0
- faceoff_mri-1.9.8/archive/FaceOff.sh +319 -0
- faceoff_mri-1.9.8/bundle_template.py +32 -0
- faceoff_mri-1.9.8/docs/citation.md +135 -0
- faceoff_mri-1.9.8/docs/development.md +38 -0
- faceoff_mri-1.9.8/docs/images/faceoff_examples.gif +0 -0
- faceoff_mri-1.9.8/docs/images/results_deface.gif +0 -0
- faceoff_mri-1.9.8/docs/images/results_reface.gif +0 -0
- faceoff_mri-1.9.8/docs/installation.md +97 -0
- faceoff_mri-1.9.8/docs/outputs.md +22 -0
- faceoff_mri-1.9.8/docs/results.md +61 -0
- faceoff_mri-1.9.8/docs/template.md +15 -0
- faceoff_mri-1.9.8/docs/usage.md +79 -0
- faceoff_mri-1.9.8/faceoff/COPYING-MNI +11 -0
- faceoff_mri-1.9.8/faceoff/__init__.py +1 -0
- faceoff_mri-1.9.8/faceoff/__main__.py +5 -0
- faceoff_mri-1.9.8/faceoff/ants.py +366 -0
- faceoff_mri-1.9.8/faceoff/cli.py +480 -0
- faceoff_mri-1.9.8/faceoff/deface.py +534 -0
- faceoff_mri-1.9.8/faceoff/images.py +15 -0
- faceoff_mri-1.9.8/faceoff/inputs.py +93 -0
- faceoff_mri-1.9.8/faceoff/masking.py +128 -0
- faceoff_mri-1.9.8/faceoff/messages.py +131 -0
- faceoff_mri-1.9.8/faceoff/qc.py +215 -0
- faceoff_mri-1.9.8/faceoff/regions.py +76 -0
- faceoff_mri-1.9.8/faceoff/registration.py +288 -0
- faceoff_mri-1.9.8/faceoff/reorient.py +169 -0
- faceoff_mri-1.9.8/faceoff/scratch.py +43 -0
- faceoff_mri-1.9.8/faceoff/template.py +114 -0
- faceoff_mri-1.9.8/faceoff/template_files.py +77 -0
- faceoff_mri-1.9.8/licences/ANTs-COPYING.txt +202 -0
- faceoff_mri-1.9.8/licences/ITK-LICENSE.txt +202 -0
- faceoff_mri-1.9.8/licences/ITK-NOTICE.txt +15 -0
- faceoff_mri-1.9.8/pyproject.toml +45 -0
- faceoff_mri-1.9.8/scripts/check_dist.py +59 -0
- faceoff_mri-1.9.8/tests/conftest.py +107 -0
- faceoff_mri-1.9.8/tests/test_ants.py +314 -0
- faceoff_mri-1.9.8/tests/test_check_dist.py +48 -0
- faceoff_mri-1.9.8/tests/test_cli.py +312 -0
- faceoff_mri-1.9.8/tests/test_deface.py +865 -0
- faceoff_mri-1.9.8/tests/test_dockerfile.py +23 -0
- faceoff_mri-1.9.8/tests/test_images.py +21 -0
- faceoff_mri-1.9.8/tests/test_inputs.py +78 -0
- faceoff_mri-1.9.8/tests/test_masking.py +154 -0
- faceoff_mri-1.9.8/tests/test_messages.py +40 -0
- faceoff_mri-1.9.8/tests/test_qc.py +76 -0
- faceoff_mri-1.9.8/tests/test_regions.py +38 -0
- faceoff_mri-1.9.8/tests/test_registration.py +188 -0
- faceoff_mri-1.9.8/tests/test_reorient.py +167 -0
- faceoff_mri-1.9.8/tests/test_scratch.py +24 -0
- faceoff_mri-1.9.8/tests/test_template.py +118 -0
- faceoff_mri-1.9.8/tests/test_versions.py +23 -0
- faceoff_mri-1.9.8/uv.lock +596 -0
|
@@ -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,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 [](https://github.com/srikash/FaceOff/releases/tag/v1.9.8) [](LICENCE) [](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
|
+

|
|
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 [](https://github.com/srikash/FaceOff/releases/tag/v1.9.8) [](LICENCE) [](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
|
+

|
|
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).
|