diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 186cd8eeb..79f60b067 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,77 +1,153 @@ -# How to Contribute to DeepLabCut +# Contributing to DeepLabCut -DeepLabCut is an actively developed package and we welcome community development and involvement. We are especially seeking people from underrepresented backgrounds in OSS to contribute their expertise and experience. Please get in touch if you want to discuss specific contributions you are interested in developing, and we can help shape a road-map. +Thanks for your interest in contributing to DeepLabCut! We welcome bug fixes, new features, documentation improvements, tests, and general maintenance contributions. -We are happy to receive code extensions, bug fixes, documentation updates, etc. +We especially encourage contributions from people from backgrounds that are underrepresented in open-source software. If you want to discuss an idea before opening a pull request, feel free to start a discussion or open an issue. -If you are a new user, we recommend checking out the detailed [Github Guides](https://guides.github.com). +If you are new to GitHub, the [GitHub Guides](https://guides.github.com/) are a great place to start. -## Setting up a development installation +## Ways to contribute -In order to make changes to `deeplabcut`, you will need to [fork](https://guides.github.com/activities/forking/#fork) the -[repository](https://github.com/deeplabcut/deeplabcut). +You can help by: -If you are not familiar with `git`, we recommend reading up on [this guide](https://guides.github.com/introduction/git-handbook/#basic-git). +- Fixing bugs +- Improving documentation +- Adding tests +- Improving examples +- Refactoring or cleaning up code +- Proposing or implementing new features -Here are guidelines for installing deeplabcut locally on your own computer, where you can make changes to the code! We often update the master deeplabcut code base on github, and then ~1 a month we push out a stable release on pypi. This is what most users turn to on a daily basis (i.e. pypi is where you get your `pip install deeplabcut` code from! +## Development setup -But, sometimes we add things to the repo that are not yet integrated, or you might want to edit the code yourself, or you will need to do this to contribute. Here, we show you how to do this. +To work on DeepLabCut locally: -**Step 1:** +1. [Fork the repository](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo). +2. Clone your fork: -- git clone the repo into a folder on your computer: +```bash +git clone https://github.com//DeepLabCut.git +cd DeepLabCut +``` -- click on this green button and copy the link: +3. Create and activate a Python environment. -![](https://images.squarespace-cdn.com/content/v1/57f6d51c9f74566f55ecf271/1581984907363-G8AFGX4V20Y1XD1PSZAK/ke17ZwdGBToddI8pDm48kGJBV0_F4LE4_UtCip_K_3lZw-zPPgdn4jUwVcJE1ZvWEtT5uBSRWt4vQZAgTJucoTqqXjS3CfNDSuuf31e0tVE0ejQCe16973Pm-pux3j5_Oqt57D2H0YbaJ3tl8vn_eR926scO3xePJoa6uVJa9B4/gitclone.png?format=500w) +We recommend using the project's development dependency group from `pyproject.toml` so you get the tools needed for local development (including formatting, linting, and testing). -- then in the terminal type: `git clone https://github.com/DeepLabCut/DeepLabCut.git` +For example, with `uv`: -**Step 2:** +```bash +uv sync --group dev +``` -- Now you will work from the terminal inside this cloned folder: +With `pip` (e.g. in a `conda` environment): -![](https://images.squarespace-cdn.com/content/v1/57f6d51c9f74566f55ecf271/1581985288123-V8XUAY0C0ZDNJ5WBHB7Y/ke17ZwdGBToddI8pDm48kIsGBOdR9tS_SxF6KQXIcDtZw-zPPgdn4jUwVcJE1ZvWQUxwkmyExglNqGp0IvTJZUJFbgE-7XRK3dMEBRBhUpz3c8X74DzCy4P3pv-ZANOdh-3ZL9iVkcryTbbTskaGvEc42UcRKU-PHxLXKM6ZekE/terminal.png?format=750w) +```bash +pip install -e . --group dev +``` -- Now, when you start `ipython` and `import deeplabcut` you are importing the folder "deeplabcut" - so any changes you make, or any changes we made before adding it to the pip package, are here. +If you use a different environment manager, install the package in editable/development mode together with the `dev` dependency group defined in `pyproject.toml`. -- You can also check which deeplabcut you are importing by running: `deeplabcut.__file__` +## Working on the code -![](https://images.squarespace-cdn.com/content/v1/57f6d51c9f74566f55ecf271/1581985466026-94OCSZJ5TL8U52JLB5VU/ke17ZwdGBToddI8pDm48kNdOD5iqmBzHwUaWGKS6qHBZw-zPPgdn4jUwVcJE1ZvWQUxwkmyExglNqGp0IvTJZUJFbgE-7XRK3dMEBRBhUpyQPoegsR7K4odW9xcCi1MIHmvHh95_BFXYdKinJaRhV61R4G3qaUq94yWmtQgdj1A/importlocal.png?format=750w) +Once your environment is ready, your local checkout is what Python will import. -If you make changes to the code/first use the code, be sure you run `./resinstall.sh`, which you find in the main DeepLabCut folder: +If you want to verify that you are using the local source tree, you can run: -![](https://images.squarespace-cdn.com/content/v1/57f6d51c9f74566f55ecf271/1609353210708-FRNREI7HUNS4GLDSJ00G/ke17ZwdGBToddI8pDm48kAya1IcSd32bok4WHvykeicUqsxRUqqbr1mOJYKfIPR7LoDQ9mXPOjoJoqy81S2I8N_N4V1vUb5AoIIIbLZhVYy7Mythp_T-mtop-vrsUOmeInPi9iDjx9w8K4ZfjXt2dq18t0tDkB2HMfL2JGcLHN27k5rSOPIU8nEAZT0p1MiSCjLISwBs8eEdxAxTptZAUg/Screen+Shot+2020-12-30+at+7.33.16+PM.png?format=2500w) +```bash +python -c "import deeplabcut; print(deeplabcut.__file__)" +``` +Using `ipython` or Jupyter is completely optional—use whatever workflow you prefer. +If you change packaged resources or otherwise need to refresh the local installation, run: -Note, before committing to DeepLabCut, please be sure your code is formatted according to `black`. To learn more, -see [`black`'s documentation](https://black.readthedocs.io/en/stable/). +```bash +./reinstall.sh +``` -Now, please make a [pull request](https://github.com/DeepLabCut/DeepLabCut/pull/new/) that includes both a **summary of and changes to**: +> [!NOTE] +> This script automatically uninstalls the package, builds a new wheel using `setup.py`, and installs that wheel. It is not a simple `pip install -e .` because some resources are copied during installation and need to be refreshed. -- How you modified the code and what new functionality it has. -- DOCSTRING update for your change -- A working example of how it works for users. -- If it's a function that also can be used in downstream steps (i.e. could be plotted) we ask you (1) highlight this, and (2) ideally you provide that functionality as well. If you have any questions, please reach out: admin@deeplabcut.org +## Code style and pre-commit - - +Set it up once in your clone: -**Review & Formatting:** +```bash +pre-commit install +``` -- Please run black on the code to conform to our Black code style (see more at https://pypi.org/project/black/). -- Please assign a reviewer, typically @AlexEMG, @mmathislab, or @jeylau (i/e. the [core-developers](https://github.com/orgs/DeepLabCut/teams/core-developers/members)) +Whenever you commit, `pre-commit` will run the configured checks. -**Code headers** +Please run `pre-commit` before opening a pull request. This helps catch formatting, import ordering, whitespace, YAML, and other common issues early and accelerates code review greatly. -- The code headers can be standardized by running `python tools/update_license_headers.py` -- Edit `NOTICE.yml` to update the header. +## Tests -**DeepLabCut is an open-source tool and has benefited from suggestions and edits by many individuals:** +Pull requests are validated in CI, and contributors are encouraged to run tests locally using: -- the [authors](/AUTHORS) -- [code contributors](https://github.com/DeepLabCut/DeepLabCut/graphs/contributors) +```bash +pytest tests +``` +in the project root before opening a pull request. + +> [!IMPORTANT] +> Heavier tests are also run automatically on GitHub, so this is not a strict requirement, +> but it can help catch issues early and speed up the review process. + +## Pull request guidelines + +When submitting a pull request, please: + +- Clearly describe what changed and why +- Link any related issue(s) +- Update docstrings and documentation when behavior changes +- Add or update tests when appropriate +- Include a small usage example when it helps reviewers understand and/or test the change + +Smaller, focused pull requests are usually much easier to review than very large ones. + +### Draft pull requests + +We use draft pull requests to indicate work in progress. +You may still request reviews and feedbacks on draft pull requests, and we encourage you to do so if you would like early feedback on your work. +Please note that the draft status is in no way related to the perceived quality of the code or its potential for merging, but is simply a way to indicate that the work is not yet ready for final review and merging. +Most pull requests exist for the majority of their lifetime as drafts, which is expected. + +## Documentation + +Documentation improvements are always welcome. + +If your change affects users, please update the relevant docs, examples, or inline docstrings so the behavior is discoverable and easy to understand. + +## Code headers and notices + +If you need to standardize code headers, run: + +```bash +python tools/update_license_headers.py +``` + +Contributors are requested not to update `NOTICE.yml` or `LICENSE` files. + +## Review process + +A maintainer will review your pull request. You do not need to supply a specific release timeline in your PR description—contributions are reviewed and merged as capacity allows. + +If you have questions about where a change should go or how to structure it, opening a draft pull request is completely fine. + +## Need help? + +If you are unsure whether something is in scope, open an issue or draft PR and ask. +We'd much rather help early than have you spend time on the wrong thing. +We also welcome "Feature requests" issues if you would like to discuss implementation details or would like preliminary feedback. + +## Acknowledgments + +DeepLabCut is an open-source project and has benefited from many contributors over time, including: + +- The [authors](/AUTHORS) +- Listed [code contributors](https://github.com/DeepLabCut/DeepLabCut/graphs/contributors) +- And many others over the years. + +We look forward to your contributions! diff --git a/pyproject.toml b/pyproject.toml index 7a82bf35b..78576642b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -110,6 +110,7 @@ Documentation = "https://deeplabcut.github.io/DeepLabCut/README.html" [dependency-groups] dev = [ "coverage", + "pre-commit", "pydantic>=2,<3", "pytest", "pytest-cov", diff --git a/tools/README.md b/tools/README.md index de24cbd86..bb4c1803a 100644 --- a/tools/README.md +++ b/tools/README.md @@ -2,19 +2,9 @@ This document summarizes the developer tooling and workflows used in this repo. -> **Quick start (recommended)** -> -> ```bash -> python -m pip install -U pip -> pip install -U pytest coverage pre-commit -> ``` -> -> If you prefer faster dependency installs, you can use `uv` (optional): -> -> ```bash -> # Install uv (see https://docs.astral.sh/uv/ for platform-specific installers) -> uv --version -> ``` +```bash +pip install -e . --group dev +``` --- @@ -28,19 +18,13 @@ pre-commit install Run on all files: -```bash -pre-commit run --all-files -``` - ---- +Steering committee members may edit the `NOTICE.yml` to update the header. ## 2) License headers Code headers can be standardized by running: -```bash -python tools/update_license_headers.py -``` +Please follow the instructions in `CONTRIBUTING.md` for contributing to the codebase, including running tests and pre-commit checks before opening a pull request. Run from the repository root. Update `NOTICE.yml` to change header content. @@ -56,13 +40,6 @@ pytest ### Run a specific test module or folder -```bash -pytest tests/test_auxiliaryfunctions.py -pytest tests/core/ -``` - -### Coverage - ```bash coverage run -m pytest coverage report diff --git a/uv.lock b/uv.lock index dd08a6906..8de940472 100644 --- a/uv.lock +++ b/uv.lock @@ -428,6 +428,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, ] +[[package]] +name = "cfgv" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, +] + [[package]] name = "charset-normalizer" version = "3.4.6" @@ -1137,6 +1146,7 @@ wandb = [ [package.dev-dependencies] dev = [ { name = "coverage" }, + { name = "pre-commit" }, { name = "pydantic" }, { name = "pytest" }, { name = "pytest-cov" }, @@ -1203,11 +1213,21 @@ provides-extras = ["gui", "openvino", "docs", "fmpose3d", "tf", "apple-mchips", [package.metadata.requires-dev] dev = [ { name = "coverage" }, + { name = "pre-commit" }, { name = "pydantic", specifier = ">=2,<3" }, { name = "pytest" }, { name = "pytest-cov" }, ] +[[package]] +name = "distlib" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, +] + [[package]] name = "dlclibrary" version = "0.0.11" @@ -1823,6 +1843,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6f/75/ca21955d6117a394a482c7862ce96216239d0e3a53133ae8510727a8bcfa/huggingface_hub-1.7.1-py3-none-any.whl", hash = "sha256:38c6cce7419bbde8caac26a45ed22b0cea24152a8961565d70ec21f88752bfaa", size = 616308, upload-time = "2026-03-13T09:36:06.062Z" }, ] +[[package]] +name = "identify" +version = "2.6.18" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/46/c4/7fb4db12296cdb11893d61c92048fe617ee853f8523b9b296ac03b43757e/identify-2.6.18.tar.gz", hash = "sha256:873ac56a5e3fd63e7438a7ecbc4d91aca692eb3fefa4534db2b7913f3fc352fd", size = 99580, upload-time = "2026-03-15T18:39:50.319Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl", hash = "sha256:8db9d3c8ea9079db92cafb0ebf97abdc09d52e97f4dcf773a2e694048b7cd737", size = 99394, upload-time = "2026-03-15T18:39:48.915Z" }, +] + [[package]] name = "idna" version = "3.11" @@ -3179,6 +3208,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504, upload-time = "2025-12-08T17:02:38.159Z" }, ] +[[package]] +name = "nodeenv" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, +] + [[package]] name = "npe2" version = "0.8.1" @@ -3936,6 +3974,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl", hash = "sha256:f265597baa9f760d25ceb29d0beb8186c243d6607b0f60b83ecf14078dbc703b", size = 67175, upload-time = "2026-01-30T19:15:08.36Z" }, ] +[[package]] +name = "pre-commit" +version = "4.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/40/f1/6d86a29246dfd2e9b6237f0b5823717f60cad94d47ddc26afa916d21f525/pre_commit-4.5.1.tar.gz", hash = "sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61", size = 198232, upload-time = "2025-12-16T21:14:33.552Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77", size = 226437, upload-time = "2025-12-16T21:14:32.409Z" }, +] + [[package]] name = "prompt-toolkit" version = "3.0.52" @@ -7191,6 +7245,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, ] +[[package]] +name = "virtualenv" +version = "20.35.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/20/28/e6f1a6f655d620846bd9df527390ecc26b3805a0c5989048c210e22c5ca9/virtualenv-20.35.4.tar.gz", hash = "sha256:643d3914d73d3eeb0c552cbb12d7e82adf0e504dbf86a3182f8771a153a1971c", size = 6028799, upload-time = "2025-10-29T06:57:40.511Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/0c/c05523fa3181fdf0c9c52a6ba91a23fbf3246cc095f26f6516f9c60e6771/virtualenv-20.35.4-py3-none-any.whl", hash = "sha256:c21c9cede36c9753eeade68ba7d523529f228a403463376cf821eaae2b650f1b", size = 6005095, upload-time = "2025-10-29T06:57:37.598Z" }, +] + [[package]] name = "vispy" version = "0.15.2"