├── .gitignore ├── .zenodo.json ├── CITATION.bib ├── LICENSE.txt ├── README.md ├── docs ├── _config.yml ├── _toc.yml ├── brainplotlib_logo.png ├── examples │ ├── cortical_masks.md │ ├── different_cmaps.md │ ├── different_resolutions.md │ ├── different_surfaces.md │ ├── index.md │ ├── plot_colorbar.md │ └── save_image.md └── index.md ├── images ├── example_data.png ├── example_data_with_colorbar.png ├── random_data.png └── random_data_with_colorbar.png ├── nitrc.yaml ├── pyproject.toml ├── scripts ├── compute_fsaverage_mapping_part1.py └── compute_fsaverage_mapping_part2.py ├── setup.cfg ├── src └── brainplotlib │ ├── __init__.py │ └── data │ ├── example_data.npy │ ├── fsaverage_to_inflated_image.npy │ ├── fsaverage_to_midthickness_image.npy │ ├── fsaverage_to_pial_image.npy │ ├── fsaverage_to_white_image.npy │ ├── mask_fsaverage5_lh.npy │ ├── mask_fsaverage5_rh.npy │ ├── mask_fsaverage6_lh.npy │ ├── mask_fsaverage6_rh.npy │ ├── mask_fsaverage_lh.npy │ ├── mask_fsaverage_rh.npy │ ├── voronoi_fsaverage_lh_icoorder3.npy │ ├── voronoi_fsaverage_lh_icoorder4.npy │ ├── voronoi_fsaverage_lh_icoorder5.npy │ ├── voronoi_fsaverage_lh_icoorder6.npy │ ├── voronoi_fsaverage_rh_icoorder3.npy │ ├── voronoi_fsaverage_rh_icoorder4.npy │ ├── voronoi_fsaverage_rh_icoorder5.npy │ └── voronoi_fsaverage_rh_icoorder6.npy └── tests ├── test_plotting.py ├── test_surf_types.py └── test_unmask_upsample.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /.zenodo.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "brainplotlib: plotting brain data on cortical surface", 3 | "description": "

brainplotlib allows plotting data on cortical surface with minimal requirements. It supports multiple resolutions and both masked and non-masked data.

", 4 | "creators": [ 5 | { 6 | "affiliation": "Dartmouth College", 7 | "name": "Ma Feilong", 8 | "orcid": "0000-0002-6838-3971" 9 | }, 10 | { 11 | "affiliation": "Dartmouth College", 12 | "name": "Guo Jiahui", 13 | "orcid": "0000-0002-1528-9025" 14 | }, 15 | { 16 | "affiliation": "Università di Bologna; Dartmouth College", 17 | "name": "M. Ida Gobbini", 18 | "orcid": "0000-0001-6727-7934" 19 | }, 20 | { 21 | "affiliation": "Dartmouth College", 22 | "name": "James V. Haxby", 23 | "orcid": "0000-0002-6558-3118" 24 | } 25 | ], 26 | "keywords": [ 27 | "visualization", 28 | "plotting", 29 | "brain", 30 | "fMRI", 31 | "neuroimaging", 32 | "Python" 33 | ], 34 | "upload_type": "software" 35 | } 36 | -------------------------------------------------------------------------------- /CITATION.bib: -------------------------------------------------------------------------------- 1 | @software{brainplotlib, 2 | author = {Ma Feilong and Guo Jiahui and M. Ida Gobbini and James V. Haxby}, 3 | title = {brainplotlib: plotting brain data on cortical surface}, 4 | month = feb, 5 | year = 2022, 6 | publisher = {Zenodo}, 7 | doi = {10.5281/zenodo.5979819}, 8 | url = {https://doi.org/10.5281/zenodo.5979819} 9 | } 10 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2022-2023, The brainplotlib developers 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![PyPI](https://img.shields.io/pypi/v/brainplotlib)](https://pypi.org/project/brainplotlib/) 2 | [![Downloads](https://static.pepy.tech/badge/brainplotlib)](https://pepy.tech/project/brainplotlib) 3 | ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/brainplotlib) 4 | [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.5979819.svg)](https://doi.org/10.5281/zenodo.5979819) 5 | 6 | `brainplotlib` is a Python package that plots data on cortical surface. 7 | It's designed to have minimal requirements --- only `NumPy` and `matplotlib`. 8 | 9 | ![brain image](https://github.com/feilong/brainplotlib/raw/main/images/example_data_with_colorbar.png) 10 | 11 | ## Installation 12 | The package can be installed with pip: 13 | ```bash 14 | pip install brainplotlib 15 | ``` 16 | 17 | ## Example usage 18 | 19 | **See the [examples gallery](https://feilong.github.io/brainplotlib/examples/index.html) for all code examples with detailed explanations.** 20 | 21 | ```Python 22 | import numpy as np 23 | from brainplotlib import brain_plot, example_data 24 | 25 | # The example_data is icoorder5 resolution (10242 vertices per hemisphere), 26 | # and the non-cortical vertices have been masked out (9372 and 9370 remaining 27 | # vertices for the left and right hemisphere, respectively). 28 | 29 | img, scale = brain_plot( 30 | example_data, vmax=10, vmin=-10, cmap='seismic', return_scale=True) 31 | ``` 32 | 33 | The rendered image is a NumPy array. 34 | It can be rendered using `matplotlib`: 35 | ```Python 36 | import matplotlib.pyplot as plt 37 | fig = plt.figure(figsize=(img.shape[1] / 200, img.shape[0] / 200), dpi=200) 38 | plt.imshow(img) 39 | plt.axis('off') 40 | cbar = plt.colorbar(scale, shrink=0.8, aspect=30) 41 | plt.savefig('example_data_with_colorbar.png', bbox_inches='tight') 42 | plt.show() 43 | ``` 44 | 45 | Alternatively, the high-resolution image can be saved directly using `OpenCV`. 46 | ```Python 47 | import cv2 48 | cv2.imwrite( 49 | 'example_data.png', 50 | np.round(img[:, :, [2, 1, 0]] * 255).astype(np.uint8)) 51 | ``` 52 | 53 | ## Citation 54 | If you use this software in your publications, please cite it [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.5979819.svg)](https://doi.org/10.5281/zenodo.5979819) 55 | ```bibtex 56 | @software{brainplotlib, 57 | author = {Ma Feilong and Guo Jiahui and M. Ida Gobbini and James V. Haxby}, 58 | title = {brainplotlib: plotting brain data on cortical surface}, 59 | month = feb, 60 | year = 2022, 61 | publisher = {Zenodo}, 62 | doi = {10.5281/zenodo.5979819}, 63 | url = {https://doi.org/10.5281/zenodo.5979819} 64 | } 65 | ``` 66 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | # Book settings 2 | # Learn more at https://jupyterbook.org/customize/config.html 3 | 4 | title: "" 5 | author: the brainplotlib developers and contributors 6 | logo: brainplotlib_logo.png 7 | 8 | # Force re-execution of notebooks on each build. 9 | # See https://jupyterbook.org/content/execute.html 10 | execute: 11 | execute_notebooks: cache 12 | 13 | # Define the name of the latex output file for PDF builds 14 | latex: 15 | latex_documents: 16 | targetname: book.tex 17 | 18 | # Add a bibtex file so that we can create citations 19 | # bibtex_bibfiles: 20 | # - references.bib 21 | 22 | # Information about where the book exists on the web 23 | repository: 24 | url: https://github.com/feilong/brainplotlib # Online location of your book 25 | path_to_book: docs # Optional path to your book, relative to the repository root 26 | branch: master # Which branch of the repository should be used when creating links (optional) 27 | 28 | # Add GitHub buttons to your book 29 | # See https://jupyterbook.org/customize/config.html#add-a-link-to-your-repository 30 | html: 31 | use_issues_button: true 32 | use_repository_button: true 33 | 34 | parse: 35 | myst_substitutions: 36 | gallery: | 37 | `````{grid} 38 | 39 | ````{grid-item-card} 40 | :columns: 4 41 | :class-header: bg-light text-center 42 | :class-body: text-center m-0 p-0 43 | :link-type: doc 44 | :link: /examples/save_image 45 | Save the high-res image 46 | ^^^ 47 | ```{glue:figure} save_image 48 | ``` 49 | ```` 50 | 51 | ````{grid-item-card} 52 | :columns: 4 53 | :class-header: bg-light text-center 54 | :class-body: text-center m-0 p-0 55 | :link-type: doc 56 | :link: /examples/plot_colorbar 57 | Plot a colorbar 58 | ^^^ 59 | ```{glue:figure} with_colorbar 60 | ``` 61 | ```` 62 | 63 | ````{grid-item-card} 64 | :columns: 4 65 | :class-header: bg-light text-center 66 | :class-body: text-center m-0 p-0 67 | :link-type: doc 68 | :link: /examples/different_cmaps 69 | Different colormaps 70 | ^^^ 71 | ```{glue:figure} different_cmaps 72 | ``` 73 | ```` 74 | 75 | ````` 76 | 77 | `````{grid} 78 | 79 | ````{grid-item-card} 80 | :columns: 4 81 | :class-header: bg-light text-center 82 | :class-body: text-center m-0 p-0 83 | :link-type: doc 84 | :link: /examples/different_resolutions 85 | Different data resolutions 86 | ^^^ 87 | ```{glue:figure} different_resolutions 88 | ``` 89 | ```` 90 | 91 | ````{grid-item-card} 92 | :columns: 4 93 | :class-header: bg-light text-center 94 | :class-body: text-center m-0 p-0 95 | :link-type: doc 96 | :link: /examples/cortical_masks 97 | Handle cortical masks 98 | ^^^ 99 | ```{glue:figure} cortical_masks 100 | ``` 101 | ```` 102 | 103 | ````{grid-item-card} 104 | :columns: 4 105 | :class-header: bg-light text-center 106 | :class-body: text-center m-0 p-0 107 | :link-type: doc 108 | :link: /examples/different_surfaces 109 | Alternative surface types 110 | ^^^ 111 | ```{glue:figure} different_surfaces 112 | ``` 113 | ```` 114 | 115 | ````` 116 | 117 | gallery_link : | 118 | --- 119 | [**<< Go back to the gallery of examples**](/examples/index) 120 | -------------------------------------------------------------------------------- /docs/_toc.yml: -------------------------------------------------------------------------------- 1 | # Table of contents 2 | # Learn more at https://jupyterbook.org/customize/toc.html 3 | 4 | format: jb-book 5 | root: index 6 | parts: 7 | - caption: Examples 8 | chapters: 9 | - file: examples/index 10 | - file: examples/save_image 11 | - file: examples/plot_colorbar 12 | - file: examples/different_cmaps 13 | - file: examples/different_resolutions 14 | - file: examples/cortical_masks 15 | - file: examples/different_surfaces 16 | -------------------------------------------------------------------------------- /docs/brainplotlib_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feilong/brainplotlib/fbca555fcd34488ac2bca80f58b0e2d7fb2f9a28/docs/brainplotlib_logo.png -------------------------------------------------------------------------------- /docs/examples/cortical_masks.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | cell_metadata_filter: -all 4 | formats: md:myst 5 | text_representation: 6 | extension: .md 7 | format_name: myst 8 | format_version: 0.13 9 | jupytext_version: 1.11.5 10 | kernelspec: 11 | display_name: Python 3 12 | language: python 13 | name: python3 14 | --- 15 | 16 | # Handle cortical masks 17 | 18 | Not every vertex on surface belongs to the cerebral cortex. 19 | There are approximately 8.5% of vertices around the medial wall that are non-cortical. 20 | When analyzing fMRI data on surface, we usually want to focus on vertices of the cerebral cortex and mask out non-cortical vertices. 21 | This masking step changes the number of vertices and the shape of the data. 22 | 23 | ```{margin} Different surface spaces 24 | If you use the cortical mask of the `fsaverage5` or `fsaverage6` surface instead of `fsaverage`, the number of cortical vertices would slightly differ. 25 | ``` 26 | For example, with the `icoorder5` resolution `fsaverage` surface there are 10242 vertices per hemisphere in total. 27 | After masking out the non-cortical vertices, there are 9372 and 9370 vertices for the left and right hemispheres, respectively. 28 | 29 | The `brain_plot` function automatically detects whether the data has been masked or not and render it accordingly. 30 | 31 | ```{glue:} cortical_masks 32 | ``` 33 | 34 | ```{code-cell}python 35 | import numpy as np 36 | from brainplotlib import brain_plot 37 | import matplotlib.pyplot as plt 38 | 39 | rng = np.random.default_rng(0) 40 | ``` 41 | 42 | ```{code-cell}python 43 | :tags: ["remove-output"] 44 | fig, axs = plt.subplots(2, 2, dpi=300, figsize=([_/300 + 1 for _ in [1728, 1560]])) 45 | for i in range(2): 46 | for j in range(2): 47 | if (i, j) == (0, 0): 48 | v = rng.random((588 + 587, )) 49 | title = 'masked icoorder3' 50 | elif (i, j) == (0, 1): 51 | v = rng.random((642 * 2, )) 52 | title = 'non-masked icoorder3' 53 | elif (i, j) == (1, 0): 54 | v = rng.random((9372 + 9370, )) 55 | title = 'masked icoorder5' 56 | elif (i, j) == (1, 1): 57 | v = rng.random((10242 * 2, )) 58 | title = 'non-masked icoorder5' 59 | 60 | ax = axs[i][j] 61 | img = brain_plot(v, vmax=1, vmin=0) 62 | ax.imshow(img) 63 | ax.axis('off') 64 | ax.set_title(title) 65 | plt.show() 66 | ``` 67 | 68 | ```{code-cell}python 69 | :tags: ["remove-cell"] 70 | from myst_nb import glue 71 | glue('cortical_masks', fig, display=False) 72 | ``` 73 | 74 | ## Different forms of input 75 | 76 | The `brain_plot` function also handles different forms of input. The values to be plotted on surface can be one of: 77 | - Two NumPy arrays for the left and right hemispheres, respectively. 78 | - A list or tuple of NumPy arrays with two elements. 79 | - A concatenated NumPy array comprising data from both left and right hemispheres (left hemisphere first). 80 | 81 | The same image can be generated using different forms of input, as long as the actual data is the same. 82 | ```{code-cell}python 83 | lh, rh = rng.random((588, )), rng.random((587, )) 84 | 85 | img1 = brain_plot(lh, rh, vmax=1, vmin=0) 86 | img2 = brain_plot([lh, rh], vmax=1, vmin=0) 87 | img3 = brain_plot(np.concatenate([lh, rh], axis=0), vmax=1, vmin=0) 88 | 89 | np.testing.assert_array_equal(img1, img2) 90 | np.testing.assert_array_equal(img1, img3) 91 | ``` 92 | 93 | {{ gallery_link }} 94 | -------------------------------------------------------------------------------- /docs/examples/different_cmaps.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | cell_metadata_filter: -all 4 | formats: md:myst 5 | text_representation: 6 | extension: .md 7 | format_name: myst 8 | format_version: 0.13 9 | jupytext_version: 1.11.5 10 | kernelspec: 11 | display_name: Python 3 12 | language: python 13 | name: python3 14 | --- 15 | 16 | # Use different colormaps 17 | 18 | This example shows how to use different colormaps. 19 | 20 | ```{glue:} different_cmaps 21 | ``` 22 | 23 | ```{code-cell}python 24 | import numpy as np 25 | from brainplotlib import brain_plot 26 | import matplotlib.pyplot as plt 27 | 28 | rng = np.random.default_rng(0) 29 | v = rng.random((1175, )) 30 | ``` 31 | ```{margin} Change the colormap 32 | The `cmap` parameter of `brain_plot` can use any `matplotlib` colormaps in a similar way as `plt.plot`. 33 | ``` 34 | ```{code-cell}python 35 | :tags: ["remove-output"] 36 | fig, axs = plt.subplots(2, 2, dpi=300, figsize=([_/300 + 1 for _ in [1728, 1560]])) 37 | cmaps = ['viridis', 'jet', 'bwr', 'plasma'] 38 | for i in range(2): 39 | for j in range(2): 40 | ax = axs[i][j] 41 | cmap = cmaps[i*2+j] 42 | img = brain_plot(v, vmax=1, vmin=0, cmap=cmap) 43 | ax.imshow(img) 44 | ax.axis('off') 45 | ax.set_title(cmap) 46 | plt.show() 47 | ``` 48 | ```{code-cell}python 49 | :tags: ["remove-cell"] 50 | from myst_nb import glue 51 | glue('different_cmaps', fig, display=False) 52 | ``` 53 | 54 | {{ gallery_link }} 55 | -------------------------------------------------------------------------------- /docs/examples/different_resolutions.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | cell_metadata_filter: -all 4 | formats: md:myst 5 | text_representation: 6 | extension: .md 7 | format_name: myst 8 | format_version: 0.13 9 | jupytext_version: 1.11.5 10 | kernelspec: 11 | display_name: Python 3 12 | language: python 13 | name: python3 14 | --- 15 | 16 | # Different data resolutions 17 | 18 | This example is about different input data resolutions. 19 | 20 | ```{glue:} different_resolutions 21 | ``` 22 | 23 | Neuroimaging data on cortical surface has different spatial resolutions: 24 | 25 | | name | resolution | # of vertices per hemishere | 26 | | --- | --- | --- | 27 | | icoorder7 | 0.7 mm | 163842 | 28 | | icoorder6 | 1.5 mm | 40962 | 29 | | icoorder5 | 3 mm | 10242 | 30 | | icoorder4 | 6 mm | 2562 | 31 | | icoorder3 | 13 mm | 642 | 32 | 33 | Typical the data resolution is between `icoorder7` (usually anatomical scans) and `icoorder5` (usually functional scans). `icoorder3` resolution is often used in defining connectivity targets for [connectivity hyperalignment](https://doi.org/10.1371/journal.pcbi.1006120) and in examples. 34 | 35 | The number of cortical vertices **per hemisphere** can be computed based on icoorder: $ n_v = 4^{icoorder} \times 10 + 2 $, where $n_v$ is the number of vertices. 36 | 37 | The `brain_plot` function automatically handles various data resolution between `icoorder7` and `icoorder3`. When the input data has a lower resolution than `icoorder7`, it's automatically upsampled to the `icoorder7` resolution in a nearest-neighbor manner based on the [Voronoi diagram](https://en.wikipedia.org/wiki/Voronoi_diagram). The data is always visualized using the `icoorder7` high-resolution surface (i.e., `fsaverage`). 38 | 39 | ```{code-cell}python 40 | import numpy as np 41 | from brainplotlib import brain_plot 42 | import matplotlib.pyplot as plt 43 | 44 | rng = np.random.default_rng(0) 45 | ``` 46 | ```{code-cell}python 47 | :tags: ["remove-output"] 48 | fig, axs = plt.subplots(2, 2, dpi=300, figsize=([_/300 + 1 for _ in [1728, 1560]])) 49 | icoorders = [3, 5, 6, 7] 50 | for i in range(2): 51 | for j in range(2): 52 | ax = axs[i][j] 53 | icoorder = icoorders[i*2+j] 54 | nv = 4**icoorder * 10 + 2 55 | v = rng.random((nv * 2, )) 56 | img = brain_plot(v, vmax=1, vmin=0, cmap='coolwarm') 57 | ax.imshow(img) 58 | ax.axis('off') 59 | ax.set_title(f'icoorder{icoorder}') 60 | plt.show() 61 | ``` 62 | ```{code-cell}python 63 | :tags: ["remove-cell"] 64 | from myst_nb import glue 65 | glue('different_resolutions', fig, display=False) 66 | ``` 67 | 68 | {{ gallery_link }} 69 | -------------------------------------------------------------------------------- /docs/examples/different_surfaces.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | cell_metadata_filter: -all 4 | formats: md:myst 5 | text_representation: 6 | extension: .md 7 | format_name: myst 8 | format_version: 0.13 9 | jupytext_version: 1.11.5 10 | kernelspec: 11 | display_name: Python 3 12 | language: python 13 | name: python3 14 | --- 15 | 16 | # Use different types of surface 17 | 18 | By default `brain_plot` uses the `inflated` surface. 19 | This can be changed to the `pial`, `white`, or `midthickness` (average of `pial` and `white`) surface using the `surf_type` parameter. 20 | 21 | ```{glue:} different_surfaces 22 | ``` 23 | 24 | ```{code-cell}python 25 | import numpy as np 26 | from brainplotlib import brain_plot 27 | import matplotlib.pyplot as plt 28 | 29 | rng = np.random.default_rng(0) 30 | v = rng.random((1175, )) 31 | ``` 32 | ```{code-cell}python 33 | :tags: ["remove-output"] 34 | fig, axs = plt.subplots(2, 2, dpi=300, figsize=([_/300 + 1 for _ in [1728, 1560]])) 35 | surf_types = ['inflated', 'midthickness', 'pial', 'white'] 36 | for i in range(2): 37 | for j in range(2): 38 | ax = axs[i][j] 39 | surf_type = surf_types[i*2+j] 40 | img = brain_plot(v, vmax=1, vmin=0, cmap='rainbow', surf_type=surf_type) 41 | ax.imshow(img) 42 | ax.axis('off') 43 | ax.set_title(f'{surf_type} surface') 44 | plt.show() 45 | ``` 46 | ```{code-cell}python 47 | :tags: ["remove-cell"] 48 | from myst_nb import glue 49 | glue('different_surfaces', fig, display=False) 50 | ``` 51 | 52 | {{ gallery_link }} 53 | -------------------------------------------------------------------------------- /docs/examples/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | cell_metadata_filter: -all 4 | formats: md:myst 5 | text_representation: 6 | extension: .md 7 | format_name: myst 8 | format_version: 0.13 9 | jupytext_version: 1.11.5 10 | kernelspec: 11 | display_name: Python 3 12 | language: python 13 | name: python3 14 | --- 15 | 16 | # Gallery of examples 17 | 18 | Click each image below to see the corresponding example code and explanations. 19 | 20 | {{ gallery }} 21 | -------------------------------------------------------------------------------- /docs/examples/plot_colorbar.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | cell_metadata_filter: -all 4 | formats: md:myst 5 | text_representation: 6 | extension: .md 7 | format_name: myst 8 | format_version: 0.13 9 | jupytext_version: 1.11.5 10 | kernelspec: 11 | display_name: Python 3 12 | language: python 13 | name: python3 14 | --- 15 | 16 | # Plot a colorbar along with the image 17 | 18 | This example shows how to plot a colorbar besides the brain. 19 | 20 | ```{glue:} with_colorbar 21 | ``` 22 | 23 | ```{margin} Example data 24 | The example data is a NumPy array combining masked data of both hemispheres, based on a face-selectivity map from [Jiahui et al. (2020)](https://doi.org/10.1016/j.neuroimage.2019.116458) [Figure 5](https://www.sciencedirect.com/science/article/pii/S1053811919310493#fig5). 25 | ``` 26 | ```{code-cell}python 27 | import numpy as np 28 | from brainplotlib import brain_plot, example_data 29 | import matplotlib.pyplot as plt 30 | 31 | print(example_data.shape, example_data.dtype) 32 | ``` 33 | ```{margin} Get color scale information 34 | The `return_scale` parameter allows returning the color scale information along with the image itself, which can then be used by `plt.colorbar`. 35 | ``` 36 | ```{code-cell}python 37 | img, scale = brain_plot( 38 | example_data, vmax=10, vmin=-10, cmap='seismic', return_scale=True) 39 | ``` 40 | ```{code-cell}python 41 | :tags: ["remove-output"] 42 | fig = plt.figure( 43 | figsize=(img.shape[1] / 300, img.shape[0] / 300), dpi=300) 44 | plt.imshow(img) 45 | plt.axis('off') 46 | cbar = plt.colorbar(scale, shrink=0.8, aspect=30) 47 | plt.show() 48 | ``` 49 | 50 | ```{code-cell}python 51 | :tags: ["remove-cell"] 52 | from myst_nb import glue 53 | glue('with_colorbar', fig, display=False) 54 | ``` 55 | 56 | {{ gallery_link }} 57 | -------------------------------------------------------------------------------- /docs/examples/save_image.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | cell_metadata_filter: -all 4 | formats: md:myst 5 | text_representation: 6 | extension: .md 7 | format_name: myst 8 | format_version: 0.13 9 | jupytext_version: 1.11.5 10 | kernelspec: 11 | display_name: Python 3 12 | language: python 13 | name: python3 14 | --- 15 | 16 | # Save the high-res image 17 | 18 | This example shows how to plot random data on the brain and save it as an image file. 19 | 20 | ![](save_image_pillow.png) 21 | 22 | 23 | ## Render the image 24 | 25 | The `brain_plot` function renders the image based on data `v` and returns a NumPy array. 26 | The Numpy array has a `np.float64` dtype, and the range of its values is 0--1. 27 | 28 | ```{code-cell}python 29 | import numpy as np 30 | from brainplotlib import brain_plot 31 | 32 | rng = np.random.default_rng(0) 33 | v = rng.random((1175, )) 34 | 35 | img = brain_plot(v, vmax=1, vmin=0, cmap='viridis') 36 | 37 | print(img.dtype, img.shape) 38 | print(img.max(), img.min()) 39 | ``` 40 | 41 | 42 | ## Save the image 43 | The rendered image can be saved using the package of your choice: 44 | 45 | ```{code-cell}python 46 | :tags: ["remove-cell"] 47 | import matplotlib.pyplot as plt 48 | fig = plt.figure(figsize=(img.shape[1] / 200, img.shape[0] / 200), dpi=200) 49 | ax = fig.add_axes([0, 0, 1, 1]) 50 | ax.imshow(img) 51 | ax.axis('off') 52 | plt.savefig('save_image_matplotlib.png') 53 | plt.close() 54 | ``` 55 | ```{code-cell}python 56 | :tags: ["remove-cell"] 57 | from PIL import Image 58 | im = Image.fromarray( 59 | np.round(img * 255).astype(np.uint8)) 60 | im.save('save_image_pillow.png') 61 | ``` 62 | ```{code-cell}python 63 | :tags: ["remove-cell"] 64 | from myst_nb import glue 65 | glue('save_image', im, display=False) 66 | ``` 67 | ```{code-cell}python 68 | :tags: ["remove-cell"] 69 | import cv2 70 | ## The default channel order of OpenCV is BGR rather than RGB. 71 | reorder = {3: [2, 1, 0], 4: [2, 1, 0, 3]}[img.shape[2]] 72 | cv2.imwrite( 73 | 'save_image_opencv.png', 74 | np.round(img[:, :, reorder] * 255).astype(np.uint8)) 75 | ``` 76 | 77 | `````{tab-set} 78 | ````{tab-item} Using matplotlib 79 | ```python 80 | import matplotlib.pyplot as plt 81 | fig = plt.figure( 82 | figsize=(img.shape[1] / 200, img.shape[0] / 200), dpi=200) 83 | ax = fig.add_axes([0, 0, 1, 1]) 84 | ax.imshow(img) 85 | ax.axis('off') 86 | plt.savefig('save_image_matplotlib.png') 87 | plt.close() 88 | ``` 89 | ```` 90 | ````{tab-item} Using Pillow 91 | ```python 92 | from PIL import Image 93 | im = Image.fromarray( 94 | np.round(img * 255).astype(np.uint8)) 95 | im.save('save_image_pillow.png') 96 | ``` 97 | ```{note} 98 | The code block above requires that the [Pillow package](https://pillow.readthedocs.io/en/stable/index.html) has been installed. 99 | ``` 100 | ```` 101 | ````{tab-item} Using OpenCV 102 | ```python 103 | import cv2 104 | ## The default channel order of OpenCV is BGR rather than RGB. 105 | reorder = {3: [2, 1, 0], 4: [2, 1, 0, 3]}[img.shape[2]] 106 | cv2.imwrite( 107 | 'save_image_opencv.png', 108 | np.round(img[:, :, reorder] * 255).astype(np.uint8)) 109 | ``` 110 | ```{note} 111 | The code block above requires that [OpenCV](https://opencv.org/) and its Python bindings to be installed. 112 | ``` 113 | ```` 114 | ````` 115 | 116 | ## Comparison of saved images 117 | ````{grid} 118 | ```{grid-item-card} 119 | :class-header: bg-light text-center 120 | Using matplotlib 121 | ^^^ 122 | ![](save_image_matplotlib.png) 123 | ``` 124 | ```{grid-item-card} 125 | :class-header: bg-light text-center 126 | Using Pillow 127 | ^^^ 128 | ![](save_image_pillow.png) 129 | ``` 130 | ```{grid-item-card} 131 | :class-header: bg-light text-center 132 | Using OpenCV 133 | ^^^ 134 | ![](save_image_opencv.png) 135 | ``` 136 | ```` 137 | 138 | {{ gallery_link }} 139 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Welcome to brainplotlib 2 | 3 | `brainplotlib` is a Python package that can plot data on cortical surface. 4 | It is designed to have minimal requirements. 5 | It only depends on [`NumPy`](https://numpy.org/) and [`matplotlib`](https://matplotlib.org/). 6 | 7 | 8 | ## Installation 9 | 10 | `brainplotlib` can be easily installed with pip 11 | ```bash 12 | python -m pip install brainplotlib 13 | ``` 14 | 15 | 16 | ## Examples 17 | 18 | The examples gallery contains example scripts demonstrating common ways to use the package. 19 | Click an image to see the corresponding example script and explanations. 20 | 21 | {{ gallery }} 22 | 23 | 24 | ## Citation 25 | 26 | Please cite the package if you use it in your publications 27 | [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.5979819.svg)](https://doi.org/10.5281/zenodo.5979819) 28 | ```bibtex 29 | @software{brainplotlib, 30 | author = {Ma Feilong and Guo Jiahui and M. Ida Gobbini and James V. Haxby}, 31 | title = {{brainplotlib: plotting brain data on cortical surface}}, 32 | month = feb, 33 | year = 2022, 34 | publisher = {Zenodo}, 35 | doi = {10.5281/zenodo.5979819}, 36 | url = {https://doi.org/10.5281/zenodo.5979819} 37 | } 38 | ``` 39 | -------------------------------------------------------------------------------- /images/example_data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feilong/brainplotlib/fbca555fcd34488ac2bca80f58b0e2d7fb2f9a28/images/example_data.png -------------------------------------------------------------------------------- /images/example_data_with_colorbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feilong/brainplotlib/fbca555fcd34488ac2bca80f58b0e2d7fb2f9a28/images/example_data_with_colorbar.png -------------------------------------------------------------------------------- /images/random_data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feilong/brainplotlib/fbca555fcd34488ac2bca80f58b0e2d7fb2f9a28/images/random_data.png -------------------------------------------------------------------------------- /images/random_data_with_colorbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feilong/brainplotlib/fbca555fcd34488ac2bca80f58b0e2d7fb2f9a28/images/random_data_with_colorbar.png -------------------------------------------------------------------------------- /nitrc.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | tool_resource_name: brainplotlib 3 | associations: [] 4 | organization: "" 5 | description: brainplotlib is a Python package that plots data on cortical surface. 6 | It's designed to have minimal requirements --- only NumPy and matplotlib. 7 | non_member_message: This tool/resource's administrator will have to grant you permission 8 | to view this page. 9 | funding_source: ~ 10 | homepage: http://www.nitrc.org/projects/brainplotlib/ 11 | center: "" 12 | neurodebian_package: ~ 13 | neurolex_id: ~ 14 | ... 15 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools>=42", 4 | "wheel" 5 | ] 6 | build-backend = "setuptools.build_meta" 7 | -------------------------------------------------------------------------------- /scripts/compute_fsaverage_mapping_part1.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | 4 | from brainplotlib3d import ValuesIcoorder, Surface, plot_to_file 5 | 6 | 7 | if __name__ == '__main__': 8 | # nv = 1175 # 588 + 587 9 | nv = 4**7 * 10 + 2 10 | v = np.arange(nv * 2, dtype=int) 11 | c1 = v // 256 **2 12 | c2 = (v // 256) % 256 13 | c3 = v % 256 14 | c = np.stack([c1, c2, c3], axis=1) / 255. 15 | v = np.array_split(c, 2, axis=0) 16 | values = ValuesIcoorder(v[0], v[1], fill_nan=0.8, space='fsaverage', icoorder=7) 17 | 18 | for surf_type in ['inflated', 'pial', 'midthickness', 'white']: 19 | out_fn = f'fsaverage_{surf_type}.npy' 20 | if os.path.exists(out_fn): 21 | continue 22 | 23 | zoom = 0.015 24 | if surf_type == 'inflated': 25 | zoom = 0.013 26 | 27 | surf = Surface(surf_type, 'FS') 28 | surf.add_colors(values) 29 | actors = surf.get_actors(ambient=1, diffuse=0, specular=0) 30 | 31 | img = plot_to_file([actors[0]], [actors[1]], 32 | surf.focal_points, out_fn=None, 33 | zoom=zoom, magnification=1, aa_frames=1) 34 | np.save(out_fn, img) 35 | exit(0) 36 | -------------------------------------------------------------------------------- /scripts/compute_fsaverage_mapping_part2.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import numpy as np 3 | 4 | 5 | if __name__ == '__main__': 6 | for surf_type in ['inflated', 'pial', 'midthickness', 'white']: 7 | img = np.load(f'fsaverage_{surf_type}.npy') 8 | nv = 4**7 * 10 + 2 9 | mask = (img[:, :, 3] == 0.0) 10 | img = np.round(img[:, :, :3] * 255).astype(int) 11 | v = ((img[:, :, 0] * 256**2) + (img[:, :, 1] * 256) + img[:, :, 2]) 12 | print(v.shape, mask.shape) 13 | v[mask] = -1 14 | np.save(f'fsaverage_to_{surf_type}_image.npy', v) 15 | print(v.max(), v.min(), np.unique(v).shape) 16 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = brainplotlib 3 | version = 1.0.0 4 | author = Ma Feilong 5 | author_email = mafeilong@gmail.com 6 | description = A lightweight package to plot brain surfaces with Python. 7 | long_description = file: README.md 8 | long_description_content_type = text/markdown 9 | keywords = brain, surface, fmri, plotting, visualization 10 | url = https://feilong.github.io/brainplotlib/ 11 | project_urls = 12 | Bug Tracker = https://github.com/feilong/brainplotlib/issues 13 | Source Code = https://github.com/feilong/brainplotlib 14 | classifiers = 15 | Programming Language :: Python :: 3 :: Only 16 | Programming Language :: Python :: 3.6 17 | Programming Language :: Python :: 3.7 18 | Programming Language :: Python :: 3.8 19 | Programming Language :: Python :: 3.9 20 | Programming Language :: Python :: 3.10 21 | 22 | [options] 23 | package_dir = 24 | = src 25 | packages = find: 26 | python_requires = >=3.6 27 | include_package_data = True 28 | 29 | install_requires = 30 | numpy 31 | matplotlib 32 | 33 | [options.package_data] 34 | brainplotlib = 35 | data/*.npy 36 | 37 | [options.packages.find] 38 | where = src 39 | 40 | [options.extras_require] 41 | tests = 42 | pytest 43 | -------------------------------------------------------------------------------- /src/brainplotlib/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | from matplotlib import cm, colors 4 | 5 | 6 | DATA_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'data') 7 | 8 | PLOT_MAPPING = {surf_type: np.load(os.path.join(DATA_DIR, f'fsaverage_to_{surf_type}_image.npy')) 9 | for surf_type in ['inflated', 'pial', 'midthickness', 'white']} 10 | 11 | example_data = np.load(os.path.join(DATA_DIR, 'example_data.npy')) 12 | 13 | GUESS_SEPARATE = { 14 | ## masked data 15 | # (588, 587): ('fsaverage5', 3, True), 16 | # (588, 587): ('fsaverage6', 3, True), 17 | (588, 587): ('fsaverage', 3, True), 18 | 19 | (2341, 2346): ('fsaverage5', 4, True), 20 | (2343, 2347): ('fsaverage6', 4, True), 21 | (2343, 2344): ('fsaverage', 4, True), 22 | 23 | (9354, 9361): ('fsaverage5', 5, True), 24 | (9372, 9369): ('fsaverage6', 5, True), 25 | (9372, 9370): ('fsaverage', 5, True), 26 | 27 | (37476, 37471): ('fsaverage6', 6, True), 28 | (37487, 37482): ('fsaverage', 6, True), 29 | 30 | (149955, 149926): ('fsaverage', 7, True), 31 | 32 | ## unmasked data 33 | (642, 642): ('fsaverage', 3, False), 34 | (2562, 2562): ('fsaverage', 4, False), 35 | (10242, 10242): ('fsaverage', 5, False), 36 | (40962, 40962): ('fsaverage', 6, False), 37 | (163842, 163842): ('fsaverage', 7, False), 38 | } 39 | 40 | GUESS_COMBINED = { 41 | ## masked data 42 | # 1175: ('fsaverage5', 3, True, [588]), 43 | # 1175: ('fsaverage6', 3, True, [588]), 44 | 1175: ('fsaverage', 3, True, [588]), 45 | 46 | # 4687: ('fsaverage5', 4, True, [2341]), 47 | 4690: ('fsaverage6', 4, True, [2343]), 48 | 4687: ('fsaverage', 4, True, [2343]), 49 | 50 | 18715: ('fsaverage5', 5, True, [9354]), 51 | 18741: ('fsaverage6', 5, True, [9372]), 52 | 18742: ('fsaverage', 5, True, [9372]), 53 | 54 | 74947: ('fsaverage6', 6, True, [37476]), 55 | 74969: ('fsaverage', 6, True, [37487]), 56 | 57 | 299881: ('fsaverage', 7, True, [149955]), 58 | 59 | ## unmasked data 60 | 1284: ('fsaverage', 3, False, [642]), 61 | 5124: ('fsaverage', 4, False, [2562]), 62 | 20484: ('fsaverage', 5, False, [10242]), 63 | 81924: ('fsaverage', 6, False, [40962]), 64 | 327684: ('fsaverage', 7, False, [163842]), 65 | } 66 | 67 | 68 | def unmask_and_upsample(lh, rh, space, icoorder, masked): 69 | nv = 4**icoorder * 10 + 2 70 | new_values = [] 71 | for v, lr in zip([lh, rh], 'lr'): 72 | if masked: 73 | mask = np.load(os.path.join(DATA_DIR, f'mask_{space}_{lr}h.npy'))[:nv] 74 | vv = np.full((nv, ) + v.shape[1:], np.nan) 75 | vv[mask] = v 76 | else: 77 | vv = v 78 | 79 | if icoorder < 7: 80 | voronoi = np.load(os.path.join(DATA_DIR, f'voronoi_fsaverage_{lr}h_icoorder{icoorder}.npy')) 81 | vv = vv[voronoi] 82 | new_values.append(vv) 83 | new_values = np.concatenate(new_values, axis=0) 84 | return new_values 85 | 86 | 87 | def prepare_data(*values): 88 | while isinstance(values, (tuple, list)) and len(values) == 1: 89 | values = values[0] 90 | 91 | if isinstance(values, (tuple, list)) and len(values) == 2: 92 | ## separate left and right hemisphere 93 | lh, rh = values 94 | shapes = (lh.shape[0], rh.shape[0]) 95 | space, icoorder, masked = GUESS_SEPARATE[shapes] 96 | new_values = unmask_and_upsample(lh, rh, space, icoorder, masked) 97 | 98 | else: 99 | ## combined hemispheres 100 | space, icoorder, masked, sections = GUESS_COMBINED[values.shape[0]] 101 | lh, rh = np.array_split(values, sections, axis=0) 102 | new_values = unmask_and_upsample(lh, rh, space, icoorder, masked) 103 | 104 | return new_values 105 | 106 | 107 | def brain_plot(*values, vmax, vmin, cmap=None, medial_wall_color=[0.8, 0.8, 0.8, 1.0], background_color=[1.0, 1.0, 1.0, 0.0], return_scale=False, surf_type='inflated'): 108 | values = prepare_data(*values) 109 | nan_mask = np.isnan(values) 110 | r = (values - vmin) / (vmax - vmin) 111 | r = np.clip(r, 0.0, 1.0) 112 | cmap = cm.get_cmap(cmap) 113 | c = cmap(r) 114 | c[nan_mask] = medial_wall_color 115 | c = np.concatenate([c, [_[:c.shape[1]] for _ in [medial_wall_color, background_color]]], axis=0) 116 | img = c[PLOT_MAPPING[surf_type]] 117 | if return_scale: 118 | norm = colors.Normalize(vmax=vmax, vmin=vmin, clip=True) 119 | scale = cm.ScalarMappable(norm=norm, cmap=cmap) 120 | return img, scale 121 | return img 122 | -------------------------------------------------------------------------------- /src/brainplotlib/data/example_data.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feilong/brainplotlib/fbca555fcd34488ac2bca80f58b0e2d7fb2f9a28/src/brainplotlib/data/example_data.npy -------------------------------------------------------------------------------- /src/brainplotlib/data/fsaverage_to_inflated_image.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feilong/brainplotlib/fbca555fcd34488ac2bca80f58b0e2d7fb2f9a28/src/brainplotlib/data/fsaverage_to_inflated_image.npy -------------------------------------------------------------------------------- /src/brainplotlib/data/fsaverage_to_midthickness_image.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feilong/brainplotlib/fbca555fcd34488ac2bca80f58b0e2d7fb2f9a28/src/brainplotlib/data/fsaverage_to_midthickness_image.npy -------------------------------------------------------------------------------- /src/brainplotlib/data/fsaverage_to_pial_image.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feilong/brainplotlib/fbca555fcd34488ac2bca80f58b0e2d7fb2f9a28/src/brainplotlib/data/fsaverage_to_pial_image.npy -------------------------------------------------------------------------------- /src/brainplotlib/data/fsaverage_to_white_image.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feilong/brainplotlib/fbca555fcd34488ac2bca80f58b0e2d7fb2f9a28/src/brainplotlib/data/fsaverage_to_white_image.npy -------------------------------------------------------------------------------- /src/brainplotlib/data/mask_fsaverage5_lh.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feilong/brainplotlib/fbca555fcd34488ac2bca80f58b0e2d7fb2f9a28/src/brainplotlib/data/mask_fsaverage5_lh.npy -------------------------------------------------------------------------------- /src/brainplotlib/data/mask_fsaverage5_rh.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feilong/brainplotlib/fbca555fcd34488ac2bca80f58b0e2d7fb2f9a28/src/brainplotlib/data/mask_fsaverage5_rh.npy -------------------------------------------------------------------------------- /src/brainplotlib/data/mask_fsaverage6_lh.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feilong/brainplotlib/fbca555fcd34488ac2bca80f58b0e2d7fb2f9a28/src/brainplotlib/data/mask_fsaverage6_lh.npy -------------------------------------------------------------------------------- /src/brainplotlib/data/mask_fsaverage6_rh.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feilong/brainplotlib/fbca555fcd34488ac2bca80f58b0e2d7fb2f9a28/src/brainplotlib/data/mask_fsaverage6_rh.npy -------------------------------------------------------------------------------- /src/brainplotlib/data/mask_fsaverage_lh.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feilong/brainplotlib/fbca555fcd34488ac2bca80f58b0e2d7fb2f9a28/src/brainplotlib/data/mask_fsaverage_lh.npy -------------------------------------------------------------------------------- /src/brainplotlib/data/mask_fsaverage_rh.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feilong/brainplotlib/fbca555fcd34488ac2bca80f58b0e2d7fb2f9a28/src/brainplotlib/data/mask_fsaverage_rh.npy -------------------------------------------------------------------------------- /src/brainplotlib/data/voronoi_fsaverage_lh_icoorder3.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feilong/brainplotlib/fbca555fcd34488ac2bca80f58b0e2d7fb2f9a28/src/brainplotlib/data/voronoi_fsaverage_lh_icoorder3.npy -------------------------------------------------------------------------------- /src/brainplotlib/data/voronoi_fsaverage_lh_icoorder4.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feilong/brainplotlib/fbca555fcd34488ac2bca80f58b0e2d7fb2f9a28/src/brainplotlib/data/voronoi_fsaverage_lh_icoorder4.npy -------------------------------------------------------------------------------- /src/brainplotlib/data/voronoi_fsaverage_lh_icoorder5.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feilong/brainplotlib/fbca555fcd34488ac2bca80f58b0e2d7fb2f9a28/src/brainplotlib/data/voronoi_fsaverage_lh_icoorder5.npy -------------------------------------------------------------------------------- /src/brainplotlib/data/voronoi_fsaverage_lh_icoorder6.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feilong/brainplotlib/fbca555fcd34488ac2bca80f58b0e2d7fb2f9a28/src/brainplotlib/data/voronoi_fsaverage_lh_icoorder6.npy -------------------------------------------------------------------------------- /src/brainplotlib/data/voronoi_fsaverage_rh_icoorder3.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feilong/brainplotlib/fbca555fcd34488ac2bca80f58b0e2d7fb2f9a28/src/brainplotlib/data/voronoi_fsaverage_rh_icoorder3.npy -------------------------------------------------------------------------------- /src/brainplotlib/data/voronoi_fsaverage_rh_icoorder4.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feilong/brainplotlib/fbca555fcd34488ac2bca80f58b0e2d7fb2f9a28/src/brainplotlib/data/voronoi_fsaverage_rh_icoorder4.npy -------------------------------------------------------------------------------- /src/brainplotlib/data/voronoi_fsaverage_rh_icoorder5.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feilong/brainplotlib/fbca555fcd34488ac2bca80f58b0e2d7fb2f9a28/src/brainplotlib/data/voronoi_fsaverage_rh_icoorder5.npy -------------------------------------------------------------------------------- /src/brainplotlib/data/voronoi_fsaverage_rh_icoorder6.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feilong/brainplotlib/fbca555fcd34488ac2bca80f58b0e2d7fb2f9a28/src/brainplotlib/data/voronoi_fsaverage_rh_icoorder6.npy -------------------------------------------------------------------------------- /tests/test_plotting.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | import importlib.util 4 | from brainplotlib import brain_plot 5 | 6 | 7 | class TestPlotting: 8 | def test_icoorder5_masked(self, tmp_path): 9 | values = np.arange(9372), np.arange(9370) 10 | img = brain_plot(*values, vmax=18741, vmin=0, cmap=None) 11 | assert img.shape in [(1560, 1728, 4), (1560, 1728, 3)] 12 | assert img.dtype == np.float64 13 | assert np.all(img <= 1) 14 | assert np.all(img >= 0) 15 | if importlib.util.find_spec('cv2'): 16 | import cv2 17 | cv2.imwrite(os.path.join(tmp_path, 'test_icoorder5_masked.png'), np.round(img * 255).astype(np.uint8)[:, :, [2, 1, 0, 3]]) 18 | 19 | def test_icoorder5_masked_random(self, tmp_path): 20 | rng = np.random.default_rng() 21 | values = rng.random((9372, )), rng.random((9370, )) 22 | img = brain_plot(*values, vmax=1, vmin=0, cmap=None) 23 | assert img.shape in [(1560, 1728, 4), (1560, 1728, 3)] 24 | assert img.dtype == np.float64 25 | assert np.all(img <= 1) 26 | assert np.all(img >= 0) 27 | if importlib.util.find_spec('cv2'): 28 | import cv2 29 | cv2.imwrite(os.path.join(tmp_path, 'test_icoorder5_masked_random.png'), np.round(img * 255).astype(np.uint8)[:, :, [2, 1, 0, 3]]) 30 | 31 | def test_icoorder5_nonmasked(self, tmp_path): 32 | values = np.arange(10242), np.arange(10242) 33 | img = brain_plot(*values, vmax=20483, vmin=0, cmap=None) 34 | assert img.shape in [(1560, 1728, 4), (1560, 1728, 3)] 35 | assert img.dtype == np.float64 36 | assert np.all(img <= 1) 37 | assert np.all(img >= 0) 38 | if importlib.util.find_spec('cv2'): 39 | import cv2 40 | cv2.imwrite(os.path.join(tmp_path, 'test_icoorder5_nonmasked.png'), np.round(img * 255).astype(np.uint8)[:, :, [2, 1, 0, 3]]) 41 | 42 | def test_icoorder5_nonmasked_random(self, tmp_path): 43 | rng = np.random.default_rng() 44 | values = rng.random((10242, )), rng.random((10242, )) 45 | img = brain_plot(*values, vmax=1, vmin=0, cmap=None) 46 | assert img.shape in [(1560, 1728, 4), (1560, 1728, 3)] 47 | assert img.dtype == np.float64 48 | assert np.all(img <= 1) 49 | assert np.all(img >= 0) 50 | if importlib.util.find_spec('cv2'): 51 | import cv2 52 | cv2.imwrite(os.path.join(tmp_path, 'test_icoorder5_nonmasked_random.png'), np.round(img * 255).astype(np.uint8)[:, :, [2, 1, 0, 3]]) 53 | 54 | def test_icoorder3_masked(self, tmp_path): 55 | values = np.arange(588), np.arange(587) 56 | img = brain_plot(*values, vmax=1174, vmin=0, cmap=None) 57 | assert img.shape in [(1560, 1728, 4), (1560, 1728, 3)] 58 | assert img.dtype == np.float64 59 | assert np.all(img <= 1) 60 | assert np.all(img >= 0) 61 | if importlib.util.find_spec('cv2'): 62 | import cv2 63 | cv2.imwrite(os.path.join(tmp_path, 'test_icoorder3_masked.png'), np.round(img * 255).astype(np.uint8)[:, :, [2, 1, 0, 3]]) 64 | 65 | def test_icoorder3_masked_random(self, tmp_path): 66 | rng = np.random.default_rng() 67 | values = rng.random((588, )), rng.random((587, )) 68 | img = brain_plot(*values, vmax=1, vmin=0, cmap=None) 69 | assert img.shape in [(1560, 1728, 4), (1560, 1728, 3)] 70 | assert img.dtype == np.float64 71 | assert np.all(img <= 1) 72 | assert np.all(img >= 0) 73 | if importlib.util.find_spec('cv2'): 74 | import cv2 75 | cv2.imwrite(os.path.join(tmp_path, 'test_icoorder3_masked_random.png'), np.round(img * 255).astype(np.uint8)[:, :, [2, 1, 0, 3]]) 76 | 77 | def test_icoorder3_nonmasked(self, tmp_path): 78 | values = np.arange(642), np.arange(642) 79 | img = brain_plot(*values, vmax=1283, vmin=0, cmap=None) 80 | assert img.shape in [(1560, 1728, 4), (1560, 1728, 3)] 81 | assert img.dtype == np.float64 82 | assert np.all(img <= 1) 83 | assert np.all(img >= 0) 84 | if importlib.util.find_spec('cv2'): 85 | import cv2 86 | cv2.imwrite(os.path.join(tmp_path, 'test_icoorder3_nonmasked.png'), np.round(img * 255).astype(np.uint8)[:, :, [2, 1, 0, 3]]) 87 | 88 | def test_icoorder3_nonmasked(self, tmp_path): 89 | rng = np.random.default_rng() 90 | values = rng.random((642, )), rng.random((642, )) 91 | img = brain_plot(*values, vmax=1, vmin=0, cmap=None) 92 | assert img.shape in [(1560, 1728, 4), (1560, 1728, 3)] 93 | assert img.dtype == np.float64 94 | assert np.all(img <= 1) 95 | assert np.all(img >= 0) 96 | if importlib.util.find_spec('cv2'): 97 | import cv2 98 | cv2.imwrite(os.path.join(tmp_path, 'test_icoorder3_nonmasked_random.png'), np.round(img * 255).astype(np.uint8)[:, :, [2, 1, 0, 3]]) 99 | 100 | 101 | class TestColormaps: 102 | def test_bwr_cmap(self, tmp_path): 103 | rng = np.random.default_rng() 104 | values = rng.random((588, )), rng.random((587, )) 105 | img = brain_plot(*values, vmax=1, vmin=0, cmap='bwr') 106 | assert img.shape in [(1560, 1728, 4), (1560, 1728, 3)] 107 | assert img.dtype == np.float64 108 | assert np.all(img <= 1) 109 | assert np.all(img >= 0) 110 | if importlib.util.find_spec('cv2'): 111 | import cv2 112 | cv2.imwrite(os.path.join(tmp_path, 'test_bwr_cmap.png'), np.round(img * 255).astype(np.uint8)[:, :, [2, 1, 0, 3]]) 113 | 114 | def test_jet_cmap(self, tmp_path): 115 | rng = np.random.default_rng() 116 | values = rng.random((588, )), rng.random((587, )) 117 | img = brain_plot(*values, vmax=1, vmin=0, cmap='jet') 118 | assert img.shape in [(1560, 1728, 4), (1560, 1728, 3)] 119 | assert img.dtype == np.float64 120 | assert np.all(img <= 1) 121 | assert np.all(img >= 0) 122 | if importlib.util.find_spec('cv2'): 123 | import cv2 124 | cv2.imwrite(os.path.join(tmp_path, 'test_jet_cmap.png'), np.round(img * 255).astype(np.uint8)[:, :, [2, 1, 0, 3]]) 125 | 126 | 127 | class TestScale: 128 | def test_color_scale(self, tmp_path): 129 | rng = np.random.default_rng() 130 | values = rng.random((588, )), rng.random((587, )) 131 | img, scale = brain_plot(*values, vmax=1, vmin=0, cmap='viridis', return_scale=True) 132 | from matplotlib import cm 133 | assert isinstance(scale, cm.ScalarMappable) 134 | assert img.shape in [(1560, 1728, 4), (1560, 1728, 3)] 135 | assert img.dtype == np.float64 136 | assert np.all(img <= 1) 137 | assert np.all(img >= 0) 138 | if importlib.util.find_spec('cv2'): 139 | import cv2 140 | cv2.imwrite(os.path.join(tmp_path, 'test_colorscale.png'), np.round(img * 255).astype(np.uint8)[:, :, [2, 1, 0, 3]]) 141 | -------------------------------------------------------------------------------- /tests/test_surf_types.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | import importlib.util 4 | from brainplotlib import brain_plot 5 | 6 | 7 | class TestSurfaceTypes: 8 | def test_inflated_surface(self, tmp_path): 9 | rng = np.random.default_rng() 10 | values = rng.random((588, )), rng.random((587, )) 11 | img, scale = brain_plot(*values, vmax=1, vmin=0, cmap='viridis', return_scale=True, surf_type='inflated') 12 | from matplotlib import cm 13 | assert isinstance(scale, cm.ScalarMappable) 14 | assert img.shape in [(1560, 1728, 4), (1560, 1728, 3)] 15 | assert img.dtype == np.float64 16 | assert np.all(img <= 1) 17 | assert np.all(img >= 0) 18 | if importlib.util.find_spec('cv2'): 19 | import cv2 20 | cv2.imwrite(os.path.join(tmp_path, 'test_inflated.png'), np.round(img * 255).astype(np.uint8)[:, :, [2, 1, 0, 3]]) 21 | 22 | def test_pial_surface(self, tmp_path): 23 | rng = np.random.default_rng() 24 | values = rng.random((588, )), rng.random((587, )) 25 | img, scale = brain_plot(*values, vmax=1, vmin=0, cmap='viridis', return_scale=True, surf_type='pial') 26 | from matplotlib import cm 27 | assert isinstance(scale, cm.ScalarMappable) 28 | assert img.shape in [(1560, 1728, 4), (1560, 1728, 3)] 29 | assert img.dtype == np.float64 30 | assert np.all(img <= 1) 31 | assert np.all(img >= 0) 32 | if importlib.util.find_spec('cv2'): 33 | import cv2 34 | cv2.imwrite(os.path.join(tmp_path, 'test_pial.png'), np.round(img * 255).astype(np.uint8)[:, :, [2, 1, 0, 3]]) 35 | 36 | def test_midthickness_surface(self, tmp_path): 37 | rng = np.random.default_rng() 38 | values = rng.random((588, )), rng.random((587, )) 39 | img, scale = brain_plot(*values, vmax=1, vmin=0, cmap='viridis', return_scale=True, surf_type='midthickness') 40 | from matplotlib import cm 41 | assert isinstance(scale, cm.ScalarMappable) 42 | assert img.shape in [(1560, 1728, 4), (1560, 1728, 3)] 43 | assert img.dtype == np.float64 44 | assert np.all(img <= 1) 45 | assert np.all(img >= 0) 46 | if importlib.util.find_spec('cv2'): 47 | import cv2 48 | cv2.imwrite(os.path.join(tmp_path, 'test_midthickness.png'), np.round(img * 255).astype(np.uint8)[:, :, [2, 1, 0, 3]]) 49 | 50 | def test_white_surface(self, tmp_path): 51 | rng = np.random.default_rng() 52 | values = rng.random((588, )), rng.random((587, )) 53 | img, scale = brain_plot(*values, vmax=1, vmin=0, cmap='viridis', return_scale=True, surf_type='white') 54 | from matplotlib import cm 55 | assert isinstance(scale, cm.ScalarMappable) 56 | assert img.shape in [(1560, 1728, 4), (1560, 1728, 3)] 57 | assert img.dtype == np.float64 58 | assert np.all(img <= 1) 59 | assert np.all(img >= 0) 60 | if importlib.util.find_spec('cv2'): 61 | import cv2 62 | cv2.imwrite(os.path.join(tmp_path, 'test_white.png'), np.round(img * 255).astype(np.uint8)[:, :, [2, 1, 0, 3]]) 63 | -------------------------------------------------------------------------------- /tests/test_unmask_upsample.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from brainplotlib import prepare_data, unmask_and_upsample 3 | 4 | 5 | class TestUnmaskUpsample: 6 | def test_icoorder5_masked(self): 7 | lh = np.arange(9372) 8 | rh = np.arange(9370) + 9372 9 | values = unmask_and_upsample(lh, rh, 'fsaverage', 5, True) 10 | assert np.nanmin(values) == 0 11 | assert np.nanmax(values) == 18741 12 | assert np.any(np.isnan(values)) 13 | 14 | def test_icoorder5_nonmasked(self): 15 | lh = np.arange(10242) 16 | rh = np.arange(10242) + 10242 17 | values = unmask_and_upsample(lh, rh, 'fsaverage', 5, False) 18 | assert values.min() == 0 19 | assert values.max() == 20483 20 | assert np.all(np.isfinite(values)) 21 | 22 | def test_icoorder7_nonmasked(self): 23 | lh = np.arange(163842) 24 | rh = np.arange(163842) + 163842 25 | values = unmask_and_upsample(lh, rh, 'fsaverage', 7, False) 26 | assert values.min() == 0 27 | assert values.max() == 327683 28 | assert np.all(np.isfinite(values)) 29 | 30 | class TestPrepareData: 31 | def test_tuple_input(self): 32 | lh = np.arange(9372) 33 | rh = np.arange(9370) + 9372 34 | values = prepare_data((lh, rh)) 35 | assert values.shape == (327684, ) 36 | 37 | def test_list_input(self): 38 | lh = np.arange(9372) 39 | rh = np.arange(9370) + 9372 40 | values = prepare_data([lh, rh]) 41 | assert values.shape == (327684, ) 42 | 43 | def test_serial_input(self): 44 | lh = np.arange(9372) 45 | rh = np.arange(9370) + 9372 46 | values = prepare_data(lh, rh) 47 | assert values.shape == (327684, ) 48 | 49 | def test_concatenated_input(self): 50 | lh = np.arange(9372) 51 | rh = np.arange(9370) + 9372 52 | values = prepare_data(np.concatenate([lh, rh], axis=0)) 53 | assert values.shape == (327684, ) 54 | --------------------------------------------------------------------------------