Repository: brainglobe/brainreg (language: python)

Issue:
Add support for atlases with multiple reference images
In atlases that have provided an additional reference stack, allow the user to select which reference stack in the atlas they would like to apply the registration to.


Discussion / hints:

Fix patch:
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/.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/BR-CORE-README.md b/BR-CORE-README.md
new file mode 100644
index 0000000..77425c0
--- /dev/null
+++ b/BR-CORE-README.md
@@ -0,0 +1,177 @@
+[![Python Version](https://img.shields.io/pypi/pyversions/brainreg.svg)](https://pypi.org/project/brainreg)
+[![PyPI](https://img.shields.io/pypi/v/brainreg.svg)](https://pypi.org/project/brainreg)
+[![Wheel](https://img.shields.io/pypi/wheel/brainreg.svg)](https://pypi.org/project/brainreg)
+[![Development Status](https://img.shields.io/pypi/status/brainreg.svg)](https://github.com/brainglobe/brainreg)
+[![Tests](https://img.shields.io/github/workflow/status/brainglobe/brainreg/tests)](
+    https://github.com/brainglobe/brainreg/actions)
+[![codecov](https://codecov.io/gh/brainglobe/brainreg/branch/master/graph/badge.svg?token=FbPgwBIGnd)](https://codecov.io/gh/brainglobe/brainreg)
+[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/python/black)
+
+# 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).
+
+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
+[raising an issue](https://github.com/brainglobe/brainreg/issues).
+
+## 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.
+
+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)
+
+**Overview of the registration process**
+
+## Installation
+
+```bash
+pip install brainreg[napari]
+```
+
+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
+
+### Basic usage
+
+```bash
+brainreg /path/to/raw/data /path/to/output/directory -v 5 2 2 --orientation psl
+```
+
+## Arguments
+
+### Mandatory
+
+* 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
+
+**You must also specify the voxel sizes, see [Specifying voxel size](https://brainglobe.info/documentation/general/image-definition.html#voxel-sizes)**
+
+### 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.
+
+#### Misc options
+
+* `--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.
+
+### 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.:
+
+```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.
+
+### 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 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)
+
+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.
+
+### Visualisation
+
+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
+
+##### Sample space
+
+Open napari and drag your brainreg output directory (the one with the log file) onto the napari window.
+
+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 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.
+
+_N.B. If you use a high resolution atlas (such as `allen_mouse_10um`), then the files can take a little while to load._
+
+![sample_space](https://raw.githubusercontent.com/brainglobe/napari-brainreg/master/resources/sample_space.gif)
+
+##### Atlas space
+
+`napari-brainreg` also comes with an additional plugin, for visualising your data
+in atlas space.
+
+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
+
+Contributions to brainreg are more than welcome. Please see the [developers guide](https://brainglobe.info/developers/index.html).
+
+### Citing brainreg
+
+If you find brainreg useful, and use it in your research, please let us know and also cite the paper:
+
+> Tyson, A. L., V&eacute;lez-Fort, M.,  Rousseau, C. V., Cossell, L., Tsitoura, C., Lenzi, S. C., Obenhaus, H. A., Claudi, F., Branco, T.,  Margrie, T. W. (2022). Accurate determination of marker location within whole-brain microscopy images. Scientific Reports, 12, 867 [doi.org/10.1038/s41598-021-04676-9](https://doi.org/10.1038/s41598-021-04676-9)
+
+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>
+
+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>
+
+**Don't forget to cite the developers of the atlas that you used (e.g. the Allen Brain Atlas)!**
diff --git a/BR-NAPARI-README.md b/BR-NAPARI-README.md
new file mode 100644
index 0000000..d893475
--- /dev/null
+++ b/BR-NAPARI-README.md
@@ -0,0 +1,75 @@
+[![Python Version](https://img.shields.io/pypi/pyversions/brainreg-napari.svg)](https://pypi.org/project/brainreg-napari)
+[![PyPI](https://img.shields.io/pypi/v/brainreg-napari.svg)](https://pypi.org/project/brainreg-napari)
+[![Wheel](https://img.shields.io/pypi/wheel/brainreg-napari.svg)](https://pypi.org/project/brainreg-napari)
+[![Development Status](https://img.shields.io/pypi/status/brainreg-napari.svg)](https://github.com/brainglobe/brainreg-napari)
+[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/python/black)
+[![codecov](https://codecov.io/gh/brainglobe/brainreg-napari/branch/master/graph/badge.svg?token=HEBXJPLD2S)](https://codecov.io/gh/brainglobe/brainreg-napari)
+
+# brainreg-napari
+
+Napari plugin to run [brainreg](https://github.com/brainglobe/brainreg),
+developed by [Stephen Lenzi](https://github.com/stephenlenzi).
+
+## Installation
+
+```bash
+pip install brainreg-napari
+```
+
+## Usage
+
+Documentation and tutorials for the plugin 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).
+
+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
+[raising an issue](https://github.com/brainglobe/brainreg/issues).
+
+## Details
+
+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).
+
+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.
+
+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)
+**Overview of the registration process**
+
+## Contributing
+
+Contributions to brainreg-napari are more than welcome. Please see the [developers guide](https://brainglobe.info/developers/index.html).
+
+### Citing brainreg
+
+If you find brainreg useful, and use it in your research, please let us know and also cite the paper:
+
+> Tyson, A. L., V&eacute;lez-Fort, M.,  Rousseau, C. V., Cossell, L., Tsitoura, C., Lenzi, S. C., Obenhaus, H. A., Claudi, F., Branco, T.,  Margrie, T. W. (2022). Accurate determination of marker location within whole-brain microscopy images. Scientific Reports, 12, 867 [doi.org/10.1038/s41598-021-04676-9](https://doi.org/10.1038/s41598-021-04676-9)
+
+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>
+
+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>
+
+**Don't forget to cite the developers of the atlas that you used (e.g. the Allen Brain Atlas)!**
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..814db81 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,17 +1,25 @@
 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
+exclude BR-CORE-README.md
+exclude BR-NAPARI-README.md
 
-recursive-include src *.dll
-recursive-include src *.exe
-recursive-include src *.txt
+include brainreg/napari/napari.yaml
 
-recursive-exclude src *.zip
+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-include src/brainreg/bin/nifty_reg/linux_x64 *
-recursive-include src/brainreg/bin/nifty_reg/osX *
+recursive-exclude .napari *
+recursive-exclude brainreg/core *.zip
+recursive-exclude examples *
+
+prune tests
diff --git a/README.md b/README.md
index dae00a4..49d9200 100644
--- a/README.md
+++ b/README.md
@@ -1,173 +1,1 @@
-[![Python Version](https://img.shields.io/pypi/pyversions/brainreg.svg)](https://pypi.org/project/brainreg)
-[![PyPI](https://img.shields.io/pypi/v/brainreg.svg)](https://pypi.org/project/brainreg)
-[![Wheel](https://img.shields.io/pypi/wheel/brainreg.svg)](https://pypi.org/project/brainreg)
-[![Development Status](https://img.shields.io/pypi/status/brainreg.svg)](https://github.com/brainglobe/brainreg)
-[![Tests](https://img.shields.io/github/workflow/status/brainglobe/brainreg/tests)](
-    https://github.com/brainglobe/brainreg/actions)
-[![codecov](https://codecov.io/gh/brainglobe/brainreg/branch/master/graph/badge.svg?token=FbPgwBIGnd)](https://codecov.io/gh/brainglobe/brainreg)
-[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/python/black)
-
-# 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).
-
-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
-[raising an issue](https://github.com/brainglobe/brainreg/issues).
-
-## 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.
-
-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)
-
-
-**Overview of the registration process**
-
-## Installation
-```bash
-pip install brainreg[napari]
-```
-
-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
-
-### Basic usage
-```bash
-brainreg /path/to/raw/data /path/to/output/directory -v 5 2 2 --orientation psl
-```
-
-## Arguments
-### Mandatory
-
-* 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
-
-**You must also specify the voxel sizes, see [Specifying voxel size](https://brainglobe.info/documentation/general/image-definition.html#voxel-sizes)**
-
-### 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.
-
-#### Misc options
-
-* `--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.
-
-### 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.:
-
-```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.
-
-### 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 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)
-
-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.
-
-
-### Visualisation
-
-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
-
-##### Sample space
-Open napari and drag your brainreg output directory (the one with the log file) onto the napari window.
-
-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 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.
-
-_N.B. If you use a high resolution atlas (such as `allen_mouse_10um`), then the files can take a little while to load._
-
-![sample_space](https://raw.githubusercontent.com/brainglobe/napari-brainreg/master/resources/sample_space.gif)
-
-
-##### Atlas space
-`napari-brainreg` also comes with an additional plugin, for visualising your data
-in atlas space.
-
-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
-Contributions to brainreg are more than welcome. Please see the [developers guide](https://brainglobe.info/developers/index.html).
-
-### Citing brainreg
-
-If you find brainreg useful, and use it in your research, please let us know and also cite the paper:
-
-> Tyson, A. L., V&eacute;lez-Fort, M.,  Rousseau, C. V., Cossell, L., Tsitoura, C., Lenzi, S. C., Obenhaus, H. A., Claudi, F., Branco, T.,  Margrie, T. W. (2022). Accurate determination of marker location within whole-brain microscopy images. Scientific Reports, 12, 867 [doi.org/10.1038/s41598-021-04676-9](https://doi.org/10.1038/s41598-021-04676-9)
-
-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
-
-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
-
-**Don't forget to cite the developers of the atlas that you used (e.g. the Allen Brain Atlas)!**
+# Brainreg
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,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..fb22e91
--- /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 brainreg_segment.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 1f015c5..e6c3c2a 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",
@@ -16,13 +17,13 @@ classifiers = [
     "Programming Language :: Python :: 3.10",
     "Programming Language :: Python :: 3.11",
 ]
-requires-python = ">=3.8"
+requires-python = ">=3.9"
 dependencies = [
     "bg-atlasapi",
     "bg-space",
+    "brainglobe-utils",
     "fancylog",
     "imio",
-    "brainglobe-utils",
     "numpy",
     "scikit-image",
 ]
@@ -31,32 +32,54 @@ dynamic = ["version"]
 [project.optional-dependencies]
 napari = [
     "brainglobe-napari-io",
-    "brainreg-napari",
-    "brainreg-segment>=0.0.2",
-    "napari[pyside2]",
+    "brainreg-segment",
+    "magicgui",
+    "napari-plugin-engine >= 0.1.4",
+    "napari[pyqt5]",
+    "pooch>1",                       # For sample data
+    "qtpy",
 ]
 dev = [
     "black",
+    "check-manifest",
+    "gitpython",
+    "napari[pyqt5]",
     "pre-commit",
-    "pytest",
     "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",
-]
+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"
@@ -72,19 +95,17 @@ filterwarnings = [
     "ignore:`np.uint0` is a deprecated alias for `np.uintp`",
     "ignore:`np.void0` is a deprecated alias for `np.void`",
     "ignore:`np.bytes0` is a deprecated alias for `np.bytes_`",
-    "ignore:`np.str0` is a deprecated alias for `np.str_`"
-]
-markers = [
-    "slow: marks tests as slow (deselect with '-m \"not slow\"')",
+    "ignore:`np.str0` is a deprecated alias for `np.str_`",
 ]
+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
-exclude = ["__init__.py","build",".eggs"]
+exclude = ["__init__.py", "build", ".eggs"]
 select = ["I", "E", "F"]
 fix = true
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


Test patch:
diff --git a/.github/workflows/test_and_deploy.yml b/.github/workflows/test_and_deploy.yml
index 4fe8031..7af6b13 100644
--- a/.github/workflows/test_and_deploy.yml
+++ b/.github/workflows/test_and_deploy.yml
@@ -35,8 +35,6 @@ jobs:
           python-version: "3.10"
         - os: windows-latest
           python-version: "3.9"
-        - os: ubuntu-latest
-          python-version: "3.8"
 
     steps:
       - name: Cache atlases
@@ -44,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/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/tests/tests/test_unit/test_misc.py b/tests/tests/test_unit/test_misc.py
index 3f1956c..0581fb9 100644
--- a/tests/tests/test_unit/test_misc.py
+++ b/tests/tests/test_unit/test_misc.py
@@ -1,5 +1,6 @@
 import pandas as pd
-from brainreg.utils.misc import safe_pandas_concat
+
+from brainreg.core.utils.misc import safe_pandas_concat
 
 
 def test_safe_pandas_concat() -> None: