From e91a1328f98be58ba261b6b64a15d05db94f65d4 Mon Sep 17 00:00:00 2001 From: Will Graham <32364977+willGraham01@users.noreply.github.com> Date: Mon, 6 Nov 2023 12:09:23 +0000 Subject: [PATCH] Version 1 release :partying_face: (#146) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Merge `brainreg-napari` into `brainreg` (#145) * initial commit * adds gui * removes deprecated folder * Update README.md * Update README.md * adds brainreg to requirements * renames gui label from register to brainreg * renames register to brainreg-register * adds brainglobe repo to setup.py * Delete requirements.txt * makes output destination selectable in GUI * Update entry point * removes sort_input_file option * Update setup.py * updates project urls in setup.cfg * Update support link * fixes output path to directory * re adds sort_input_file as hardcoded variable * adds atlas options as dropdown * adds showing the results of registration * removed a global declaration * minor refactoring * deleted commented code * fixes logging output * only loads relevant output data * outputs scaled overlay of boundaries and atlas * imports and groups segment plugin with register * lowers requirements from python >=3.7 to >=3.6 and removes project url from setup.py * removes superfluous pixel args from logging * Update readme, setup.py & general reformatting (#16) * update gitignore * Update setup.py * update readme * format with black * remove docs * remove unused import * delete tests for now * format imports * Release (#18) * Update setup.py * add bump2version config * Bump version: 0.0.1 → 0.0.2-rc0 * remove reqs from manifest * disable tests on GH actions for now * fixes reset_defaults by ignoring atlas dropdown * adds additional channels functionality * Update OS compatibility & update use of napari API * Bump (#23) * Bump version: 0.0.2-rc1 → 0.0.2-rc2 * Bump version: 0.0.2-rc2 → 0.0.2-rc3 * Bump version: 0.0.2-rc3 → 0.0.3-rc0 * Add tooltips (#25) * change default parameter names * Add tooltips * Bump version: 0.0.3-rc0 → 0.0.4-rc0 * Update README.md * Update broken image URL * Add categories and preview page workflow for the napari hub (#27) * adds EDAM Bioimaging labels * adds Action for napari hub preview service * Update citation * Update README.md * Convert to npe2 * Add pyqt library setup * Fix manifest * Add basic smoke test * Add link to contributing guide * Add pre-commit * Fix manifest * Add sample data * Add example script * Fix pre-commit and manifest * Use g-node.org for sample data * Clean up example Make noqa more specific * Set plugin values from metadata * Add a test for sample data * Add pooch to requirements Move pooch req comment * Test loading sample data through napari * Set sensible contrast limits * pre-commit fixes * Add Python 3.10 support, drop 3.7 support * Add an integration test Type block * Fix getting additional layers * Factor out add_image_layers function * Remove arguments from run() * Refactor blocking/non-blocking behaviour * Store metadata in napari layers * Use re-usable action for running tests * Give better name to add_image_layers * Add error if trying to load non-existent registration * Return layers from add_registered_image_layers * Bump version: 0.0.4-rc0 → 0.0.5-rc0 * Input orientation check (#44) * Add many features: - debug mode - interactive way of checking input orientation - brain geometry - save annotations with original orientation * add comments and docstring. * Crop the reference atlas when using interactive orientation check with half brains. * Fix a bug where run throw an error. * Fix typo in docstrings * format * Add info to use if data not loaded before checking orientation * Update docstrings Co-authored-by: Jules Scholler * Update readme * format readme * Add codecov badge * Added citation file (#45) * Added citation file * Changes according to PR comments * Updated Citation file Updated citation file to have the preferred citation coming from the article/book when available Co-authored-by: Simão sá * Update packaging (#46) * update pre-commit config * update args for new brainreg version * lint * update manifest * Bump version: 0.0.5-rc0 → 0.0.5-rc1 * update testing * update packaging * update requirements * update manifest * move entry point to pyproject.toml * update hub preview version * update release for use with setuptools_scm * migrate from BG to neuroinformatics actions * add setuptools_scm to dev requirements * reduce number of tested version * update python versions tested * update release * [Napari HUB cli] Metadata enhancement suggestions (#47) * Copy "Display Name" from secondary to primary file Display Name was found in a secondary file and copied in the primary one: * (found here) "pyproject.toml" * (copied here) "brainreg_napari/napari.yaml" * Copy "Summary Sentence" from secondary to primary file Summary Sentence was found in a secondary file and copied in the primary one: * (found here) "pyproject.toml" * (copied here) ".napari/config.yml" * Use standard pre-commit config (#51) * Add standard pre-commit config * Automated pre-commit fixes * Testing clean (#50) * Use composite test action Fix use-xvfb argument * Clean tox.ini * Don't fail fast * Remove old setup steps * Clean up dev dependencies Add pyqt5 back as a dev dep * Try passing display env Put back passenv in tox * Use selected atlas to check orientation * Add orientation check tests * [pre-commit.ci] pre-commit autoupdate (#57) updates: - [github.com/charliermarsh/ruff-pre-commit: v0.0.240 → v0.0.272](https://github.com/charliermarsh/ruff-pre-commit/compare/v0.0.240...v0.0.272) - [github.com/psf/black: 23.1.0 → 23.3.0](https://github.com/psf/black/compare/23.1.0...23.3.0) - [github.com/pre-commit/mirrors-mypy: v0.991 → v1.3.0](https://github.com/pre-commit/mirrors-mypy/compare/v0.991...v1.3.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * Update links (#56) * Update README.md * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * Update dependency (imlib -> brainglobe-utils) (#58) * [pre-commit.ci] pre-commit autoupdate (#59) updates: - [github.com/charliermarsh/ruff-pre-commit: v0.0.272 → v0.0.275](https://github.com/charliermarsh/ruff-pre-commit/compare/v0.0.272...v0.0.275) - [github.com/pre-commit/mirrors-mypy: v1.3.0 → v1.4.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.3.0...v1.4.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * [pre-commit.ci] pre-commit autoupdate (#60) updates: - https://github.com/charliermarsh/ruff-pre-commit → https://github.com/astral-sh/ruff-pre-commit - [github.com/astral-sh/ruff-pre-commit: v0.0.275 → v0.0.276](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.275...v0.0.276) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * [pre-commit.ci] pre-commit autoupdate (#61) updates: - [github.com/astral-sh/ruff-pre-commit: v0.0.276 → v0.0.277](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.276...v0.0.277) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * [pre-commit.ci] pre-commit autoupdate (#62) updates: - [github.com/astral-sh/ruff-pre-commit: v0.0.277 → v0.0.278](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.277...v0.0.278) - [github.com/psf/black: 23.3.0 → 23.7.0](https://github.com/psf/black/compare/23.3.0...23.7.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * [pre-commit.ci] pre-commit autoupdate (#63) updates: - [github.com/astral-sh/ruff-pre-commit: v0.0.278 → v0.0.280](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.278...v0.0.280) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * [pre-commit.ci] pre-commit autoupdate (#65) updates: - [github.com/astral-sh/ruff-pre-commit: v0.0.280 → v0.0.281](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.280...v0.0.281) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * [pre-commit.ci] pre-commit autoupdate (#66) updates: - [github.com/astral-sh/ruff-pre-commit: v0.0.281 → v0.0.282](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.281...v0.0.282) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * [pre-commit.ci] pre-commit autoupdate (#67) updates: - [github.com/astral-sh/ruff-pre-commit: v0.0.282 → v0.0.284](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.282...v0.0.284) - [github.com/pre-commit/mirrors-mypy: v1.4.1 → v1.5.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.4.1...v1.5.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * [pre-commit.ci] pre-commit autoupdate (#68) updates: - [github.com/astral-sh/ruff-pre-commit: v0.0.284 → v0.0.285](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.284...v0.0.285) - [github.com/pre-commit/mirrors-mypy: v1.5.0 → v1.5.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.5.0...v1.5.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * [pre-commit.ci] pre-commit autoupdate (#70) updates: - [github.com/astral-sh/ruff-pre-commit: v0.0.285 → v0.0.286](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.285...v0.0.286) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * Update .pre-commit-config.yaml (#71) * [pre-commit.ci] pre-commit autoupdate (#72) updates: - [github.com/astral-sh/ruff-pre-commit: v0.0.286 → v0.0.287](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.286...v0.0.287) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * [pre-commit.ci] pre-commit autoupdate (#74) updates: - [github.com/astral-sh/ruff-pre-commit: v0.0.287 → v0.0.292](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.287...v0.0.292) - [github.com/psf/black: 23.7.0 → 23.9.1](https://github.com/psf/black/compare/23.7.0...23.9.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * Test on Python 3.11 (#49) * Use composite test action Fix use-xvfb argument * Clean tox.ini * Don't fail fast * Remove old setup steps * Clean up dev dependencies Add pyqt5 back as a dev dep * Try passing display env Put back passenv in tox * Test on Python 3.11 * Update Python versions --------- Co-authored-by: Adam Tyson * Delete .github/workflows/plugin_preview.yml (#75) * Write safe concat function * Use safe concat of dataframes * Fix logic of safe concat method * pre-commit pass * Add unit test to aplease codecov overlords * Restructure as brainreg/core, adjust imports accordingly * Make package installable again * Fix test import * Rework package structure to be compatible with brainreg-core * Update example code with new structure * Rename napari plugin to drop the -napari suffix * Exclude the to-be-reconciled READMEs from the manifest --------- Co-authored-by: stephen Co-authored-by: Adam Tyson Co-authored-by: Adam Tyson Co-authored-by: Justin Kiggins Co-authored-by: David Stansby Co-authored-by: Patrick Roddy Co-authored-by: Alessandro Felder Co-authored-by: Jules Scholler Co-authored-by: Simão Bolota <99416933+SimaoBolota-MetaCell@users.noreply.github.com> Co-authored-by: Simão sá Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * Reconcile the `core` and `napari` READMEs (#147) * Parse existing brainreg readme * Incorporate plugin readme and update manifest * Apply Adam's updates Co-authored-by: Adam Tyson --------- Co-authored-by: Adam Tyson * Reconcile the `core` and `napari` READMEs (#147) * Parse existing brainreg readme * Incorporate plugin readme and update manifest * Apply Adam's updates Co-authored-by: Adam Tyson --------- Co-authored-by: Adam Tyson * Update README.md * Update with brainreg-segment rename to brainglobe-segmentation (#149) * Update with brainreg-segment rename to brainglobe-segmentation * Update pyproject.toml --------- Co-authored-by: stephen Co-authored-by: Adam Tyson Co-authored-by: Adam Tyson Co-authored-by: Justin Kiggins Co-authored-by: David Stansby Co-authored-by: Patrick Roddy Co-authored-by: Alessandro Felder Co-authored-by: Jules Scholler Co-authored-by: Simão Bolota <99416933+SimaoBolota-MetaCell@users.noreply.github.com> Co-authored-by: Simão sá Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .flake8 | 4 + .github/workflows/test_and_deploy.yml | 8 +- .gitignore | 3 +- .napari/config.yml | 7 + CITATION.cff | 102 +++ MANIFEST.in | 23 +- README.md | 161 +++-- {src/brainreg => brainreg}/__init__.py | 2 +- brainreg/core/__init__.py | 1 + .../core}/backend/__init__.py | 0 .../core}/backend/niftyreg/__init__.py | 0 .../backend/niftyreg/niftyreg_binaries.py | 0 .../core}/backend/niftyreg/parameters.py | 0 .../core}/backend/niftyreg/parser.py | 0 .../core}/backend/niftyreg/paths.py | 0 .../core}/backend/niftyreg/registration.py | 0 .../core}/backend/niftyreg/run.py | 10 +- .../core}/backend/niftyreg/utils.py | 0 .../core}/bin/nifty_reg/LICENSE.txt | 0 .../core}/bin/nifty_reg/linux_x64/reg_aladin | Bin .../core}/bin/nifty_reg/linux_x64/reg_average | Bin .../core}/bin/nifty_reg/linux_x64/reg_f3d | Bin .../bin/nifty_reg/linux_x64/reg_jacobian | Bin .../core}/bin/nifty_reg/linux_x64/reg_measure | Bin .../bin/nifty_reg/linux_x64/reg_resample | Bin .../core}/bin/nifty_reg/linux_x64/reg_tools | Bin .../bin/nifty_reg/linux_x64/reg_transform | Bin .../core}/bin/nifty_reg/niftyreg-git.zip | Bin .../core}/bin/nifty_reg/niftyreg_version.txt | 0 .../core}/bin/nifty_reg/osX/reg_aladin | Bin .../core}/bin/nifty_reg/osX/reg_average | Bin .../core}/bin/nifty_reg/osX/reg_f3d | Bin .../core}/bin/nifty_reg/osX/reg_jacobian | Bin .../core}/bin/nifty_reg/osX/reg_measure | Bin .../core}/bin/nifty_reg/osX/reg_resample | Bin .../core}/bin/nifty_reg/osX/reg_tools | Bin .../core}/bin/nifty_reg/osX/reg_transform | Bin .../core}/bin/nifty_reg/win64/reg_aladin.exe | Bin .../core}/bin/nifty_reg/win64/reg_average.exe | Bin .../core}/bin/nifty_reg/win64/reg_f3d.exe | Bin .../bin/nifty_reg/win64/reg_jacobian.exe | Bin .../core}/bin/nifty_reg/win64/reg_measure.exe | Bin .../bin/nifty_reg/win64/reg_resample.exe | Bin .../core}/bin/nifty_reg/win64/reg_tools.exe | Bin .../bin/nifty_reg/win64/reg_transform.exe | Bin .../bin/nifty_reg/win64/vcruntime140_1.dll | Bin {src/brainreg => brainreg/core}/cli.py | 8 +- {src/brainreg => brainreg/core}/exceptions.py | 0 {src/brainreg => brainreg/core}/main.py | 11 +- {src/brainreg => brainreg/core}/paths.py | 0 .../core}/utils/__init__.py | 0 .../core}/utils/boundaries.py | 0 {src/brainreg => brainreg/core}/utils/misc.py | 0 .../core}/utils/preprocess.py | 0 .../core}/utils/volume.py | 0 brainreg/napari/__init__.py | 0 brainreg/napari/napari.yaml | 18 + brainreg/napari/register.py | 583 ++++++++++++++++++ brainreg/napari/sample_data.py | 42 ++ brainreg/napari/util.py | 88 +++ examples/load_sample_data.py | 39 ++ pyproject.toml | 53 +- tests/test_brainreg_napari.py | 161 +++++ .../test_backend/test_niftyreg_detection.py | 2 +- .../test_integration/test_registration.py | 3 +- tests/tests/test_unit/test_exceptions.py | 5 +- tox.ini | 9 + 67 files changed, 1216 insertions(+), 127 deletions(-) create mode 100644 .flake8 create mode 100644 .napari/config.yml create mode 100644 CITATION.cff rename {src/brainreg => brainreg}/__init__.py (92%) create mode 100644 brainreg/core/__init__.py rename {src/brainreg => brainreg/core}/backend/__init__.py (100%) rename {src/brainreg => brainreg/core}/backend/niftyreg/__init__.py (100%) rename {src/brainreg => brainreg/core}/backend/niftyreg/niftyreg_binaries.py (100%) rename {src/brainreg => brainreg/core}/backend/niftyreg/parameters.py (100%) rename {src/brainreg => brainreg/core}/backend/niftyreg/parser.py (100%) rename {src/brainreg => brainreg/core}/backend/niftyreg/paths.py (100%) rename {src/brainreg => brainreg/core}/backend/niftyreg/registration.py (100%) rename {src/brainreg => brainreg/core}/backend/niftyreg/run.py (96%) rename {src/brainreg => brainreg/core}/backend/niftyreg/utils.py (100%) rename {src/brainreg => brainreg/core}/bin/nifty_reg/LICENSE.txt (100%) rename {src/brainreg => brainreg/core}/bin/nifty_reg/linux_x64/reg_aladin (100%) rename {src/brainreg => brainreg/core}/bin/nifty_reg/linux_x64/reg_average (100%) rename {src/brainreg => brainreg/core}/bin/nifty_reg/linux_x64/reg_f3d (100%) rename {src/brainreg => brainreg/core}/bin/nifty_reg/linux_x64/reg_jacobian (100%) rename {src/brainreg => brainreg/core}/bin/nifty_reg/linux_x64/reg_measure (100%) rename {src/brainreg => brainreg/core}/bin/nifty_reg/linux_x64/reg_resample (100%) rename {src/brainreg => brainreg/core}/bin/nifty_reg/linux_x64/reg_tools (100%) rename {src/brainreg => brainreg/core}/bin/nifty_reg/linux_x64/reg_transform (100%) rename {src/brainreg => brainreg/core}/bin/nifty_reg/niftyreg-git.zip (100%) rename {src/brainreg => brainreg/core}/bin/nifty_reg/niftyreg_version.txt (100%) rename {src/brainreg => brainreg/core}/bin/nifty_reg/osX/reg_aladin (100%) rename {src/brainreg => brainreg/core}/bin/nifty_reg/osX/reg_average (100%) rename {src/brainreg => brainreg/core}/bin/nifty_reg/osX/reg_f3d (100%) rename {src/brainreg => brainreg/core}/bin/nifty_reg/osX/reg_jacobian (100%) rename {src/brainreg => brainreg/core}/bin/nifty_reg/osX/reg_measure (100%) rename {src/brainreg => brainreg/core}/bin/nifty_reg/osX/reg_resample (100%) rename {src/brainreg => brainreg/core}/bin/nifty_reg/osX/reg_tools (100%) rename {src/brainreg => brainreg/core}/bin/nifty_reg/osX/reg_transform (100%) rename {src/brainreg => brainreg/core}/bin/nifty_reg/win64/reg_aladin.exe (100%) rename {src/brainreg => brainreg/core}/bin/nifty_reg/win64/reg_average.exe (100%) rename {src/brainreg => brainreg/core}/bin/nifty_reg/win64/reg_f3d.exe (100%) rename {src/brainreg => brainreg/core}/bin/nifty_reg/win64/reg_jacobian.exe (100%) rename {src/brainreg => brainreg/core}/bin/nifty_reg/win64/reg_measure.exe (100%) rename {src/brainreg => brainreg/core}/bin/nifty_reg/win64/reg_resample.exe (100%) rename {src/brainreg => brainreg/core}/bin/nifty_reg/win64/reg_tools.exe (100%) rename {src/brainreg => brainreg/core}/bin/nifty_reg/win64/reg_transform.exe (100%) rename {src/brainreg => brainreg/core}/bin/nifty_reg/win64/vcruntime140_1.dll (100%) rename {src/brainreg => brainreg/core}/cli.py (96%) rename {src/brainreg => brainreg/core}/exceptions.py (100%) rename {src/brainreg => brainreg/core}/main.py (91%) rename {src/brainreg => brainreg/core}/paths.py (100%) rename {src/brainreg => brainreg/core}/utils/__init__.py (100%) rename {src/brainreg => brainreg/core}/utils/boundaries.py (100%) rename {src/brainreg => brainreg/core}/utils/misc.py (100%) rename {src/brainreg => brainreg/core}/utils/preprocess.py (100%) rename {src/brainreg => brainreg/core}/utils/volume.py (100%) create mode 100644 brainreg/napari/__init__.py create mode 100644 brainreg/napari/napari.yaml create mode 100644 brainreg/napari/register.py create mode 100644 brainreg/napari/sample_data.py create mode 100644 brainreg/napari/util.py create mode 100644 examples/load_sample_data.py create mode 100644 tests/test_brainreg_napari.py diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..f524e7f --- /dev/null +++ b/.flake8 @@ -0,0 +1,4 @@ +[flake8] +ignore = E203, E231, W503, E722 +max-line-length = 79 +exclude = __init__.py,build,.eggs diff --git a/.github/workflows/test_and_deploy.yml b/.github/workflows/test_and_deploy.yml index e19b486..7af6b13 100644 --- a/.github/workflows/test_and_deploy.yml +++ b/.github/workflows/test_and_deploy.yml @@ -42,9 +42,15 @@ jobs: with: path: ~/.brainglobe key: atlases - - uses: neuroinformatics-unit/actions/test@v2 + + - name: Setup QT libraries + uses: tlambert03/setup-qt-libs@v1 + + - name: Run tests + uses: neuroinformatics-unit/actions/test@v2 with: python-version: ${{ matrix.python-version }} + use-xvfb: true test-with-conda: needs: [linting, manifest] diff --git a/.gitignore b/.gitignore index 2167ff3..310b1a7 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ *.conf.custom # Byte-compiled / optimized / DLL files -__pycache__/ +**/__pycache__/ *.py[cod] *$py.class @@ -122,3 +122,4 @@ tests/data/test_output *.DS_Store +pip-wheel-metadata/ diff --git a/.napari/config.yml b/.napari/config.yml new file mode 100644 index 0000000..c4f261f --- /dev/null +++ b/.napari/config.yml @@ -0,0 +1,7 @@ +labels: + ontology: EDAM-BIOIMAGING:alpha06 + terms: + - Image registration + - Affine registration + - Multi-channel +summary: Multi-atlas whole-brain microscopy registration diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 0000000..77940e8 --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,102 @@ + +cff-version: 1.2.0 +message: If you use this software, please cite it using these metadata. +title: brainreg +authors: +- family-names: "Tyson" + given-names: "A." +- family-names: "Vélez-Fort" + given-names: "M." +- family-names: "Rousseau" + given-names: "C." +- family-names: "Cossell" + given-names: "L." +- family-names: "Tsitoura" + given-names: " C." +- family-names: "Lenzi" + given-names: " S." +- family-names: "Obenhaus" + given-names: "H." +- family-names: "Claudi" + given-names: "F." +- family-names: "Branco" + given-names: "T." +- family-names: "Margrie" + given-names: "T." +url: https://github.com/brainglobe/brainreg +preferred-citation: + type: article + title: Accurate determination of marker location within whole-brain microscopy images + journal: Scientific Reports + volume: 12 + pages: 867 + doi: 10.1038/s41598-021-04676-9 + year: 2022 + date-published: 2022-01-18 + authors: + - family-names: "Tyson" + given-names: "A." + - family-names: "Vélez-Fort" + given-names: "M." + - family-names: "Rousseau" + given-names: "C." + - family-names: "Cossell" + given-names: "L." + - family-names: "Tsitoura" + given-names: " C." + - family-names: "Lenzi" + given-names: "S." + - family-names: "Obenhaus" + given-names: "H." + - family-names: "Claudi" + given-names: "F." + - family-names: "Branco" + given-names: "T." + - family-names: "Margrie" + given-names: "T." +references: + - type: article + title: AMAP is a validated pipeline for registration and segmentation of high-resolution mouse brain data + journal: Nature Communications + volume: 7 + pages: 1-9 + doi: 10.1038/ncomms11879 + year: 2016 + date-published: 2016-07-07 + authors: + - family-names: "Niedworok" + given-names: "C." + - family-names: "Brown" + given-names: "A." + - family-names: "Jorge" + given-names: "M." + - family-names: "Osten" + given-names: "P." + - family-names: "Ourselin" + given-names: "S." + - family-names: "Modat" + given-names: "M." + - family-names: "Margrie" + given-names: "T." +references: + - type: article + title: BrainGlobe Atlas API a common interface for neuroanatomical atlases + journal: Journal of Open Source Software + volume: 5 + pages: 2668 + doi: 10.21105/joss.02668 + year: 2020 + date-published: 2020-09-04 + authors: + - family-names: "Claudi" + given-names: "F." + - family-names: "Petrucco" + given-names: "L." + - family-names: "Tyson" + given-names: "A." + - family-names: "Branco" + given-names: "T." + - family-names: "Margrie" + given-names: "T." + - family-names: "Portugues" + given-names: "R." diff --git a/MANIFEST.in b/MANIFEST.in index 0e1b5be..3b77d36 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,17 +1,22 @@ include LICENSE include README.md +include CITATION.cff -prune tests +exclude .flake8 +exclude .pre-commit-config.yaml +exclude *.yml exclude tox.ini -exclude *.yaml -exclude *.yml +include brainreg/napari/napari.yaml -recursive-include src *.dll -recursive-include src *.exe -recursive-include src *.txt +recursive-include brainreg/core *.dll +recursive-include brainreg/core *.exe +recursive-include brainreg/core *.txt +recursive-include brainreg/core/bin/nifty_reg/linux_x64 * +recursive-include brainreg/core/bin/nifty_reg/osX * -recursive-exclude src *.zip +recursive-exclude .napari * +recursive-exclude brainreg/core *.zip +recursive-exclude examples * -recursive-include src/brainreg/bin/nifty_reg/linux_x64 * -recursive-include src/brainreg/bin/nifty_reg/osX * +prune tests diff --git a/README.md b/README.md index dae00a4..d1840ec 100644 --- a/README.md +++ b/README.md @@ -9,154 +9,139 @@ # brainreg -brainreg is an update to -[amap](https://github.com/SainsburyWellcomeCentre/amap_python) (itself a port -of the [original Java software](https://www.nature.com/articles/ncomms11879)) -to include multiple registration backends, and to support the many atlases -provided by [bg-atlasapi](https://github.com/brainglobe/bg-atlasapi). +brainreg is an update to [amap](https://github.com/SainsburyWellcomeCentre/amap_python) (which is itself a port +of the [original Java software](https://www.nature.com/articles/ncomms11879)) to include multiple registration backends, and to support the many atlases provided by [bg-atlasapi](https://github.com/brainglobe/bg-atlasapi). +It also comes with an optional [napari plugin](https://github.com/brainglobe/brainreg-napari) if you'd rather use brainreg with through graphical interface. -Documentation can be found [here](https://brainglobe.info/documentation/brainreg/index.html). - -For segmentation of bulk structures in 3D space -(e.g. injection sites, Neuropixels probes), please see -[brainreg-segment](https://github.com/brainglobe/brainreg-segment). - -**N.B. There is also a [napari plugin](https://github.com/brainglobe/brainreg-napari) if you'd rather use brainreg with a graphical user interface. Currently this interface is slightly limited compared to the command line tool** - -This software is at a very early stage, and was written with our data in mind. -Over time we hope to support other data types/formats. If you have any issues, please get in touch [on the forum](https://forum.image.sc/tag/brainglobe) or by +Documentation for both the command-line tool and graphical interface can be found [here](https://brainglobe.info/documentation/brainreg/index.html). +If you have any issues, please get in touch [on the forum](https://forum.image.sc/tag/brainglobe), [on Zulip](https://brainglobe.zulipchat.com/), or by [raising an issue](https://github.com/brainglobe/brainreg/issues). +For segmentation of bulk structures in 3D space (e.g. injection sites, Neuropixels probes), please see [brainglobe-segmentation](https://github.com/brainglobe/brainglobe-segmentation). + ## Details -The aim of brainreg is to register the template brain - (e.g. from the [Allen Reference Atlas](https://mouse.brain-map.org/static/atlas)) - to the sample image. Once this is complete, any other image in the template - space can be aligned with the sample (such as region annotations, for - segmentation of the sample image). The template to sample transformation - can also be inverted, allowing sample images to be aligned in a common - coordinate space. -To do this, the template and sample images are filtered, and then registered in -a three step process (reorientation, affine registration, and freeform -registration.) The resulting transform from template to standard space is then -applied to the atlas. +The aim of brainreg is to register the template brain (e.g. from the [Allen Reference Atlas](https://mouse.brain-map.org/static/atlas)) to the sample image. +Once this is complete, any other image in the template space can be aligned with the sample (such as region annotations, for segmentation of the sample image). +The template to sample transformation can also be inverted, allowing sample images to be aligned in a common coordinate space. -Full details of the process are in the -[original aMAP paper](https://www.nature.com/articles/ncomms11879). -![reg_process](https://user-images.githubusercontent.com/13147259/143553945-a046e918-7614-4211-814c-fc840bb0159d.png) +To do this, the template and sample images are filtered, and then registered in a three step process (reorientation, affine registration, and freeform registration). +The resulting transform from template to standard space is then applied to the atlas. +Full details of the process are in the [original aMAP paper](https://www.nature.com/articles/ncomms11879). -**Overview of the registration process** +![An illustrated overview of the registration process](https://user-images.githubusercontent.com/13147259/143553945-a046e918-7614-4211-814c-fc840bb0159d.png) ## Installation + +To install both the command line tool and the napari plugin, run + ```bash pip install brainreg[napari] ``` +in your desired Python environment. To only install the command line tool with no GUI (e.g. to run brainreg on an HPC cluster), just run: + ```bash pip install brainreg ``` -**N.B. If you are using macOS, please run `conda install -c conda-forge niftyreg` to ensure all dependencies are installed.** -## Usage +### Installing on macOS + +If you are using macOS, please run -### Basic usage ```bash -brainreg /path/to/raw/data /path/to/output/directory -v 5 2 2 --orientation psl +conda install -c conda-forge niftyreg ``` -## Arguments -### Mandatory +in your environment before installing, to ensure all dependencies are installed. -* Path to the directory of the images. (Can also be a text file pointing to the files\) -* Output directory for all intermediate and final results +## Command line usage -**You must also specify the voxel sizes, see [Specifying voxel size](https://brainglobe.info/documentation/general/image-definition.html#voxel-sizes)** +### Basic usage -### Additional options +```bash +brainreg /path/to/raw/data /path/to/output/directory -v 5 2 2 --orientation psl +``` -* `-a` or `--additional` Paths to N additional channels to downsample to the same coordinate space. -* `--sort-input-file` If set to true, the input text file will be sorted using natural sorting. This means that the file paths will be sorted as would be expected by a human and not purely alphabetically. -* `--brain_geometry` Can be one of `full` (default) for full brain registration, `hemisphere_l` for left hemisphere data-set and `hemisphere_r` for right hemisphere data-set. +Full command-line arguments are available with `brainreg -h`, but please +[get in touch](mailto:code@adamltyson.com?subject=brainreg) if you have any questions. -#### Misc options +### Mandatory arguments -* `--n-free-cpus` The number of CPU cores on the machine to leave unused by the program to spare resources. -* `--debug` Debug mode. Will increase verbosity of logging and save all intermediate files for diagnosis of software issues. -* `--save-original-orientation` Option to save the registered atlas with the same orientation as the input data. +- Path to the directory of the images. This can also be a text file pointing to the files. +- Output directory for all intermediate and final results. +- You must also specify the voxel sizes with the `-v` flag, see [specifying voxel size](https://brainglobe.info/documentation/general/image-definition.html#voxel-sizes) for details. ### Atlas -By default, brainreg will use the 25um version of the [Allen Mouse Brain Atlas](https://mouse.brain-map.org/). To use another atlas \(e.g. for another species, or another resolution\), you must use the `--atlas` flag, followed by the string describing the atlas, e.g.: +By default, brainreg will use the 25um version of the [Allen Mouse Brain Atlas](https://mouse.brain-map.org/). +To use another atlas (e.g. for another species, or another resolution), you must use the `--atlas` flag, followed by the string describing the atlas, e.g.: ```bash --atlas allen_mouse_50um ``` -_To find out which atlases are available, once brainreg is installed, please run `brainglobe list`. The name of the resulting atlases is the string to pass with the `--atlas` flag._ - - -### Registration backend - -To change the registration algorithm used, use the `--backend` flag. The default is `niftyreg` as that is currently the only option. +To find out which atlases are available, once brainreg is installed, please run `brainglobe list`. +The name of the resulting atlases is the string to pass with the `--atlas` flag. ### Input data orientation -If your data does not match the [brainglobe](https://github.com/brainglobe) default orientation \(the origin voxel is the most anterior, superior, left-most voxel, then you must specify the orientation by using the `--orientation` flag. What follows must be a string in the [bg-space](https://github.com/brainglobe/bg-space) "initials" form, to describe the origin voxel. - +If your data does not match the BrainGlobe default orientation (the origin voxel is the most anterior, superior, left-most voxel), then you must specify the orientation by using the `--orientation` flag. +What follows must be a string in the [bg-space](https://github.com/brainglobe/bg-space) "initials" form, to describe the origin voxel. - -If the origin of your data \(first, top left voxel\) is the most anterior, superior, left part of the brain, then the orientation string would be "asl" \(anterior, superior, left\), and you would use: +If the origin of your data (first, top left voxel) is the most anterior, superior, left part of the brain, then the orientation string would be "asl" (anterior, superior, left), and you would use: ```bash --orientation asl ``` - ### Registration options -To change how the actual registration performs, see [Registration parameters](https://brainglobe.info/documentation/brainreg/user-guide/parameters.html) +To change how the actual registration performs, see [registration parameters](https://brainglobe.info/documentation/brainreg/user-guide/parameters.html) -Full command-line arguments are available with `brainreg -h`, but please -[get in touch](mailto:code@adamltyson.com?subject=brainreg) if you have any questions. +### Additional options +- `-a` or `--additional` Paths to N additional channels to downsample to the same coordinate space. +- `--sort-input-file` If set to true, the input text file will be sorted using natural sorting. This means that the file paths will be sorted as would be expected by a human and not purely alphabetically. +- `--brain_geometry` Can be one of `full` (default) for full brain registration, `hemisphere_l` for left hemisphere data-set and `hemisphere_r` for right hemisphere data-set. -### Visualisation +### Misc options -brainreg comes with a plugin ([brainglobe-napari-io](https://github.com/brainglobe/brainglobe-napari-io)) for [napari](https://github.com/napari/napari) to view your data +- `--n-free-cpus` The number of CPU cores on the machine to leave unused by the program to spare resources. +- `--debug` Debug mode. Will increase verbosity of logging and save all intermediate files for diagnosis of software issues. +- `--save-original-orientation` Option to save the registered atlas with the same orientation as the input data. -##### Sample space -Open napari and drag your brainreg output directory (the one with the log file) onto the napari window. +## Visualising results -Various images should then open, including: -* `Registered image` - the image used for registration, downsampled to atlas resolution -* `atlas_name` - e.g. `allen_mouse_25um` the atlas labels, warped to your sample brain -* `Boundaries` - the boundaries of the atlas regions +If you have installed the optional [napari](https://github.com/napari/napari) plugin, you can use napari to view your data. +The plugin automatically fetches the [brainglobe-napari-io](https://github.com/brainglobe/brainglobe-napari-io) which provides this functionality. +If you have installed only the command-line tool you can still manually install [brainglobe-napari-io](https://github.com/brainglobe/brainglobe-napari-io) and follow the steps below. -If you downsampled additional channels, these will also be loaded. +### Sample space -Most of these images will not be visible by default. Click the little eye icon to toggle visibility. +Open napari and drag your brainreg output directory (the one with the log file) onto the napari window. + +Various images should then open, including: -_N.B. If you use a high resolution atlas (such as `allen_mouse_10um`), then the files can take a little while to load._ +- `Registered image` - the image used for registration, downsampled to atlas resolution +- `atlas_name` - e.g. `allen_mouse_25um` the atlas labels, warped to your sample brain +- `Boundaries` - the boundaries of the atlas regions -![sample_space](https://raw.githubusercontent.com/brainglobe/napari-brainreg/master/resources/sample_space.gif) +If you downsampled additional channels, these will also be loaded. +Most of these images will not be visible by default - click the little eye icon to toggle visibility. +**Note:** If you use a high resolution atlas (such as `allen_mouse_10um`), then the files can take a little while to load. -##### Atlas space -`napari-brainreg` also comes with an additional plugin, for visualising your data -in atlas space. +![GIF illustration of loading brainreg output into napari for visualisation](https://raw.githubusercontent.com/brainglobe/napari-brainreg/master/resources/sample_space.gif) -This is typically only used in other software, but you can enable it yourself: -* Open napari -* Navigate to `Plugins` -> `Plugin Call Order` -* In the `Plugin Sorter` window, select `napari_get_reader` from the `select hook...` dropdown box -* Drag `brainreg_standard` (the atlas space viewer plugin) above `brainreg` (the normal plugin) to ensure that the atlas space plugin is used preferentially. +## Contributing -### Contributing -Contributions to brainreg are more than welcome. Please see the [developers guide](https://brainglobe.info/developers/index.html). +Contributions to brainreg are more than welcome. +Please see the [developers guide](https://brainglobe.info/developers/index.html). -### Citing brainreg +## Citing brainreg If you find brainreg useful, and use it in your research, please let us know and also cite the paper: @@ -164,10 +149,10 @@ If you find brainreg useful, and use it in your research, please let us know and Please also cite aMAP (the original pipeline from which this software is based): ->Niedworok, C.J., Brown, A.P.Y., Jorge Cardoso, M., Osten, P., Ourselin, S., Modat, M. and Margrie, T.W., (2016). AMAP is a validated pipeline for registration and segmentation of high-resolution mouse brain data. Nature Communications. 7, 1–9. https://doi.org/10.1038/ncomms11879 +>Niedworok, C.J., Brown, A.P.Y., Jorge Cardoso, M., Osten, P., Ourselin, S., Modat, M. and Margrie, T.W., (2016). AMAP is a validated pipeline for registration and segmentation of high-resolution mouse brain data. Nature Communications. 7, 1–9. Lastly, if you can, please cite the BrainGlobe Atlas API that provided the atlas: ->Claudi, F., Petrucco, L., Tyson, A. L., Branco, T., Margrie, T. W. and Portugues, R. (2020). BrainGlobe Atlas API: a common interface for neuroanatomical atlases. Journal of Open Source Software, 5(54), 2668, https://doi.org/10.21105/joss.02668 +>Claudi, F., Petrucco, L., Tyson, A. L., Branco, T., Margrie, T. W. and Portugues, R. (2020). BrainGlobe Atlas API: a common interface for neuroanatomical atlases. Journal of Open Source Software, 5(54), 2668, -**Don't forget to cite the developers of the atlas that you used (e.g. the Allen Brain Atlas)!** +Finally, **don't forget to cite the developers of the atlas that you used (e.g. the Allen Brain Atlas)!** diff --git a/src/brainreg/__init__.py b/brainreg/__init__.py similarity index 92% rename from src/brainreg/__init__.py rename to brainreg/__init__.py index 091633a..33757c5 100644 --- a/src/brainreg/__init__.py +++ b/brainreg/__init__.py @@ -1,8 +1,8 @@ from importlib.metadata import PackageNotFoundError, version -from . import * try: __version__ = version("brainreg") + del version except PackageNotFoundError: # package is not installed pass diff --git a/brainreg/core/__init__.py b/brainreg/core/__init__.py new file mode 100644 index 0000000..b6e690f --- /dev/null +++ b/brainreg/core/__init__.py @@ -0,0 +1 @@ +from . import * diff --git a/src/brainreg/backend/__init__.py b/brainreg/core/backend/__init__.py similarity index 100% rename from src/brainreg/backend/__init__.py rename to brainreg/core/backend/__init__.py diff --git a/src/brainreg/backend/niftyreg/__init__.py b/brainreg/core/backend/niftyreg/__init__.py similarity index 100% rename from src/brainreg/backend/niftyreg/__init__.py rename to brainreg/core/backend/niftyreg/__init__.py diff --git a/src/brainreg/backend/niftyreg/niftyreg_binaries.py b/brainreg/core/backend/niftyreg/niftyreg_binaries.py similarity index 100% rename from src/brainreg/backend/niftyreg/niftyreg_binaries.py rename to brainreg/core/backend/niftyreg/niftyreg_binaries.py diff --git a/src/brainreg/backend/niftyreg/parameters.py b/brainreg/core/backend/niftyreg/parameters.py similarity index 100% rename from src/brainreg/backend/niftyreg/parameters.py rename to brainreg/core/backend/niftyreg/parameters.py diff --git a/src/brainreg/backend/niftyreg/parser.py b/brainreg/core/backend/niftyreg/parser.py similarity index 100% rename from src/brainreg/backend/niftyreg/parser.py rename to brainreg/core/backend/niftyreg/parser.py diff --git a/src/brainreg/backend/niftyreg/paths.py b/brainreg/core/backend/niftyreg/paths.py similarity index 100% rename from src/brainreg/backend/niftyreg/paths.py rename to brainreg/core/backend/niftyreg/paths.py diff --git a/src/brainreg/backend/niftyreg/registration.py b/brainreg/core/backend/niftyreg/registration.py similarity index 100% rename from src/brainreg/backend/niftyreg/registration.py rename to brainreg/core/backend/niftyreg/registration.py diff --git a/src/brainreg/backend/niftyreg/run.py b/brainreg/core/backend/niftyreg/run.py similarity index 96% rename from src/brainreg/backend/niftyreg/run.py rename to brainreg/core/backend/niftyreg/run.py index 4dcadfb..0c7a0da 100644 --- a/src/brainreg/backend/niftyreg/run.py +++ b/brainreg/core/backend/niftyreg/run.py @@ -9,11 +9,11 @@ from brainglobe_utils.general.system import delete_directory_contents from brainglobe_utils.image.scale import scale_and_convert_to_16_bits -from brainreg.backend.niftyreg.parameters import RegistrationParams -from brainreg.backend.niftyreg.paths import NiftyRegPaths -from brainreg.backend.niftyreg.registration import BrainRegistration -from brainreg.backend.niftyreg.utils import save_nii -from brainreg.utils import preprocess +from brainreg.core.backend.niftyreg.parameters import RegistrationParams +from brainreg.core.backend.niftyreg.paths import NiftyRegPaths +from brainreg.core.backend.niftyreg.registration import BrainRegistration +from brainreg.core.backend.niftyreg.utils import save_nii +from brainreg.core.utils import preprocess def crop_atlas(atlas, brain_geometry): diff --git a/src/brainreg/backend/niftyreg/utils.py b/brainreg/core/backend/niftyreg/utils.py similarity index 100% rename from src/brainreg/backend/niftyreg/utils.py rename to brainreg/core/backend/niftyreg/utils.py diff --git a/src/brainreg/bin/nifty_reg/LICENSE.txt b/brainreg/core/bin/nifty_reg/LICENSE.txt similarity index 100% rename from src/brainreg/bin/nifty_reg/LICENSE.txt rename to brainreg/core/bin/nifty_reg/LICENSE.txt diff --git a/src/brainreg/bin/nifty_reg/linux_x64/reg_aladin b/brainreg/core/bin/nifty_reg/linux_x64/reg_aladin similarity index 100% rename from src/brainreg/bin/nifty_reg/linux_x64/reg_aladin rename to brainreg/core/bin/nifty_reg/linux_x64/reg_aladin diff --git a/src/brainreg/bin/nifty_reg/linux_x64/reg_average b/brainreg/core/bin/nifty_reg/linux_x64/reg_average similarity index 100% rename from src/brainreg/bin/nifty_reg/linux_x64/reg_average rename to brainreg/core/bin/nifty_reg/linux_x64/reg_average diff --git a/src/brainreg/bin/nifty_reg/linux_x64/reg_f3d b/brainreg/core/bin/nifty_reg/linux_x64/reg_f3d similarity index 100% rename from src/brainreg/bin/nifty_reg/linux_x64/reg_f3d rename to brainreg/core/bin/nifty_reg/linux_x64/reg_f3d diff --git a/src/brainreg/bin/nifty_reg/linux_x64/reg_jacobian b/brainreg/core/bin/nifty_reg/linux_x64/reg_jacobian similarity index 100% rename from src/brainreg/bin/nifty_reg/linux_x64/reg_jacobian rename to brainreg/core/bin/nifty_reg/linux_x64/reg_jacobian diff --git a/src/brainreg/bin/nifty_reg/linux_x64/reg_measure b/brainreg/core/bin/nifty_reg/linux_x64/reg_measure similarity index 100% rename from src/brainreg/bin/nifty_reg/linux_x64/reg_measure rename to brainreg/core/bin/nifty_reg/linux_x64/reg_measure diff --git a/src/brainreg/bin/nifty_reg/linux_x64/reg_resample b/brainreg/core/bin/nifty_reg/linux_x64/reg_resample similarity index 100% rename from src/brainreg/bin/nifty_reg/linux_x64/reg_resample rename to brainreg/core/bin/nifty_reg/linux_x64/reg_resample diff --git a/src/brainreg/bin/nifty_reg/linux_x64/reg_tools b/brainreg/core/bin/nifty_reg/linux_x64/reg_tools similarity index 100% rename from src/brainreg/bin/nifty_reg/linux_x64/reg_tools rename to brainreg/core/bin/nifty_reg/linux_x64/reg_tools diff --git a/src/brainreg/bin/nifty_reg/linux_x64/reg_transform b/brainreg/core/bin/nifty_reg/linux_x64/reg_transform similarity index 100% rename from src/brainreg/bin/nifty_reg/linux_x64/reg_transform rename to brainreg/core/bin/nifty_reg/linux_x64/reg_transform diff --git a/src/brainreg/bin/nifty_reg/niftyreg-git.zip b/brainreg/core/bin/nifty_reg/niftyreg-git.zip similarity index 100% rename from src/brainreg/bin/nifty_reg/niftyreg-git.zip rename to brainreg/core/bin/nifty_reg/niftyreg-git.zip diff --git a/src/brainreg/bin/nifty_reg/niftyreg_version.txt b/brainreg/core/bin/nifty_reg/niftyreg_version.txt similarity index 100% rename from src/brainreg/bin/nifty_reg/niftyreg_version.txt rename to brainreg/core/bin/nifty_reg/niftyreg_version.txt diff --git a/src/brainreg/bin/nifty_reg/osX/reg_aladin b/brainreg/core/bin/nifty_reg/osX/reg_aladin similarity index 100% rename from src/brainreg/bin/nifty_reg/osX/reg_aladin rename to brainreg/core/bin/nifty_reg/osX/reg_aladin diff --git a/src/brainreg/bin/nifty_reg/osX/reg_average b/brainreg/core/bin/nifty_reg/osX/reg_average similarity index 100% rename from src/brainreg/bin/nifty_reg/osX/reg_average rename to brainreg/core/bin/nifty_reg/osX/reg_average diff --git a/src/brainreg/bin/nifty_reg/osX/reg_f3d b/brainreg/core/bin/nifty_reg/osX/reg_f3d similarity index 100% rename from src/brainreg/bin/nifty_reg/osX/reg_f3d rename to brainreg/core/bin/nifty_reg/osX/reg_f3d diff --git a/src/brainreg/bin/nifty_reg/osX/reg_jacobian b/brainreg/core/bin/nifty_reg/osX/reg_jacobian similarity index 100% rename from src/brainreg/bin/nifty_reg/osX/reg_jacobian rename to brainreg/core/bin/nifty_reg/osX/reg_jacobian diff --git a/src/brainreg/bin/nifty_reg/osX/reg_measure b/brainreg/core/bin/nifty_reg/osX/reg_measure similarity index 100% rename from src/brainreg/bin/nifty_reg/osX/reg_measure rename to brainreg/core/bin/nifty_reg/osX/reg_measure diff --git a/src/brainreg/bin/nifty_reg/osX/reg_resample b/brainreg/core/bin/nifty_reg/osX/reg_resample similarity index 100% rename from src/brainreg/bin/nifty_reg/osX/reg_resample rename to brainreg/core/bin/nifty_reg/osX/reg_resample diff --git a/src/brainreg/bin/nifty_reg/osX/reg_tools b/brainreg/core/bin/nifty_reg/osX/reg_tools similarity index 100% rename from src/brainreg/bin/nifty_reg/osX/reg_tools rename to brainreg/core/bin/nifty_reg/osX/reg_tools diff --git a/src/brainreg/bin/nifty_reg/osX/reg_transform b/brainreg/core/bin/nifty_reg/osX/reg_transform similarity index 100% rename from src/brainreg/bin/nifty_reg/osX/reg_transform rename to brainreg/core/bin/nifty_reg/osX/reg_transform diff --git a/src/brainreg/bin/nifty_reg/win64/reg_aladin.exe b/brainreg/core/bin/nifty_reg/win64/reg_aladin.exe similarity index 100% rename from src/brainreg/bin/nifty_reg/win64/reg_aladin.exe rename to brainreg/core/bin/nifty_reg/win64/reg_aladin.exe diff --git a/src/brainreg/bin/nifty_reg/win64/reg_average.exe b/brainreg/core/bin/nifty_reg/win64/reg_average.exe similarity index 100% rename from src/brainreg/bin/nifty_reg/win64/reg_average.exe rename to brainreg/core/bin/nifty_reg/win64/reg_average.exe diff --git a/src/brainreg/bin/nifty_reg/win64/reg_f3d.exe b/brainreg/core/bin/nifty_reg/win64/reg_f3d.exe similarity index 100% rename from src/brainreg/bin/nifty_reg/win64/reg_f3d.exe rename to brainreg/core/bin/nifty_reg/win64/reg_f3d.exe diff --git a/src/brainreg/bin/nifty_reg/win64/reg_jacobian.exe b/brainreg/core/bin/nifty_reg/win64/reg_jacobian.exe similarity index 100% rename from src/brainreg/bin/nifty_reg/win64/reg_jacobian.exe rename to brainreg/core/bin/nifty_reg/win64/reg_jacobian.exe diff --git a/src/brainreg/bin/nifty_reg/win64/reg_measure.exe b/brainreg/core/bin/nifty_reg/win64/reg_measure.exe similarity index 100% rename from src/brainreg/bin/nifty_reg/win64/reg_measure.exe rename to brainreg/core/bin/nifty_reg/win64/reg_measure.exe diff --git a/src/brainreg/bin/nifty_reg/win64/reg_resample.exe b/brainreg/core/bin/nifty_reg/win64/reg_resample.exe similarity index 100% rename from src/brainreg/bin/nifty_reg/win64/reg_resample.exe rename to brainreg/core/bin/nifty_reg/win64/reg_resample.exe diff --git a/src/brainreg/bin/nifty_reg/win64/reg_tools.exe b/brainreg/core/bin/nifty_reg/win64/reg_tools.exe similarity index 100% rename from src/brainreg/bin/nifty_reg/win64/reg_tools.exe rename to brainreg/core/bin/nifty_reg/win64/reg_tools.exe diff --git a/src/brainreg/bin/nifty_reg/win64/reg_transform.exe b/brainreg/core/bin/nifty_reg/win64/reg_transform.exe similarity index 100% rename from src/brainreg/bin/nifty_reg/win64/reg_transform.exe rename to brainreg/core/bin/nifty_reg/win64/reg_transform.exe diff --git a/src/brainreg/bin/nifty_reg/win64/vcruntime140_1.dll b/brainreg/core/bin/nifty_reg/win64/vcruntime140_1.dll similarity index 100% rename from src/brainreg/bin/nifty_reg/win64/vcruntime140_1.dll rename to brainreg/core/bin/nifty_reg/win64/vcruntime140_1.dll diff --git a/src/brainreg/cli.py b/brainreg/core/cli.py similarity index 96% rename from src/brainreg/cli.py rename to brainreg/core/cli.py index 6af2a3d..5752c67 100644 --- a/src/brainreg/cli.py +++ b/brainreg/core/cli.py @@ -10,10 +10,10 @@ import brainreg as program_for_log from brainreg import __version__ -from brainreg.backend.niftyreg.parser import niftyreg_parse -from brainreg.main import main as register -from brainreg.paths import Paths -from brainreg.utils.misc import get_arg_groups, log_metadata +from brainreg.core.backend.niftyreg.parser import niftyreg_parse +from brainreg.core.main import main as register +from brainreg.core.paths import Paths +from brainreg.core.utils.misc import get_arg_groups, log_metadata temp_dir = tempfile.TemporaryDirectory() temp_dir_path = temp_dir.name diff --git a/src/brainreg/exceptions.py b/brainreg/core/exceptions.py similarity index 100% rename from src/brainreg/exceptions.py rename to brainreg/core/exceptions.py diff --git a/src/brainreg/main.py b/brainreg/core/main.py similarity index 91% rename from src/brainreg/main.py rename to brainreg/core/main.py index f9f1916..59337d6 100644 --- a/src/brainreg/main.py +++ b/brainreg/core/main.py @@ -5,10 +5,13 @@ from bg_atlasapi import BrainGlobeAtlas from brainglobe_utils.general.system import get_num_processes -from brainreg.backend.niftyreg.run import run_niftyreg -from brainreg.exceptions import LoadFileException, path_is_folder_with_one_tiff -from brainreg.utils.boundaries import boundaries -from brainreg.utils.volume import calculate_volumes +from brainreg.core.backend.niftyreg.run import run_niftyreg +from brainreg.core.exceptions import ( + LoadFileException, + path_is_folder_with_one_tiff, +) +from brainreg.core.utils.boundaries import boundaries +from brainreg.core.utils.volume import calculate_volumes def main( diff --git a/src/brainreg/paths.py b/brainreg/core/paths.py similarity index 100% rename from src/brainreg/paths.py rename to brainreg/core/paths.py diff --git a/src/brainreg/utils/__init__.py b/brainreg/core/utils/__init__.py similarity index 100% rename from src/brainreg/utils/__init__.py rename to brainreg/core/utils/__init__.py diff --git a/src/brainreg/utils/boundaries.py b/brainreg/core/utils/boundaries.py similarity index 100% rename from src/brainreg/utils/boundaries.py rename to brainreg/core/utils/boundaries.py diff --git a/src/brainreg/utils/misc.py b/brainreg/core/utils/misc.py similarity index 100% rename from src/brainreg/utils/misc.py rename to brainreg/core/utils/misc.py diff --git a/src/brainreg/utils/preprocess.py b/brainreg/core/utils/preprocess.py similarity index 100% rename from src/brainreg/utils/preprocess.py rename to brainreg/core/utils/preprocess.py diff --git a/src/brainreg/utils/volume.py b/brainreg/core/utils/volume.py similarity index 100% rename from src/brainreg/utils/volume.py rename to brainreg/core/utils/volume.py diff --git a/brainreg/napari/__init__.py b/brainreg/napari/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/brainreg/napari/napari.yaml b/brainreg/napari/napari.yaml new file mode 100644 index 0000000..49ee37a --- /dev/null +++ b/brainreg/napari/napari.yaml @@ -0,0 +1,18 @@ +name: brainreg +schema_version: 0.1.0 +contributions: + commands: + - id: brainreg.Register + title: Atlas Registration + python_name: brainreg.napari.register:brainreg_register + - id: brainreg.SampleData + title: Low resolution brain + python_name: brainreg.napari.sample_data:load_test_brain + widgets: + - command: brainreg.Register + display_name: Atlas Registration + sample_data: + - key: sample + display_name: Low resolution brain + command: brainreg.SampleData +display_name: brainreg diff --git a/brainreg/napari/register.py b/brainreg/napari/register.py new file mode 100644 index 0000000..33545ae --- /dev/null +++ b/brainreg/napari/register.py @@ -0,0 +1,583 @@ +import json +import logging +import pathlib +from collections import namedtuple +from enum import Enum +from typing import Dict, List, Tuple + +import bg_space as bg +import napari +import numpy as np +from bg_atlasapi import BrainGlobeAtlas +from brainglobe_napari_io.cellfinder.reader_dir import load_registration +from brainglobe_segmentation.atlas.utils import get_available_atlases +from fancylog import fancylog +from magicgui import magicgui +from napari._qt.qthreading import thread_worker +from napari.types import LayerDataTuple +from napari.utils.notifications import show_info + +import brainreg as program_for_log +from brainreg.core.backend.niftyreg.run import run_niftyreg +from brainreg.core.paths import Paths +from brainreg.core.utils.boundaries import boundaries +from brainreg.core.utils.misc import log_metadata +from brainreg.core.utils.volume import calculate_volumes +from brainreg.napari.util import ( + NiftyregArgs, + downsample_and_save_brain, + initialise_brainreg, +) + +PRE_PROCESSING_ARGS = None + + +def add_registered_image_layers( + viewer: napari.Viewer, *, registration_directory: pathlib.Path +) -> Tuple[napari.layers.Image, napari.layers.Labels]: + """ + Read in saved registration data and add as layers to the + napari viewer. + + Returns + ------- + boundaries : + Registered boundaries. + labels : + Registered brain regions. + """ + layers: List[LayerDataTuple] = [] + + meta_file = (registration_directory / "brainreg.json").resolve() + if meta_file.exists(): + with open(meta_file) as json_file: + metadata = json.load(json_file) + layers = load_registration(layers, registration_directory, metadata) + else: + raise FileNotFoundError( + f"'brainreg.json' file not found in {registration_directory}" + ) + + boundaries = viewer.add_layer(napari.layers.Layer.create(*layers[0])) + labels = viewer.add_layer(napari.layers.Layer.create(*layers[1])) + return boundaries, labels + + +def get_layer_labels(widget): + return [layer._name for layer in widget.viewer.value.layers] + + +def get_additional_images_downsample(widget) -> Dict[str, str]: + """ + For any selected layers loaded from a file, get a mapping from + layer name -> layer file path. + """ + images = {} + for layer in widget.viewer.value.layers.selection: + if layer._source.path is not None: + images[layer._name] = str(layer._source.path) + return images + + +def get_atlas_dropdown(): + atlas_dict = {} + for i, k in enumerate(get_available_atlases().keys()): + atlas_dict.setdefault(k, k) + atlas_keys = Enum("atlas_key", atlas_dict) + return atlas_keys + + +def get_brain_geometry_dropdown(): + geometry_dict = { + "Full brain": "full", + "Right hemisphere": "hemisphere_r", + "Left hemisphere": "hemisphere_l", + } + return Enum("geometry_keys", geometry_dict) + + +def brainreg_register(): + DEFAULT_PARAMETERS = dict( + z_pixel_um=5, + y_pixel_um=2, + x_pixel_um=2, + data_orientation="psl", + brain_geometry=get_brain_geometry_dropdown(), + save_original_orientation=False, + atlas_key=get_atlas_dropdown(), + registration_output_folder=pathlib.Path.home(), + affine_n_steps=6, + affine_use_n_steps=5, + freeform_n_steps=6, + freeform_use_n_steps=4, + bending_energy_weight=0.95, + grid_spacing=10, + smoothing_sigma_reference=1, + smoothing_sigma_floating=1, + histogram_n_bins_floating=128, + histogram_n_bins_reference=128, + debug=False, + ) + + @magicgui( + call_button=True, + img_layer=dict( + label="Image layer", + ), + atlas_key=dict( + label="Atlas", + ), + z_pixel_um=dict( + value=DEFAULT_PARAMETERS["z_pixel_um"], + label="Voxel size (z)", + step=0.1, + ), + y_pixel_um=dict( + value=DEFAULT_PARAMETERS["y_pixel_um"], + label="Voxel size (y)", + step=0.1, + ), + x_pixel_um=dict( + value=DEFAULT_PARAMETERS["x_pixel_um"], + label="Voxel size (x)", + step=0.1, + ), + data_orientation=dict( + value=DEFAULT_PARAMETERS["data_orientation"], + label="Data orientation", + ), + brain_geometry=dict( + label="Brain geometry", + ), + registration_output_folder=dict( + value=DEFAULT_PARAMETERS["registration_output_folder"], + mode="d", + label="Output directory", + ), + save_original_orientation=dict( + value=DEFAULT_PARAMETERS["save_original_orientation"], + label="Save original orientation", + ), + affine_n_steps=dict( + value=DEFAULT_PARAMETERS["affine_n_steps"], label="affine_n_steps" + ), + affine_use_n_steps=dict( + value=DEFAULT_PARAMETERS["affine_use_n_steps"], + label="affine_use_n_steps", + ), + freeform_n_steps=dict( + value=DEFAULT_PARAMETERS["freeform_n_steps"], + label="freeform_n_steps", + ), + freeform_use_n_steps=dict( + value=DEFAULT_PARAMETERS["freeform_use_n_steps"], + label="freeform_use_n_steps", + ), + bending_energy_weight=dict( + value=DEFAULT_PARAMETERS["bending_energy_weight"], + label="bending_energy_weight", + ), + grid_spacing=dict( + value=DEFAULT_PARAMETERS["grid_spacing"], label="grid_spacing" + ), + smoothing_sigma_reference=dict( + value=DEFAULT_PARAMETERS["smoothing_sigma_reference"], + label="smoothing_sigma_reference", + ), + smoothing_sigma_floating=dict( + value=DEFAULT_PARAMETERS["smoothing_sigma_floating"], + label="smoothing_sigma_floating", + ), + histogram_n_bins_floating=dict( + value=DEFAULT_PARAMETERS["histogram_n_bins_floating"], + label="histogram_n_bins_floating", + ), + histogram_n_bins_reference=dict( + value=DEFAULT_PARAMETERS["histogram_n_bins_reference"], + label="histogram_n_bins_reference", + ), + debug=dict( + value=DEFAULT_PARAMETERS["debug"], + label="Debug mode", + ), + reset_button=dict(widget_type="PushButton", text="Reset defaults"), + check_orientation_button=dict( + widget_type="PushButton", text="Check orientation" + ), + ) + def widget( + viewer: napari.Viewer, + img_layer: napari.layers.Image, + atlas_key: get_atlas_dropdown(), + data_orientation: str, + brain_geometry: get_brain_geometry_dropdown(), + z_pixel_um: float, + x_pixel_um: float, + y_pixel_um: float, + registration_output_folder: pathlib.Path, + save_original_orientation: bool, + affine_n_steps: int, + affine_use_n_steps: int, + freeform_n_steps: int, + freeform_use_n_steps: int, + bending_energy_weight: float, + grid_spacing: int, + smoothing_sigma_reference: int, + smoothing_sigma_floating: float, + histogram_n_bins_floating: float, + histogram_n_bins_reference: float, + debug: bool, + reset_button, + check_orientation_button, + block: bool = False, + ): + """ + Parameters + ---------- + img_layer : napari.layers.Image + Image layer to be registered + atlas_key : str + Atlas to use for registration + data_orientation: str + Three characters describing the data orientation, e.g. "psl". + See docs for more details. + brain_geometry: str + To allow brain sub-volumes to be processed. Choose whether your + data is a whole brain or a single hemisphere. + z_pixel_um : float + Size of your voxels in the axial dimension + y_pixel_um : float + Size of your voxels in the y direction (top to bottom) + x_pixel_um : float + Size of your voxels in the xdirection (left to right) + registration_output_folder: pathlib.Path + Where to save the registration output + affine_n_steps: int, + save_original_orientation: bool + Option to save annotations with the same orientation as the input + data. Use this if you plan to map + segmented objects outside brainglobe/tools. + affine_n_steps: int + Registration starts with further downsampled versions of the + original data to optimize the global fit of the result and + prevent "getting stuck" in local minima of the similarity + function. This parameter determines how many downsampling steps + are being performed, with each step halving the data size along + each dimension. + affine_use_n_steps: int + Determines how many of the downsampling steps defined by + affine-_n_steps will have their registration computed. The + combination affine_n_steps=3, affine_use_n_steps=2 will e.g. + calculate 3 downsampled steps, each of which is half the size + of the previous one but only perform the registration on the + 2 smallest resampling steps, skipping the full resolution data. + Can be used to save time if running the full resolution doesn't + result in noticeable improvements. + freeform_n_steps: int + Registration starts with further downsampled versions of the + original data to optimize the global fit of the result and prevent + "getting stuck" in local minima of the similarity function. This + parameter determines how many downsampling steps are being + performed, with each step halving the data size along each + dimension. + freeform_use_n_steps: int + Determines how many of the downsampling steps defined by + freeform_n_steps will have their registration computed. The + combination freeform_n_steps=3, freeform_use_n_steps=2 will e.g. + calculate 3 downsampled steps, each of which is half the size of + the previous one but only perform the registration on the 2 + smallest resampling steps, skipping the full resolution data. + Can be used to save time if running the full resolution doesn't + result in noticeable improvements + bending_energy_weight: float + Sets the bending energy, which is the coefficient of the penalty + term, preventing the freeform registration from over-fitting. + The range is between 0 and 1 (exclusive) with higher values + leading to more restriction of the registration. + grid_spacing: int + Sets the control point grid spacing in x, y & z. + Smaller grid spacing allows for more local deformations + but increases the risk of over-fitting. + smoothing_sigma_reference: int + Adds a Gaussian smoothing to the reference image (the one being + registered), with the sigma defined by the number. Positive + values are interpreted as real values in mm, negative values + are interpreted as distance in voxels. + smoothing_sigma_floating: float + Adds a Gaussian smoothing to the floating image (the one being + registered), with the sigma defined by the number. Positive + values are interpreted as real values in mm, negative values + are interpreted as distance in voxels. + histogram_n_bins_floating: float + Number of bins used for the generation of the histograms used + for the calculation of Normalized Mutual Information on the + floating image + histogram_n_bins_reference: float + Number of bins used for the generation of the histograms used + for the calculation of Normalized Mutual Information on the + reference image + debug: bool + Activate debug mode (save intermediate steps). + check_orientation_button: + Interactively check the input orientation by comparing the average + projection along each axis. The top row of displayed images are + the projections of the reference atlas. The bottom row are the + projections of the aligned input data. If the two rows are + similarly oriented, the orientation is correct. If not, change + the orientation and try again. + reset_button: + Reset parameters to default + block : bool + If `True`, registration will block execution when called. By + default this is `False` to avoid blocking the napari GUI, but + is set to `True` in the tests. + """ + + def load_registration_as_layers() -> None: + """ + Load the saved registration data into napari layers. + """ + viewer = getattr(widget, "viewer").value + registration_directory = pathlib.Path( + getattr(widget, "registration_output_folder").value + ) + add_registered_image_layers( + viewer, registration_directory=registration_directory + ) + + def get_gui_logging_args(): + args_dict = {} + args_dict.setdefault("image_paths", img_layer.source.path) + args_dict.setdefault("backend", "niftyreg") + + voxel_sizes = [] + + for name in ["z_pixel_um", "y_pixel_um", "x_pixel_um"]: + voxel_sizes.append(str(getattr(widget, name).value)) + args_dict.setdefault("voxel_sizes", voxel_sizes) + + for name, value in DEFAULT_PARAMETERS.items(): + if "pixel" not in name: + if name == "atlas_key": + args_dict.setdefault( + "atlas", str(getattr(widget, name).value.value) + ) + + if name == "data_orientation": + args_dict.setdefault( + "orientation", str(getattr(widget, name).value) + ) + + args_dict.setdefault( + name, str(getattr(widget, name).value) + ) + + return ( + namedtuple("namespace", args_dict.keys())(*args_dict.values()), + args_dict, + ) + + @thread_worker + def run(): + paths = Paths(pathlib.Path(registration_output_folder)) + + niftyreg_args = NiftyregArgs( + affine_n_steps, + affine_use_n_steps, + freeform_n_steps, + freeform_use_n_steps, + bending_energy_weight, + -grid_spacing, + -smoothing_sigma_reference, + -smoothing_sigma_floating, + histogram_n_bins_floating, + histogram_n_bins_reference, + debug=False, + ) + args_namedtuple, args_dict = get_gui_logging_args() + log_metadata(paths.metadata_path, args_dict) + + fancylog.start_logging( + str(paths.registration_output_folder), + program_for_log, + variables=args_namedtuple, + verbose=niftyreg_args.debug, + log_header="BRAINREG LOG", + multiprocessing_aware=False, + ) + + voxel_sizes = z_pixel_um, x_pixel_um, y_pixel_um + + ( + n_free_cpus, + n_processes, + atlas, + scaling, + load_parallel, + ) = initialise_brainreg( + atlas_key.value, data_orientation, voxel_sizes + ) + + additional_images_downsample = get_additional_images_downsample( + widget + ) + + logging.info(f"Registering {img_layer._name}") + + target_brain = downsample_and_save_brain(img_layer, scaling) + target_brain = bg.map_stack_to( + data_orientation, atlas.metadata["orientation"], target_brain + ) + sort_input_file = False + run_niftyreg( + registration_output_folder, + paths, + atlas, + target_brain, + n_processes, + additional_images_downsample, + data_orientation, + atlas.metadata["orientation"], + niftyreg_args, + PRE_PROCESSING_ARGS, + scaling, + load_parallel, + sort_input_file, + n_free_cpus, + save_original_orientation=save_original_orientation, + brain_geometry=brain_geometry.value, + ) + + logging.info("Calculating volumes of each brain area") + calculate_volumes( + atlas, + paths.registered_atlas, + paths.registered_hemispheres, + paths.volume_csv_path, + # for all brainglobe atlases + left_hemisphere_value=1, + right_hemisphere_value=2, + ) + + logging.info("Generating boundary image") + boundaries(paths.registered_atlas, paths.boundaries_file_path) + + logging.info( + f"brainreg completed. Results can be found here: " + f"{paths.registration_output_folder}" + ) + + worker = run() + if not block: + worker.returned.connect(load_registration_as_layers) + + worker.start() + + if block: + worker.await_workers() + load_registration_as_layers() + + @widget.reset_button.changed.connect + def restore_defaults(event=None): + for name, value in DEFAULT_PARAMETERS.items(): + if name not in ["atlas_key", "brain_geometry"]: + getattr(widget, name).value = value + + @widget.check_orientation_button.changed.connect + def check_orientation(event=None): + """ + Function used to check that the input orientation is correct. + To do so it transforms the input data into the requested atlas + orientation, compute the average projection and displays it alongside + the atlas. It is then easier for the user to identify which dimension + should be swapped and avoid running the pipeline on wrongly aligned + data. + """ + + if getattr(widget, "img_layer").value is None: + show_info("Raw data must be loaded before checking orientation.") + return widget + + # Get viewer object + viewer = getattr(widget, "viewer").value + + brain_geometry = getattr(widget, "brain_geometry").value + + # Remove previous average projection layer if needed + ind_pop = [] + for i, layer in enumerate(viewer.layers): + if layer.name in [ + "Ref. proj. 0", + "Ref. proj. 1", + "Ref. proj. 2", + "Input proj. 0", + "Input proj. 1", + "Input proj. 2", + ]: + ind_pop.append(i) + else: + layer.visible = False + for index in ind_pop[::-1]: + del viewer.layers[index] + + # Load atlas and gather data + atlas = BrainGlobeAtlas(widget.atlas_key.value.name) + if brain_geometry.value == "hemisphere_l": + atlas.reference[ + atlas.hemispheres == atlas.left_hemisphere_value + ] = 0 + elif brain_geometry.value == "hemisphere_r": + atlas.reference[ + atlas.hemispheres == atlas.right_hemisphere_value + ] = 0 + input_orientation = getattr(widget, "data_orientation").value + data = getattr(widget, "img_layer").value.data + # Transform data to atlas orientation from user input + data_remapped = bg.map_stack_to( + input_orientation, atlas.orientation, data + ) + + # Compute average projection of atlas and remapped data + u_proj = [] + u_proja = [] + s = [] + for i in range(3): + u_proj.append(np.mean(data_remapped, axis=i)) + u_proja.append(np.mean(atlas.reference, axis=i)) + s.append(u_proja[-1].shape[0]) + s = np.max(s) + + # Display all projections with somewhat consistent scaling + viewer.add_image(u_proja[0], name="Ref. proj. 0") + viewer.add_image( + u_proja[1], translate=[0, u_proja[0].shape[1]], name="Ref. proj. 1" + ) + viewer.add_image( + u_proja[2], + translate=[0, u_proja[0].shape[1] + u_proja[1].shape[1]], + name="Ref. proj. 2", + ) + + s1 = u_proja[0].shape[0] / u_proj[0].shape[0] + s2 = u_proja[0].shape[1] / u_proj[0].shape[1] + viewer.add_image( + u_proj[0], translate=[s, 0], name="Input proj. 0", scale=[s1, s2] + ) + s1 = u_proja[1].shape[0] / u_proj[1].shape[0] + s2 = u_proja[1].shape[1] / u_proj[1].shape[1] + viewer.add_image( + u_proj[1], + translate=[s, u_proja[0].shape[1]], + name="Input proj. 1", + scale=[s1, s2], + ) + s1 = u_proja[2].shape[0] / u_proj[2].shape[0] + s2 = u_proja[2].shape[1] / u_proj[2].shape[1] + viewer.add_image( + u_proj[2], + translate=[s, u_proja[0].shape[1] + u_proja[1].shape[1]], + name="Input proj. 2", + scale=[s1, s2], + ) + + return widget diff --git a/brainreg/napari/sample_data.py b/brainreg/napari/sample_data.py new file mode 100644 index 0000000..50b4548 --- /dev/null +++ b/brainreg/napari/sample_data.py @@ -0,0 +1,42 @@ +import zipfile +from typing import List + +import numpy as np +import pooch +from napari.types import LayerData +from skimage.io import imread + +# git SHA for version of sample data to download +data_commit_sha = "72b73c52f19cee2173467ecdca60747a60e5fb95" + +POOCH_REGISTRY = pooch.create( + path=pooch.os_cache("brainreg_napari"), + base_url=( + "https://gin.g-node.org/cellfinder/data/" + f"raw/{data_commit_sha}/brainreg/" + ), + registry={ + "test_brain.zip": "7bcfbc45bb40358cd8811e5264ca0a2367976db90bcefdcd67adf533e0162b5f" # noqa: E501 + }, +) + + +def load_test_brain() -> List[LayerData]: + """ + Load test brain data. + """ + data = [] + brain_zip = POOCH_REGISTRY.fetch("test_brain.zip") + + with zipfile.ZipFile(brain_zip, mode="r") as archive: + for i in range(270): + with archive.open( + f"test_brain/image_{str(i).zfill(4)}.tif" + ) as tif: + data.append(imread(tif)) + + data = np.stack(data, axis=0) + meta = {"voxel_size": [50, 40, 40], "data_orientation": "psl"} + return [ + (data, {"name": "Sample brain", "metadata": meta}, "image"), + ] diff --git a/brainreg/napari/util.py b/brainreg/napari/util.py new file mode 100644 index 0000000..b91910b --- /dev/null +++ b/brainreg/napari/util.py @@ -0,0 +1,88 @@ +import logging +from dataclasses import dataclass + +import bg_space as bg +import numpy as np +import skimage.transform +from bg_atlasapi import BrainGlobeAtlas +from brainglobe_utils.general.system import get_num_processes +from tqdm import tqdm + + +def initialise_brainreg(atlas_key, data_orientation_key, voxel_sizes): + scaling_rounding_decimals = 5 + n_free_cpus = 2 + atlas = BrainGlobeAtlas(atlas_key) + source_space = bg.AnatomicalSpace(data_orientation_key) + + scaling = [] + for idx, axis in enumerate(atlas.space.axes_order): + scaling.append( + round( + float(voxel_sizes[idx]) + / atlas.resolution[ + atlas.space.axes_order.index(source_space.axes_order[idx]) + ], + scaling_rounding_decimals, + ) + ) + + n_processes = get_num_processes(min_free_cpu_cores=n_free_cpus) + load_parallel = n_processes > 1 + + logging.info("Loading raw image data") + return ( + n_free_cpus, + n_processes, + atlas, + scaling, + load_parallel, + ) + + +def downsample_and_save_brain(img_layer, scaling): + first_frame_shape = skimage.transform.rescale( + img_layer.data[0], scaling[1:2], anti_aliasing=True + ).shape + preallocated_array = np.empty( + (img_layer.data.shape[0], first_frame_shape[0], first_frame_shape[1]) + ) + print("downsampling data in x, y") + for i, img in tqdm(enumerate(img_layer.data)): + down_xy = skimage.transform.rescale( + img, scaling[1:2], anti_aliasing=True + ) + preallocated_array[i] = down_xy + + first_ds_frame_shape = skimage.transform.rescale( + preallocated_array[:, :, 0], [scaling[0], 1], anti_aliasing=True + ).shape + downsampled_array = np.empty( + (first_ds_frame_shape[0], first_frame_shape[0], first_frame_shape[1]) + ) + print("downsampling data in z") + for i, img in tqdm(enumerate(preallocated_array.T)): + down_xyz = skimage.transform.rescale( + img, [1, scaling[0]], anti_aliasing=True + ) + downsampled_array[:, :, i] = down_xyz.T + return downsampled_array + + +@dataclass +class NiftyregArgs: + """ + Class for niftyreg arguments. + """ + + affine_n_steps: int + affine_use_n_steps: int + freeform_n_steps: int + freeform_use_n_steps: int + bending_energy_weight: float + grid_spacing: float + smoothing_sigma_reference: float + smoothing_sigma_floating: float + histogram_n_bins_floating: float + histogram_n_bins_reference: float + debug: bool diff --git a/examples/load_sample_data.py b/examples/load_sample_data.py new file mode 100644 index 0000000..dc56eac --- /dev/null +++ b/examples/load_sample_data.py @@ -0,0 +1,39 @@ +""" +Load and show sample data +========================= +This example: +- loads some sample data +- adds the data to a napari viewer +- loads the brainreg napari registration plugin +- opens the napari viewer +""" +import napari +import numpy as np +from napari.layers import Layer + +from brainreg.napari.sample_data import load_test_brain + +viewer = napari.Viewer() + +layer_data = load_test_brain()[0] +# Set sensible contrast limits for viewing the brain +layer_data[1]["contrast_limits"] = np.percentile(layer_data[0], [0, 99.5]) +# Add data to napari +viewer.add_layer(Layer.create(*layer_data)) + +# Open plugin +_, brainreg_widget = viewer.window.add_plugin_dock_widget( + plugin_name="brainreg", widget_name="Atlas Registration" +) +# Set napari plugin settings from sample data metadata +metadata = layer_data[1]["metadata"] +brainreg_widget.data_orientation.value = metadata["data_orientation"] +for i, dim in enumerate(["z", "y", "x"]): + pixel_widget = getattr(brainreg_widget, f"{dim}_pixel_um") + pixel_widget.value = metadata["voxel_size"][i] + + +if __name__ == "__main__": + # The napari event loop needs to be run under here to allow the window + # to be spawned from a Python script + napari.run() diff --git a/pyproject.toml b/pyproject.toml index b1bef7a..ddca8a1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,9 +1,10 @@ [project] name = "brainreg" -description = "Automated 3D brain registration" +description = "Automated multi-atlas whole-brain microscopy registration" readme = "README.md" +license = { file = "LICENSE" } authors = [ - { name = "Adam Tyson, Charly Rousseau", email = "code@adamltyson.com" }, + { name = "Adam Tyson, Charly Rousseau, Stephen Lenzi", email = "code@adamltyson.com" }, ] classifiers = [ "Development Status :: 3 - Alpha", @@ -20,6 +21,7 @@ requires-python = ">=3.9" dependencies = [ "bg-atlasapi", "bg-space", + "brainglobe-utils", "fancylog", "imio", "brainglobe-utils>=0.2.7", @@ -31,23 +33,54 @@ dynamic = ["version"] [project.optional-dependencies] napari = [ "brainglobe-napari-io", - "brainreg-napari", - "brainreg-segment>=0.0.2", - "napari[pyside2]", + "brainglobe-segmentation >= 1.0.0", + "magicgui", + "napari-plugin-engine >= 0.1.4", + "napari[pyqt5]", + "pooch>1", # For sample data + "qtpy", ] -dev = ["black", "pre-commit", "pytest", "pytest-cov"] +dev = [ + "black", + "check-manifest", + "gitpython", + "napari[pyqt5]", + "pre-commit", + "pytest-cov", + "pytest-qt", + "pytest", + "setuptools_scm", + "tox", +] + +[project.entry-points."napari.manifest"] +brainreg = "brainreg.napari:napari.yaml" [project.scripts] -brainreg = "brainreg.cli:main" +brainreg = "brainreg.core.cli:main" + +[project.urls] +homepage = "https://brainglobe.info" +bug_tracker = "https://github.com/brainglobe/brainreg/issues" +documentation = "https://docs.brainglobe.info/brainreg" +source_code = "https://github.com/brainglobe/brainreg" +user_support = "https://forum.image.sc/tag/brainglobe" +twitter = "https://twitter.com/brain_globe" [build-system] requires = ["setuptools>=45", "wheel", "setuptools_scm[toml]>=6.2"] build-backend = "setuptools.build_meta" +[tool.setuptools_scm] + [tool.setuptools] include-package-data = true -[tool.setuptools_scm] +[tool.setuptools.packages.find] +include = ["brainreg*"] + +[tool.setuptools.package-data] +include = ["brainreg*"] [tool.pytest.ini_options] addopts = "--cov=brainreg" @@ -68,9 +101,9 @@ filterwarnings = [ markers = ["slow: marks tests as slow (deselect with '-m \"not slow\"')"] [tool.black] -target-version = ['py38', 'py39', 'py310', 'py311'] -skip-string-normalization = false line-length = 79 +skip-string-normalization = false +target-version = ['py39', 'py310', 'py311'] [tool.ruff] line-length = 79 diff --git a/tests/test_brainreg_napari.py b/tests/test_brainreg_napari.py new file mode 100644 index 0000000..210972c --- /dev/null +++ b/tests/test_brainreg_napari.py @@ -0,0 +1,161 @@ +import napari +import pytest +from bg_atlasapi import BrainGlobeAtlas + +from brainreg.napari.register import ( + add_registered_image_layers, + brainreg_register, +) + + +def test_add_detect_widget(make_napari_viewer): + """ + Smoke test to check that adding detection widget works + """ + viewer = make_napari_viewer() + widget = brainreg_register() + viewer.window.add_dock_widget(widget) + + +def test_napari_sample_data(make_napari_viewer): + """ + Check that loading the sample data via napari works. + """ + viewer = make_napari_viewer() + + assert len(viewer.layers) == 0 + viewer.open_sample("brainreg", "sample") + assert len(viewer.layers) == 1 + new_layer = viewer.layers[0] + assert isinstance(new_layer, napari.layers.Image) + assert new_layer.data.shape == (270, 193, 271) + + +def test_workflow(make_napari_viewer, tmp_path): + """ + Test a full workflow using brainreg's napari plugin. + """ + viewer = make_napari_viewer() + + # Load sample data + added_layers = viewer.open_sample("brainreg", "sample") + brain_layer = added_layers[0] + + # Load widget + _, widget = viewer.window.add_plugin_dock_widget( + plugin_name="brainreg", widget_name="Atlas Registration" + ) + # Set active layer and output folder + widget.img_layer.value = brain_layer + widget.registration_output_folder.value = tmp_path + + # Set widget settings from brain data metadata + widget.data_orientation.value = brain_layer.metadata["data_orientation"] + for i, dim in enumerate(["z", "y", "x"]): + pixel_widget = getattr(widget, f"{dim}_pixel_um") + pixel_widget.value = brain_layer.metadata["voxel_size"][i] + + assert len(viewer.layers) == 1 + + # Run registration + widget(block=True) + + # Check that layers have been added + assert len(viewer.layers) == 3 + # Check layers have expected type/name + labels = viewer.layers[1] + assert isinstance(labels, napari.layers.Labels) + assert labels.name == "example_mouse_100um" + for key in ["orientation", "atlas"]: + # There are lots of other keys in the metadata, but just check + # for a couple here. + assert ( + key in labels.metadata + ), f"Missing key '{key}' from labels metadata" + + boundaries = viewer.layers[2] + assert isinstance(boundaries, napari.layers.Image) + assert boundaries.name == "Boundaries" + + +def test_orientation_check( + make_napari_viewer, tmp_path, atlas_choice="allen_mouse_50um" +): + """ + Test that the check orientation function works + """ + viewer = make_napari_viewer() + + # Load widget + _, widget = viewer.window.add_plugin_dock_widget( + plugin_name="brainreg", widget_name="Atlas Registration" + ) + + # Set a specific atlas + # Should be a better way of doing this + for choice in widget.atlas_key.choices: + if choice.name == atlas_choice: + widget.atlas_key.value = choice + break + + assert widget.atlas_key.value.name == atlas_choice + + widget.check_orientation_button.clicked() + assert len(viewer.layers) == 0 + + # Load sample data + added_layers = viewer.open_sample("brainreg", "sample") + brain_layer = added_layers[0] + + assert len(viewer.layers) == 1 + + atlas = BrainGlobeAtlas(atlas_choice) + # click check orientation button and check output + run_and_check_orientation_check(widget, viewer, brain_layer, atlas) + + # run again and check previous output was deleted properly + run_and_check_orientation_check(widget, viewer, brain_layer, atlas) + + +def run_and_check_orientation_check(widget, viewer, brain_layer, atlas): + widget.check_orientation_button.clicked() + check_orientation_output(viewer, brain_layer, atlas) + + +def check_orientation_output(viewer, brain_layer, atlas): + # 1 for the loaded data, and three each for the + # views of two images (data & atlas) + assert len(viewer.layers) == 7 + assert brain_layer.visible is False + for i in range(1, 7): + layer = viewer.layers[i] + assert isinstance(layer, napari.layers.Image) + assert layer.visible is True + assert layer.ndim == 2 + + assert viewer.layers[4].data.shape == ( + brain_layer.data.shape[1], + brain_layer.data.shape[2], + ) + assert viewer.layers[5].data.shape == ( + brain_layer.data.shape[0], + brain_layer.data.shape[2], + ) + assert viewer.layers[6].data.shape == ( + brain_layer.data.shape[0], + brain_layer.data.shape[1], + ) + + assert viewer.layers[1].data.shape == (atlas.shape[1], atlas.shape[2]) + assert viewer.layers[2].data.shape == (atlas.shape[0], atlas.shape[2]) + assert viewer.layers[3].data.shape == (atlas.shape[0], atlas.shape[1]) + + +def test_add_layers_errors(tmp_path, make_napari_viewer): + """ + Check that an error is raised if registration metadata isn't present when + trying to add registered images to a napari viewer. + """ + viewer = make_napari_viewer() + with pytest.raises(FileNotFoundError): + add_registered_image_layers(viewer, registration_directory=tmp_path) diff --git a/tests/tests/test_backend/test_niftyreg_detection.py b/tests/tests/test_backend/test_niftyreg_detection.py index d8b54b1..d38c111 100644 --- a/tests/tests/test_backend/test_niftyreg_detection.py +++ b/tests/tests/test_backend/test_niftyreg_detection.py @@ -2,7 +2,7 @@ from pathlib import Path from typing import Tuple -from brainreg.backend.niftyreg.niftyreg_binaries import ( +from brainreg.core.backend.niftyreg.niftyreg_binaries import ( _CONDA_NIFTYREG_BINARY_PATH, get_binary, packaged_binaries_folder, diff --git a/tests/tests/test_integration/test_registration.py b/tests/tests/test_integration/test_registration.py index 347177e..5f0998c 100644 --- a/tests/tests/test_integration/test_registration.py +++ b/tests/tests/test_integration/test_registration.py @@ -3,7 +3,8 @@ from pathlib import Path import pytest -from brainreg.cli import main as brainreg_run + +from brainreg.core.cli import main as brainreg_run from .utils import check_images_same, check_volumes_equal diff --git a/tests/tests/test_unit/test_exceptions.py b/tests/tests/test_unit/test_exceptions.py index 26739f6..55effd1 100644 --- a/tests/tests/test_unit/test_exceptions.py +++ b/tests/tests/test_unit/test_exceptions.py @@ -2,8 +2,9 @@ from pathlib import Path import pytest -from brainreg.cli import main as brainreg_run -from brainreg.exceptions import LoadFileException + +from brainreg.core.cli import main as brainreg_run +from brainreg.core.exceptions import LoadFileException test_data_dir = Path(__file__).parent.parent.parent / "data" one_tiff_data_dir = test_data_dir / "input" / "exceptions" / "one_tiff" diff --git a/tox.ini b/tox.ini index e4adb8d..4b02112 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,6 @@ [tox] envlist = py{38,39,310,311} +isolated_build = True [gh-actions] python = @@ -11,5 +12,13 @@ python = [testenv] extras = dev + napari commands = pytest -v --color=yes --cov=brainreg --cov-report=xml +passenv = + CI + GITHUB_ACTIONS + DISPLAY + XAUTHORITY + NUMPY_EXPERIMENTAL_ARRAY_FUNCTION + PYVISTA_OFF_SCREEN