├── .github └── workflows │ └── build.yml ├── .gitignore ├── .maint ├── local_gitignore └── make_gitignore ├── CHANGES ├── LICENSE ├── README.rst ├── pyproject.toml ├── quickshear.pdf └── quickshear.py /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | # https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#concurrency 3 | # https://docs.github.com/en/developers/webhooks-and-events/events/github-event-types#pullrequestevent 4 | # workflow name, PR number (empty on push), push ref (empty on PR) 5 | concurrency: 6 | group: ${{ github.workflow }}-${{ github.event.number }}-${{ github.event.ref }} 7 | cancel-in-progress: true 8 | 9 | on: 10 | pull_request: 11 | push: 12 | branches: [main] 13 | workflow_dispatch: 14 | 15 | jobs: 16 | build: 17 | timeout-minutes: 30 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | os: [ubuntu, macos, windows] 22 | python-version: ["3.8", "3.11"] 23 | install: ['repo'] 24 | include: 25 | - os: ubuntu 26 | python-version: 3.11 27 | install: sdist 28 | - os: ubuntu 29 | python-version: 3.11 30 | install: wheel 31 | runs-on: ${{ matrix.os }}-latest 32 | defaults: 33 | run: 34 | shell: bash 35 | steps: 36 | - name: Checkout repository 37 | uses: actions/checkout@v3 38 | - name: Setup Python ${{ matrix.python-version }} 39 | uses: actions/setup-python@v4 40 | with: 41 | python-version: ${{ matrix.python-version }} 42 | architecture: 'x64' 43 | - run: pipx run build 44 | - run: pip install dist/*.whl 45 | if: ${{ matrix.install == 'wheel' }} 46 | - run: pip install dist/*.tar.gz 47 | if: ${{ matrix.install == 'sdist' }} 48 | - run: pip install . 49 | if: ${{ matrix.install == 'repo' }} 50 | - name: Test installed package 51 | run: | 52 | cd /tmp 53 | python -c "import quickshear; print(quickshear.__version__)" 54 | quickshear -h 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Auto-generated by .maint/make_gitignore on Tue May 16 11:55:31 AM EDT 2023 2 | 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | share/python-wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | MANIFEST 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .nox/ 45 | .coverage 46 | .coverage.* 47 | .cache 48 | nosetests.xml 49 | coverage.xml 50 | *.cover 51 | *.py,cover 52 | .hypothesis/ 53 | .pytest_cache/ 54 | cover/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | db.sqlite3-journal 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | .pybuilder/ 78 | target/ 79 | 80 | # Jupyter Notebook 81 | .ipynb_checkpoints 82 | 83 | # IPython 84 | profile_default/ 85 | ipython_config.py 86 | 87 | # pyenv 88 | # For a library or package, you might want to ignore these files since the code is 89 | # intended to run in multiple environments; otherwise, check them in: 90 | # .python-version 91 | 92 | # pipenv 93 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 94 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 95 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 96 | # install all needed dependencies. 97 | #Pipfile.lock 98 | 99 | # poetry 100 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 101 | # This is especially recommended for binary packages to ensure reproducibility, and is more 102 | # commonly ignored for libraries. 103 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 104 | #poetry.lock 105 | 106 | # pdm 107 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 108 | #pdm.lock 109 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 110 | # in version control. 111 | # https://pdm.fming.dev/#use-with-ide 112 | .pdm.toml 113 | 114 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 115 | __pypackages__/ 116 | 117 | # Celery stuff 118 | celerybeat-schedule 119 | celerybeat.pid 120 | 121 | # SageMath parsed files 122 | *.sage.py 123 | 124 | # Environments 125 | .env 126 | .venv 127 | env/ 128 | venv/ 129 | ENV/ 130 | env.bak/ 131 | venv.bak/ 132 | 133 | # Spyder project settings 134 | .spyderproject 135 | .spyproject 136 | 137 | # Rope project settings 138 | .ropeproject 139 | 140 | # mkdocs documentation 141 | /site 142 | 143 | # mypy 144 | .mypy_cache/ 145 | .dmypy.json 146 | dmypy.json 147 | 148 | # Pyre type checker 149 | .pyre/ 150 | 151 | # pytype static type analyzer 152 | .pytype/ 153 | 154 | # Cython debug symbols 155 | cython_debug/ 156 | 157 | # PyCharm 158 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 159 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 160 | # and can be added to the global gitignore or merged into this file. For a more nuclear 161 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 162 | #.idea/ 163 | 164 | # -*- mode: gitignore; -*- 165 | *~ 166 | \#*\# 167 | /.emacs.desktop 168 | /.emacs.desktop.lock 169 | *.elc 170 | auto-save-list 171 | tramp 172 | .\#* 173 | 174 | # Org-mode 175 | .org-id-locations 176 | *_archive 177 | 178 | # flymake-mode 179 | *_flymake.* 180 | 181 | # eshell files 182 | /eshell/history 183 | /eshell/lastdir 184 | 185 | # elpa packages 186 | /elpa/ 187 | 188 | # reftex files 189 | *.rel 190 | 191 | # AUCTeX auto folder 192 | /auto/ 193 | 194 | # cask packages 195 | .cask/ 196 | dist/ 197 | 198 | # Flycheck 199 | flycheck_*.el 200 | 201 | # server auth directory 202 | /server/ 203 | 204 | # projectiles files 205 | .projectile 206 | 207 | # directory configuration 208 | .dir-locals.el 209 | 210 | # network security 211 | /network-security.data 212 | 213 | 214 | *~ 215 | 216 | # temporary files which can be created if a process still has a handle open of a deleted file 217 | .fuse_hidden* 218 | 219 | # KDE directory preferences 220 | .directory 221 | 222 | # Linux trash folder which might appear on any partition or disk 223 | .Trash-* 224 | 225 | # .nfs files are created when an open file is removed but is still being accessed 226 | .nfs* 227 | 228 | # Swap 229 | [._]*.s[a-v][a-z] 230 | !*.svg # comment out if you don't need vector files 231 | [._]*.sw[a-p] 232 | [._]s[a-rt-v][a-z] 233 | [._]ss[a-gi-z] 234 | [._]sw[a-p] 235 | 236 | # Session 237 | Session.vim 238 | Sessionx.vim 239 | 240 | # Temporary 241 | .netrwhist 242 | *~ 243 | # Auto-generated tag files 244 | tags 245 | # Persistent undo 246 | [._]*.un~ 247 | 248 | .vscode/* 249 | !.vscode/settings.json 250 | !.vscode/tasks.json 251 | !.vscode/launch.json 252 | !.vscode/extensions.json 253 | !.vscode/*.code-snippets 254 | 255 | # Local History for Visual Studio Code 256 | .history/ 257 | 258 | # Built Visual Studio Code Extensions 259 | *.vsix 260 | 261 | # Windows thumbnail cache files 262 | Thumbs.db 263 | Thumbs.db:encryptable 264 | ehthumbs.db 265 | ehthumbs_vista.db 266 | 267 | # Dump file 268 | *.stackdump 269 | 270 | # Folder config file 271 | [Dd]esktop.ini 272 | 273 | # Recycle Bin used on file shares 274 | $RECYCLE.BIN/ 275 | 276 | # Windows Installer files 277 | *.cab 278 | *.msi 279 | *.msix 280 | *.msm 281 | *.msp 282 | 283 | # Windows shortcuts 284 | *.lnk 285 | 286 | # General 287 | .DS_Store 288 | .AppleDouble 289 | .LSOverride 290 | 291 | # Icon must end with two \r 292 | Icon 293 | 294 | # Thumbnails 295 | ._* 296 | 297 | # Files that might appear in the root of a volume 298 | .DocumentRevisions-V100 299 | .fseventsd 300 | .Spotlight-V100 301 | .TemporaryItems 302 | .Trashes 303 | .VolumeIcon.icns 304 | .com.apple.timemachine.donotpresent 305 | 306 | # Directories potentially created on remote AFP share 307 | .AppleDB 308 | .AppleDesktop 309 | Network Trash Folder 310 | Temporary Items 311 | .apdisk 312 | -------------------------------------------------------------------------------- /.maint/local_gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipy/quickshear/83b362b794d52183ff40ec5dcc98239b94c5633a/.maint/local_gitignore -------------------------------------------------------------------------------- /.maint/make_gitignore: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Generate .gitignore from GitHub gitignore templates 4 | 5 | BASE_URL="https://raw.githubusercontent.com/github/gitignore/main" 6 | ROOT=$( dirname $( dirname $( realpath $0 ) ) ) 7 | 8 | SOURCES=( 9 | Python 10 | Global/Emacs 11 | Global/Linux 12 | Global/Vim 13 | Global/VisualStudioCode 14 | Global/Windows 15 | Global/macOS 16 | ) 17 | 18 | cat >$ROOT/.gitignore <> $ROOT/.gitignore 23 | 24 | for SRC in ${SOURCES[@]}; do 25 | echo >> $ROOT/.gitignore 26 | curl -sSL ${BASE_URL}/${SRC}.gitignore >> $ROOT/.gitignore 27 | done 28 | -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | Version 1.2.0 2 | ============= 3 | 4 | This will be the last version to support Python 3.7 or lower. 5 | 6 | - Refactor to use nibabel orientation code, remove deprecated functions 7 | (https://github.com/nipy/quickshear/pull/14) 8 | - Move to Hatchling as build backend (https://github.com/nipy/quickshear/pull/13) 9 | 10 | Version 1.1.0 11 | ============= 12 | 13 | - Significant refactor to modularize and modernize code (https://github.com/nipy/quickshear/pull/2) 14 | - Add duecredit information for citations (https://github.com/nipy/quickshear/pull/6) 15 | - Improve robustness to input image orientations (https://github.com/nipy/quickshear/pull/7) 16 | 17 | Version 1.0.0 18 | ============= 19 | 20 | Initial release 21 | 22 | Thanks to Nakeisha Schimke and John Hale for sharing their code and agreeing to allow me to publish it. 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011, Nakeisha Schimke. All rights reserved. 2 | Redistribution and use in source and binary forms, with or without 3 | modification, are permitted provided that the following conditions are met: 4 | * Redistributions of source code must retain the above copyright notice, this 5 | list of conditions and the following disclaimer. 6 | * Redistributions in binary form must reproduce the above copyright notice, 7 | this list of conditions and the following disclaimer in the documentation 8 | and/or other materials provided with the distribution. 9 | * Neither the name of the The University of Tulsa nor the names of its 10 | contributors may be used to endorse or promote products derived from this 11 | software without specific prior written permission. 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 14 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 15 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 16 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 17 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 18 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 19 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 20 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 22 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Quickshear 2 | ---------- 3 | Quickshear uses a skull stripped version of an anatomical images as a reference 4 | to deface the unaltered anatomical image. 5 | 6 | Usage:: 7 | 8 | quickshear.py [-h] anat_file mask_file defaced_file [buffer] 9 | 10 | Quickshear defacing for neuroimages 11 | 12 | positional arguments: 13 | anat_file filename of neuroimage to deface 14 | mask_file filename of brain mask 15 | defaced_file filename of defaced output image 16 | buffer buffer size (in voxels) between shearing plane and the brain 17 | (default: 10.0) 18 | 19 | optional arguments: 20 | -h, --help show this help message and exit 21 | 22 | For a full description, see the following paper: 23 | 24 | .. [Schimke2011] Schimke, Nakeisha, and John Hale. "Quickshear defacing for neuroimages." 25 | Proceedings of the 2nd USENIX conference on Health security and privacy. 26 | USENIX Association, 2011. 27 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "quickshear" 7 | dynamic = ["version"] 8 | requires-python = ">= 3.8" 9 | description = "Quickshear Defacing for Neuroimages" 10 | readme = "README.rst" 11 | license = { file = "LICENSE" } 12 | authors = [ 13 | { name = "Nakeisha Schimke"}, 14 | { name = "John Hale" }, 15 | ] 16 | maintainers = [ 17 | { name = "Christopher J. Markiewicz" }, 18 | { email = "neuroimaging@python.org" }, 19 | ] 20 | keywords = [ 21 | "neuroimaging", 22 | "neuroscience", 23 | ] 24 | classifiers = [ 25 | "Development Status :: 5 - Production/Stable", 26 | "Intended Audience :: Science/Research", 27 | "License :: OSI Approved :: BSD License", 28 | "Programming Language :: Python :: 3", 29 | "Programming Language :: Python :: 3.8", 30 | "Programming Language :: Python :: 3.9", 31 | "Programming Language :: Python :: 3.10", 32 | "Programming Language :: Python :: 3.11", 33 | "Topic :: Software Development :: Build Tools", 34 | ] 35 | dependencies = [ 36 | "nibabel", 37 | "numpy", 38 | ] 39 | 40 | [project.scripts] 41 | quickshear = "quickshear:main" 42 | 43 | [project.urls] 44 | Homepage = "https://github.com/nipy/quickshear" 45 | 46 | [tool.hatch.version] 47 | path = "quickshear.py" 48 | 49 | [tool.hatch.build.targets.sdist] 50 | exclude = [ 51 | ".*", 52 | "quickshear.pdf", 53 | ] 54 | -------------------------------------------------------------------------------- /quickshear.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipy/quickshear/83b362b794d52183ff40ec5dcc98239b94c5633a/quickshear.pdf -------------------------------------------------------------------------------- /quickshear.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import sys 3 | import argparse 4 | import numpy as np 5 | import nibabel as nb 6 | import logging 7 | 8 | try: 9 | from due import due, BibTeX 10 | except ImportError: 11 | # Adapted from 12 | # https://github.com/duecredit/duecredit/blob/2221bfd/duecredit/stub.py 13 | class InactiveDueCreditCollector: 14 | """Just a stub at the Collector which would not do anything""" 15 | 16 | def _donothing(self, *args, **kwargs): 17 | """Perform no good and no bad""" 18 | pass 19 | 20 | def dcite(self, *args, **kwargs): 21 | """If I could cite I would""" 22 | 23 | def nondecorating_decorator(func): 24 | return func 25 | 26 | return nondecorating_decorator 27 | 28 | cite = load = add = _donothing 29 | 30 | def __repr__(self): 31 | return self.__class__.__name__ + '()' 32 | 33 | due = InactiveDueCreditCollector() 34 | 35 | def BibTeX(*args, **kwargs): 36 | pass 37 | 38 | 39 | citation_text = """@inproceedings{Schimke2011, 40 | abstract = {Data sharing offers many benefits to the neuroscience research 41 | community. It encourages collaboration and interorganizational research 42 | efforts, enables reproducibility and peer review, and allows meta-analysis and 43 | data reuse. However, protecting subject privacy and implementing HIPAA 44 | compliance measures can be a burdensome task. For high resolution structural 45 | neuroimages, subject privacy is threatened by the neuroimage itself, which can 46 | contain enough facial features to re-identify an individual. To sufficiently 47 | de-identify an individual, the neuroimage pixel data must also be removed. 48 | Quickshear Defacing accomplishes this task by effectively shearing facial 49 | features while preserving desirable brain tissue.}, 50 | address = {San Francisco}, 51 | author = {Schimke, Nakeisha and Hale, John}, 52 | booktitle = {Proceedings of the 2nd USENIX Conference on Health Security and Privacy}, 53 | title = {{Quickshear Defacing for Neuroimages}}, 54 | year = {2011}, 55 | month = sep 56 | } 57 | """ 58 | __version__ = '1.3.0.dev0' 59 | 60 | 61 | def edge_mask(mask): 62 | """Find the edges of a mask or masked image 63 | 64 | Parameters 65 | ---------- 66 | mask : 3D array 67 | Binary mask (or masked image) with axis orientation LPS or RPS, and the 68 | non-brain region set to 0 69 | 70 | Returns 71 | ------- 72 | 2D array 73 | Outline of sagittal profile (PS orientation) of mask 74 | """ 75 | # Sagittal profile 76 | brain = mask.any(axis=0) 77 | 78 | # Simple edge detection 79 | edgemask = ( 80 | 4 * brain 81 | - np.roll(brain, 1, 0) 82 | - np.roll(brain, -1, 0) 83 | - np.roll(brain, 1, 1) 84 | - np.roll(brain, -1, 1) 85 | != 0 86 | ) 87 | return edgemask.astype('uint8') 88 | 89 | 90 | def convex_hull(brain): 91 | """Find the lower half of the convex hull of non-zero points 92 | 93 | Implements Andrew's monotone chain algorithm [0]. 94 | 95 | [0] https://en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Convex_hull/Monotone_chain 96 | 97 | Parameters 98 | ---------- 99 | brain : 2D array 100 | 2D array in PS axis ordering 101 | 102 | Returns 103 | ------- 104 | (2, N) array 105 | Sequence of points in the lower half of the convex hull of brain 106 | """ 107 | # convert brain to a list of points in an n x 2 matrix where n_i = (x,y) 108 | pts = np.vstack(np.nonzero(brain)).T 109 | 110 | def cross(o, a, b): 111 | return np.cross(a - o, b - o) 112 | 113 | lower = [] 114 | for p in pts: 115 | while len(lower) >= 2 and cross(lower[-2], lower[-1], p) <= 0: 116 | lower.pop() 117 | lower.append(p) 118 | 119 | return np.array(lower).T 120 | 121 | 122 | @due.dcite( 123 | BibTeX(citation_text), 124 | description='Geometric neuroimage defacer', 125 | path='quickshear', 126 | ) 127 | def quickshear(anat_img, mask_img, buff=10): 128 | """Deface image using Quickshear algorithm 129 | 130 | Parameters 131 | ---------- 132 | anat_img : SpatialImage 133 | Nibabel image of anatomical scan, to be defaced 134 | mask_img : SpatialImage 135 | Nibabel image of skull-stripped brain mask or masked anatomical 136 | buff : int 137 | Distance from mask to set shearing plane 138 | 139 | Returns 140 | ------- 141 | SpatialImage 142 | Nibabel image of defaced anatomical scan 143 | """ 144 | src_ornt = nb.io_orientation(mask_img.affine) 145 | tgt_ornt = nb.orientations.axcodes2ornt('RPS') 146 | to_RPS = nb.orientations.ornt_transform(src_ornt, tgt_ornt) 147 | from_RPS = nb.orientations.ornt_transform(tgt_ornt, src_ornt) 148 | 149 | mask_RPS = nb.orientations.apply_orientation(mask_img.dataobj, to_RPS) 150 | 151 | edgemask = edge_mask(mask_RPS) 152 | low = convex_hull(edgemask) 153 | xdiffs, ydiffs = np.diff(low) 154 | slope = ydiffs[0] / xdiffs[0] 155 | 156 | yint = low[1][0] - (low[0][0] * slope) - buff 157 | ys = np.arange(0, mask_RPS.shape[2]) * slope + yint 158 | defaced_mask_RPS = np.ones(mask_RPS.shape, dtype='bool') 159 | 160 | for x, y in zip(np.nonzero(ys > 0)[0], ys.astype(int)): 161 | defaced_mask_RPS[:, x, :y] = 0 162 | 163 | defaced_mask = nb.orientations.apply_orientation(defaced_mask_RPS, from_RPS) 164 | 165 | return anat_img.__class__( 166 | np.asanyarray(anat_img.dataobj) * defaced_mask, 167 | anat_img.affine, 168 | anat_img.header, 169 | ) 170 | 171 | 172 | def main(): 173 | logger = logging.getLogger(__name__) 174 | logger.setLevel(logging.DEBUG) 175 | ch = logging.StreamHandler() 176 | ch.setLevel(logging.DEBUG) 177 | logger.addHandler(ch) 178 | 179 | parser = argparse.ArgumentParser( 180 | description='Quickshear defacing for neuroimages', 181 | formatter_class=argparse.ArgumentDefaultsHelpFormatter, 182 | ) 183 | parser.add_argument( 184 | 'anat_file', type=str, help='filename of neuroimage to deface' 185 | ) 186 | parser.add_argument('mask_file', type=str, help='filename of brain mask') 187 | parser.add_argument( 188 | 'defaced_file', type=str, help='filename of defaced output image' 189 | ) 190 | parser.add_argument( 191 | 'buffer', 192 | type=float, 193 | nargs='?', 194 | default=10.0, 195 | help='buffer size (in voxels) between shearing plane and the brain', 196 | ) 197 | 198 | opts = parser.parse_args() 199 | 200 | anat_img = nb.load(opts.anat_file) 201 | mask_img = nb.load(opts.mask_file) 202 | 203 | if not ( 204 | anat_img.shape == mask_img.shape 205 | and np.allclose(anat_img.affine, mask_img.affine) 206 | ): 207 | logger.warning( 208 | 'Anatomical and mask images do not have the same shape and affine.' 209 | ) 210 | return -1 211 | 212 | new_anat = quickshear(anat_img, mask_img, opts.buffer) 213 | new_anat.to_filename(opts.defaced_file) 214 | logger.info(f'Defaced file: {opts.defaced_file}') 215 | 216 | 217 | if __name__ == '__main__': 218 | sys.exit(main()) 219 | --------------------------------------------------------------------------------