Contributing

Goals

We’re building a Python toolkit to support neuroscience. This effort is complementary to others in this space, such as nilearn, nipy, and similar. We are primarily working on new research efforts in processing and understanding functional Magnetic Resonance Imaging (fMRI) data sets.

We provide high performance algorithms, typically implemented in C/C++, along with convenient Python wrapper modules that make these advanced tools available for easy use. These are some of the design goals that contributors should keep in mind:

  • We do not intend to duplicate existing functionality, either in the C++ side, or the Python side. For example, we do not provide any tools for parsing Nifti files, even though BrainIAK heavily depends on them. Nibabel already has perfectly good tools for this.

  • We try to make the C++ libraries usable outside of Python as well, so that they could be used directly in your C++ project, or accessed from other languages. However this is a secondary goal, our primary goal is to produce a solid, highly usable, very high performance toolkit for Python.

  • Every algorithm should be capable of running on a single machine, and if there is an appropriate distributed algorithm, it should also be capable of running at cluster scale. It is understood that the single-machine version of an algorithm will need to work with smaller datasets than the cluster version.

How to contribute

We use GitHub pull requests (PRs) to make improvements to the repository. You should make a fork, create a new branch for each new feature you develop, and make a PR to merge your branch into the master branch of the official repository. There are several workflows you could follow. Here is a concise step-by-step description of our recommended workflow:

  1. Fork the official BrainIAK repository on GitHub.

  2. Clone your fork:

    git clone https://github.com/yourgithubusername/brainiak
    
  3. Add the official BrainIAK repository as the upstream remote:

    git remote add upstream https://github.com/brainiak/brainiak
    
  4. Set the master branch to track the upstream remote:

    git fetch upstream
    git branch -u upstream/master
    
  5. Whenever there are commits in the official repository, pull them to keep your master branch up to date:

    git pull --ff-only
    
  6. Always create a new branch when you start working on a new feature; we only update the master branch via pull requests from feature branches; never commit directly to the master branch:

    git checkout -b new-feature
    
  7. Make changes and commit them. Include a news fragment for the release notes in docs/newsfragments if your changes are visible to users (see Pip’s documentation and our news types in pyproject.toml).

  8. Push your feature branch to your fork:

    git push --set-upstream origin new-feature  # only for the first push
    git push  # for all subsequent pushes
    
  9. When your feature is ready, make a PR on GitHub. If you collaborate with others on the code, credit them using Co-authored-by; if you are merging a PR, credit all authors using Co-authored-by in the PR squash message. After your PR is merged, update your master branch and delete your feature branch:

    git checkout master
    git pull --ff-only
    git branch -d new-feature
    git push --delete origin new-feature  # or use delete button in GitHub PR
    

Please see the GitHub help for collaborating on projects using issues and pull requests for more information.

All pull requests are automatically tested using the pr-check.sh script. You should test you contributions yourself on your computer using pr-check.sh before creating a PR. The script performs several checks in a Python virtual environment, which is isolated from your normal Python environment for reproducibility.

During development, you may wish to run some of the individual checks in pr-check.sh repeatedly until you get everything right, without waiting for the virtual environment to be set up every time. You can run the individual checks from pr-check.sh using the steps bellow:

# do not run this if using Anaconda, because Anaconda is not compatible with
# venv; instead, look at pr-check.sh to see how to run the individual
# checks that are part of pr-check.sh using Anaconda

# optional, but highly recommended: create a virtual environment
python3 -m venv ../brainiak_pr_venv
source ../brainiak_pr_venv/bin/activate

# install brainiak in editable mode and developer dependencies
python3 -m pip install -U -r requirements-dev.txt

# static analysis
./run-checks.sh

# run tests
./run-tests.sh

# build documentation
cd docs
make
cd -

# optional: remove the virtual environment, if you created one
deactivate
rm -r ../brainiak_pr_venv

When you are ready to submit your PR, run pr-check.sh even if you were using the steps above to run the individual checks in pr-check.sh during development.

If you want to obtain early feedback for your work, ask people to look at your fork. Alternatively, you can open a PR before your work is ready; in this case, you should start the PR title with WIP:, to let people know your PR is work in progress.

Tools

We primarily use PyCharm (or equivalently, IDEA with Python plugin). You’re free to use whatever you like to develop, but bear in mind that if you use the same tools as the rest of the group, more people will be able to help if something goes wrong.

The development requirements are listed in requirements-dev.txt. You can install them with:

python3 -m pip install -U -r requirements-dev.txt

Standards

  • All code exposed through public APIs must have documentation that explains what the code does, what its parameters mean, and what its return values can be, at a minimum.

  • All code must have repeatable automated unit tests, and most code should have integration tests as well.

  • Where possible, transformations and classifiers should be made compatible with Scikit-learn Pipelines by implementing fit, transform and fit_transform methods as described in the Scikit-learn pipeline documentation.

  • Use logging to record debug messages with a logger obtained using:

    logging.getLogger(__name__)
    

    Use warnings to show warning messages to users. Do not use print. See the Python Logging Tutorial for details.

  • Create usage examples for new modules in the examples directory. Add a requirements.txt file to help users install the packages your examples require. If your example requires software that is not available in PyPI, document it in a README.rst and also update the BrainIAK Dockerfile.

  • Remove the output of example Jupyter notebooks before committing them, using nbstripout.

Testing

Unit tests are small tests that execute very quickly, seconds or less. They are the first line of defense against software errors, and you must include some whenever you add code to BrainIAK. We use a tool called “pytest” to run tests; please read the Pytest documentation. You should put your tests in a test_*.py file in the test folder, following the structure of the brainiak folder. So for example, if you have your code in brainiak/funcalign/srm.py you should have tests in tests/funcalign/test_srm.py. The unit tests for a subpackage should not take more than one minute in total on our testing service, Travis CI.

You must install the package in editable mode before running the tests:

python3 -m pip install -e .

You can run ./run-tests.sh to run all the unit tests, or you can use the py.test <your-test-file.py> command to run your tests only, at a more granular level.

Next to the test results, you will also see a code coverage report. New code should have at least 90% coverage.

Note that you can only obtain test coverage data when the package is installed in editable mode or the test command is called from the test directory. If the package is installed normally and the test command is called from the project root directory, the coverage program will fail to report the coverage of the installed code, because it will look for the code in the current directory, which is not executed.

Folder layout

Since BrainIAK is primarily published as a Python package, it is largely organized according to the Python Packaging User Guide.

Python code goes in the brainiak package, usually with a subpackage for each major research initiative. If an algorithm can be implemented in a single module, place the module directly in the brainiak package, do not create a subpackage.

Name subpackages and modules using short names describing their functionality, e.g., tda for the subpackage containing topological data analysis work and htfa.py for the module implementing hierarchical topographical factor analysis.

Making a release

This information is only of interest to the core contributors who have the right to make releases.

Before making a release, ensure that:

  1. The following environment variables are set:

    export $CONDA_HOME=/path/to/miniconda3
    
  2. The following Conda channels are enabled:

    conda config --add channels conda-forge --add channels defaults
    
  3. The following Conda packages are installed:

    conda install conda-build anaconda-client
    

To make a release:

  1. Choose a release number, v. We follow Semantic Versioning, although we omit the patch number when it is 0:

    export v=0.11
    
  2. Prepare the release notes; may require manual additions for PRs without release notes in docs/newsfragments:

    git checkout -b release-v$v
    towncrier --version $v
    ./pr-check.sh
    git commit -a -m "Add release notes for v$v"
    git push --set-upstream origin release-v$v
    <Create a PR; merge the PR.>
    git checkout master
    git pull --ff-only
    git branch -d release-v$v
    
  3. Tag the release:

    git tag v$v
    
  4. Create and test the source distribution package:

    # build
    python3 setup.py sdist
    # test
    tar -xf dist/brainiak-$v.tar.gz
    cd brainiak-$v
    ./pr-check.sh
    cd -
    
  5. Create and test Conda packages (repeat command for all OSes and Python versions); requires the conda-build Conda package; make sure you create the tag on all the machines:

    # build and test in a single script
    # On Linux machine
    .conda/bin/build 3.7
    .conda/bin/build 3.8
    # On macOS machine
    git tag v$v
    .conda/bin/build 3.7
    .conda/bin/build 3.8
    
  6. Build the Docker image (requires brainiak-tutorials checkout):

    # build
    # clone if needed
    git clone git@github.com:brainiak/brainiak-tutorials.git tutorials
    cd tutorials && git pull --ff-only && cd -
    docker build --no-cache -t brainiak/brainiak .
    docker tag v$v-$(date +%Y%m%d) brainiak/brainiak
    # test
    # run pr-check.sh in docker
    # cleanup
    rm -r brainiak-$v
    
  7. Build the documentation:

    cd docs
    make
    cd -
    
  8. Push tag:

    git push upstream v$v
    
  9. Upload sdist:

    twine upload dist/brainiak-$v.tar.gz
    
  10. Upload Conda package (repeat command for all OSes and Python versions); requires the anaconda-client Conda package:

    anaconda upload -u brainiak \
    $CONDA_HOME/conda-bld/<OS>/brainiak-$v-<python_version>.tar.bz2
    
  11. Push Docker image:

    docker push brainiak/brainiak
    
  12. Publish the documentation:

    cd ../brainiak.github.io
    git checkout -b release-v$v
    rm -r docs
    cp -ir ../brainiak/docs/_build/html docs
    git commit -a -m "Update docs to v$v"
    # use your fork
    git push --set-upstream origin release-v$v
    <Create a PR; merge the PR.>
    git checkout master
    git pull --ff-only
    git branch -d release-v$v
    cd -