.. _contributing: Contributing ============= ``zea`` is an open-source project and we welcome contributions from the ultrasound community. Whether you want to report a bug, suggest a new feature, or contribute code, your input is valuable. This document outlines how you can contribute to the project. In case you have a general question or want to discuss an idea before working on it, please feel free to open an `issue on GitHub `_ or join the discussion in the `GitHub Discussions `_. How to contribute ----------------- First, take some time to read through this whole guide. Then follow the following steps to contribute: 1. Make an issue ~~~~~~~~~~~~~~~~ Take a look at the current `issues on GitHub `_ to see if there are any open issues that you can help with. If your issue is not listed, please create a new issue describing the motivation behind the problem or feature you want to work on. That way we can discuss it and make sure that your contribution is aligned with the project's goals. .. dropdown:: Example Issue Template .. code-block:: markdown **Title:** [Bug/Feature/Question]: Brief summary **Description:** - For problems or bugs - What is the problem? - Steps to reproduce - Expected behavior - Actual behavior - For feature requests - What is the feature and why is it needed? - Possible interface or implementation details - References (if applicable) - Link to relevant documentation, discussions, or related issues. 2. Fork the repository ~~~~~~~~~~~~~~~~~~~~~~ Most contributors will not have write access to the main repository. Instead, you will need to fork the repository to your own GitHub account. You can fork the repository on GitHub by clicking the "Fork" button at the top right of the page. This will create a copy of the repository under your GitHub account. One can also use: .. code-block:: shell gh repo fork tue-bmd/zea --clone 3. Setup environment ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Check out a new branch for your changes. .. code-block:: shell cd zea git checkout -b We recommend using Docker for development as it ensures consistency and avoids dependency issues — the images ship with the backends pre-installed. If you prefer a local environment, use `uv `_ or ``pip``. .. tab-set:: .. tab-item:: Docker See the :ref:`Docker ` section of the installation guide for build and run instructions. Images built with ``DEV=true`` (the default) already include the ``dev`` dependency-group (tests, docs and lint tools). Every shell prints a message of the day ending in the ``uv sync`` command for that image. Use that one rather than copying from here: it names the backend groups the image was built with, and ``uv sync`` removes any group you leave out. Run it whenever ``uv.lock`` changes, after a rebase or a backend bump, to update a running container without rebuilding it. On a ``DEV=false`` image the command also has ``--no-default-groups``. Drop that flag to add the ``dev`` group. For ``zeahub/all`` it becomes: .. code-block:: shell uv sync --group jax-gpu --group torch-gpu --group tf-gpu pre-commit install .. tab-item:: uv From the repository root run: .. code-block:: shell uv sync uv run pre-commit install This creates a ``.venv`` with the exact locked dependencies and installs ``zea`` itself in **editable** mode, so your changes to the source take effect immediately without reinstalling. This will also install the ``dev`` dependency-group, i.e. the ``tests``, ``docs`` and ``lint`` groups. Prefix commands with ``uv run`` (e.g. ``uv run pytest``) or activate the environment with ``source .venv/bin/activate``. Backends are groups too, so add the ones you want — ``cpu`` or ``gpu``, and a single backend is enough to run the test suite: .. code-block:: shell uv sync --group jax-cpu # or jax-gpu, torch-cpu, tf-gpu, ... .. tab-item:: pip Install into any existing environment (``venv``, ``conda``, ...) with plain ``pip`` (requires pip >= 25.1 for ``--group`` support). The ``-e`` flag makes the install editable: .. code-block:: shell pip install -e . --group dev pre-commit install The Docker images already come with a machine learning backend. For the uv and ``pip`` routes you need one of JAX, PyTorch or TensorFlow: with uv use the backend groups shown above, with ``pip`` install it yourself — see the :ref:`backend installation ` guide. .. _running-tests: 4. Make your changes ~~~~~~~~~~~~~~~~~~~~ In general, we recommend a test-driven development approach: 1. Write a test for the functionality you want to add. 2. Write the code to make the test pass. Tests are divided into three groups: .. list-table:: :widths: 15 85 :header-rows: 1 * - Group - Description * - **light** - Fast unit tests. Run these on every change. * - **heavy** - Resource-intensive tests, requires a GPU. * - **notebook** - Runs notebooks end-to-end, requires a GPU. **Run the light tests** (what CI runs on every push): .. code-block:: shell pytest -m 'not heavy and not notebook' When working locally you may not have all ML backends installed. Skip tests that require a missing backend with: .. code-block:: shell pytest -m 'not heavy and not notebook' --skip-unavailable-backends .. dropdown:: Choosing the backend and device Tests run on TensorFlow by default. They use one free GPU when one is available and fall back to CPU otherwise. ``KERAS_BACKEND`` is ignored; use these environment variables instead: .. code-block:: shell # Backend: tensorflow, jax or torch ZEA_TEST_BACKEND=jax pytest -m 'not heavy and not notebook' # Device: auto:1 (default), cpu, gpu:0, auto:-1 (all GPUs), ... ZEA_TEST_DEVICE=cpu pytest -m 'not heavy and not notebook' # or a specific test file or function, e.g.: ZEA_TEST_BACKEND=jax ZEA_TEST_DEVICE=gpu:0 pytest tests/test_io_lib.py::test_load_video_gif Parts of tests that only apply to other backends are skipped and counted in the summary. Cross-backend comparisons always run every installed backend on CPU, whichever backend you pick. .. dropdown:: Heavy tests Heavy tests require significant compute and are best run on a machine with a GPU. They are skipped in the light test run automatically. .. code-block:: shell pytest -m 'heavy' .. dropdown:: Notebook tests Notebook tests execute every ``.ipynb`` under ``docs/source/notebooks/`` end-to-end using `papermill `_. They are split into groups matching the CI matrix: .. code-block:: shell # All notebooks pytest -s -m 'notebook' # One subfolder only (agent / models / pipeline / data / metrics) pytest -s -m 'notebook' --notebook-dir agent pytest -s -m 'notebook' --notebook-dir data --notebook-dir metrics # A single notebook by name pytest -s -m 'notebook' --notebook dbua_example.ipynb .. dropdown:: Running a single test or test file Pass any pytest node-id to run just one test: .. code-block:: shell # Single test function pytest tests/test_tensor_ops.py::test_flatten # All tests in a file pytest tests/test_tensor_ops.py # Match by keyword pytest -k "beamform" A few things to keep in mind when making changes: - Make sure to write backend-agnostic code. This means that your code should work with all supported backends. This can be achieved by using the ``keras.ops`` API (see the `Keras ops documentation `_), instead of using backend-specific functions. For example, use ``keras.ops.squeeze`` instead of ``jax.numpy.squeeze`` or ``torch.squeeze``. Also, when converting tensors to numpy arrays, use the ``keras.ops.convert_to_numpy`` function (instead of ``my_tensor.numpy()``) to ensure compatibility with all backends. - The code is autoformatted using `ruff `_ and type-checked with `ty `_. You can run the pre-commit hooks to automatically format, lint, and type-check your code using: .. code-block:: shell pre-commit run --all-files 5. Document your changes ~~~~~~~~~~~~~~~~~~~~~~~~ The documentation uses `Sphinx `_ and generally is written in reStructuredText format. You can find the documentation files in the `docs/source` directory. Docstrings are written in Google style, which you can see examples of in the `example_google_docstrings.py `_ file. If you add new functionality, please make sure to document it in the documentation files. An example of a well structured docstring below: .. dropdown:: Example docstring .. code-block:: python def example_function(param1: int, param2: str) -> bool: """This is an example function that demonstrates a well-structured docstring. Note the spacing and indentation between the sections, which is important for the automatic docs generation. Also it is recommended to add an example section with doctests, which will automatically test the code snippets in the docs generation. Args: param1 (int): Description of the first parameter. param2 (str): Description of the second parameter. Returns: bool: Description of the return value. Raises: ValueError: If param1 is negative. Example: Use ``.. doctest::`` with ``>>>`` prompts when you want to show interactive input/output inline: .. doctest:: >>> example_function(5, "test") True Alternatively use ``.. testcode::`` for blocks that don't fit the interactive format: .. testcode:: print(example_function(5, "test")) .. testoutput:: True """ if param1 < 0: raise ValueError("param1 must be non-negative") return True .. _citing-papers: **Citing papers.** If your contribution implements a method, model, algorithm or dataset from a paper, attribute the authors with the ``.. citation::`` directive instead of writing the reference out by hand: 1. Add the paper to `docs/source/references.bib `_, using a ```` key (``paper/paper.bib`` is only for ``zea``'s own citation and the papers-using-``zea`` list). 2. Cite it from the docstring: .. code-block:: rst .. citation:: luijten2020adaptive Pass several keys at once with ``.. citation:: key1, key2``, and add a note -- a link to the reference implementation, say -- as an indented block below. 3. Build the docs; an unknown key is reported as a warning. Keep any "this is a ``zea`` implementation" remark in its own ``.. important::`` block, so the citation block holds nothing but the reference. The overall structure of the documentation is manually designed, but the API documentation is auto-generated based on the docstrings in the code. To generate the docs locally you can run: .. code-block:: shell # if you didn't install the dependencies earlier pip install -e . --group docs cd docs make docs-clean && make docs-build # you can also serve the docs locally make docs-clean && make docs-serve # and open them in your browser at http://localhost:8000 See the `README.md `_ for more information. .. _changelog-entry: 6. Log your changes ~~~~~~~~~~~~~~~~~~~ Add a line about your change to the :doc:`changelog` in `docs/source/changelog.rst `_. Put it under the **Current** section, in the subsection that fits: **New Features**, **Changed** or **Fixed**. The release notes are generated from these entries, so write them for users: what changed and how it affects them, in one or two sentences. - Reference the API with roles, such as ``:class:`zea.ops.Normalize``` or ``:func:`zea.func.vmap```, so the entry links to its documentation. - End with the pull request number and your GitHub handle: ``(:gh:`681` by :user:`your-handle`)``. You can add the number once the pull request is open. - Start breaking changes with "(Breaking)". Deprecations go under **Changed**, starting with "Deprecate". .. code-block:: rst Fixed ^^^^^ - :class:`zea.ops.Normalize` clips its output instead of its input, so results stay within ``output_range`` (:gh:`681` by :user:`tristan-deep`) A check on each pull request looks for a changelog entry that ends with the pull request number and your handle. Changes that users will not notice, such as CI, tooling or refactors, need none: a maintainer labels the pull request ``skip changelog`` (``dev`` and ``documentation`` work as well). 7. Make a pull request ~~~~~~~~~~~~~~~~~~~~~~ Once your changes are ready and all tests pass, push your branch to your forked repository: .. code-block:: shell git add . git commit -m "Description of your changes" git push origin Then, go to the original repository on GitHub and open a Pull Request (PR) from your branch. The description starts from a `template `_: explain what you changed and why as short bullets, reference any related issues, and go through the checklist. The maintainers will review your PR and may request changes or ask questions. Please respond to feedback and update your PR as needed. Once everything looks good, your changes will be merged! .. note:: Anyone can review pull requests, we encourage others to review each other's work, however, only the maintainers can approve a pull request. Pull Requests require at least one approval and all tests passing before being able to merge it. Thank you for contributing to zea! Contributing topics ------------------- Beyond the general contribution workflow above, there are a few specific areas where we especially welcome contributions. Browse the `open issues `_ to see what is being worked on, or open a new one to propose something new. Adding an example notebook ~~~~~~~~~~~~~~~~~~~~~~~~~~ New example notebooks are always welcome! Each one shows up on the :doc:`examples` page as a card with a thumbnail and tags. 1. **Add the notebook** to one of the groups in `docs/source/notebooks `_. Copy the structure of an existing notebook: a title, the Colab and GitHub badges, ``%pip install zea`` as the first cell, and all cells run in order. 2. **Add tags and a level** to the notebook metadata (*Edit → Edit Notebook Metadata* in Jupyter): .. code-block:: json "zea": {"tags": ["beamforming", "cardiac"], "level": "beginner"} Use lowercase tags, and reuse the ones already on the examples page where possible. The level is ``beginner``, ``intermediate`` or ``advanced``. 3. **Add a thumbnail** at ``docs/_static/thumbnails/.webp``: the image only, without axes, titles or colorbars. Make it from the image array in a scratch cell (don't commit it): .. code-block:: python import sys sys.path.insert(0, "../..") # docs/source from make_thumbnail import make_thumbnail make_thumbnail(image, "my_example", mode="fill") # rectangular image, cut to 4:3 make_thumbnail(image, "my_example", mode="cone") # sector scan, transparent background For ``mode="cone"``, scan convert with ``fill_value=np.nan`` so everything outside the cone becomes transparent. 4. **Check it**: ``pre-commit run --files ``, then ``make docs-serve`` in ``docs/`` to see the card. To link to an example outside ``zea`` (a blog post or another library's docs), add an ``external-example`` to ``docs/source/examples.rst`` with a thumbnail in the same folder: .. code-block:: rst .. external-example:: Title of the example :link: https://example.org/the-example :thumbnail: my_external_example.webp :source: Name of the library or blog :tags: beamforming, inverse problems :level: intermediate Adding to ``zea.models`` ~~~~~~~~~~~~~~~~~~~~~~~~ Please see the :ref:`adding-models` section for more information on how to add new models to ``zea``. Adding to ``zea.ops`` ~~~~~~~~~~~~~~~~~~~~~ Please see the :ref:`adding-ops` section for more information on how to add new ops to ``zea``.