diff --git a/docs/source/conf.py b/docs/source/conf.py index 9fae80c..47cd594 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -60,11 +60,21 @@ 'sphinx.ext.githubpages', 'sphinx.ext.autosummary', 'autoapi.extension', + 'myst_parser', 'numpydoc', 'sphinx_gallery.gen_gallery', 'sphinx_design', ] +myst_enable_extensions = [ + "colon_fence", + "deflist", + "fieldlist", + "html_image", + "tasklist", +] +myst_heading_anchors = 3 + # Suppress known deprecation warnings from dependencies # astroid 4.x deprecation - will be fixed when sphinx-autoapi updates for astroid 5.x import warnings diff --git a/docs/source/contributing.md b/docs/source/contributing.md new file mode 100644 index 0000000..655650c --- /dev/null +++ b/docs/source/contributing.md @@ -0,0 +1,194 @@ +# Contributing to TRX-Python + +We welcome contributions from the community! This guide will help you get started +with contributing to the TRX-Python project. + +## Ways to Contribute + +There are many ways to contribute to TRX-Python: + +- **Report bugs**: If you find a bug, please open an issue on GitHub +- **Suggest features**: Have an idea? Open an issue to discuss it +- **Fix bugs**: Look for issues labeled "good first issue" or "help wanted" +- **Write documentation**: Help improve our docs or add examples +- **Write tests**: Increase test coverage +- **Code review**: Review pull requests from other contributors + +## Getting Started + +1. **Fork the repository** on GitHub +2. **Clone your fork**: + + ```bash + git clone https://github.com/YOUR_USERNAME/trx-python.git + cd trx-python + ``` + +3. **Set up development environment**: + + ```bash + pip install -e ".[all]" + spin setup + ``` + + The `spin setup` command fetches version tags from upstream, which is + required for correct version detection. + +4. **Create a branch** for your changes: + + ```bash + git checkout -b my-feature-branch + ``` + +## Making Changes + +### Development Workflow + +We use [spin](https://github.com/scientific-python/spin) for development workflow: + +```bash +spin install # Install in editable mode +spin test # Run all tests +spin lint # Run linting (ruff) +spin docs # Build documentation +``` + +### Before Submitting + +1. **Run tests** to ensure your changes don't break existing functionality: + + ```bash + spin test + ``` + +2. **Run linting** to ensure code style compliance: + + ```bash + spin lint + ``` + + You can auto-fix many issues with: + + ```bash + spin lint --fix + ``` + +3. **Format your code** using ruff: + + ```bash + ruff format . + ``` + +4. **Write tests** for any new functionality + +5. **Update documentation** if needed + +## Submitting a Pull Request + +1. **Push your changes** to your fork: + + ```bash + git push origin my-feature-branch + ``` + +2. **Open a Pull Request** on GitHub against the `master` branch + +3. **Describe your changes** in the PR description: + + - What does this PR do? + - Why is this change needed? + - How was it tested? + +4. **Wait for CI checks** to pass + +5. **Address review feedback** if requested + +## Code Style + +We follow these conventions: + +- **PEP 8** style guide +- **Line length**: 88 characters maximum +- **Docstrings**: NumPy style format +- **Type hints**: Encouraged but not required + +Example docstring: + +```python +def my_function(param1, param2): + """Short description of the function. + + Parameters + ---------- + param1 : int + Description of param1. + param2 : str + Description of param2. + + Returns + ------- + result : bool + Description of return value. + + Examples + -------- + >>> my_function(1, "test") + True + """ + pass +``` + +We use [ruff](https://docs.astral.sh/ruff/) for linting and formatting. +Configuration is in `ruff.toml`. + +## Testing + +Tests are located in `trx/tests/`. We use pytest for testing. + +### Running Tests + +```bash +# Run all tests +spin test + +# Run tests matching a pattern +spin test -m memmap + +# Run with verbose output +spin test -v + +# Run a specific test file +pytest trx/tests/test_memmap.py +``` + +### Writing Tests + +- Place tests in `trx/tests/` +- Name test files `test_*.py` +- Name test functions `test_*` +- Use pytest fixtures for common setup + +## Documentation + +Documentation is built with Sphinx and hosted on GitHub Pages. + +### Building Docs + +```bash +spin docs # Build documentation +spin docs --clean # Clean build +spin docs --open # Build and open in browser +``` + +### Writing Documentation + +- Documentation source is in `docs/source/` +- Use MyST Markdown format +- API documentation is auto-generated from docstrings + +## Getting Help + +- **GitHub Issues**: For bugs and feature requests +- **GitHub Discussions**: For questions and discussions + +Thank you for contributing to TRX-Python! diff --git a/docs/source/contributing.rst b/docs/source/contributing.rst deleted file mode 100644 index 0ab192a..0000000 --- a/docs/source/contributing.rst +++ /dev/null @@ -1,209 +0,0 @@ -Contributing to TRX-Python -========================== - -We welcome contributions from the community! This guide will help you get started -with contributing to the TRX-Python project. - -Ways to Contribute ------------------- - -There are many ways to contribute to TRX-Python: - -- **Report bugs**: If you find a bug, please open an issue on GitHub -- **Suggest features**: Have an idea? Open an issue to discuss it -- **Fix bugs**: Look for issues labeled "good first issue" or "help wanted" -- **Write documentation**: Help improve our docs or add examples -- **Write tests**: Increase test coverage -- **Code review**: Review pull requests from other contributors - -Getting Started ---------------- - -1. **Fork the repository** on GitHub -2. **Clone your fork**: - - .. code-block:: bash - - git clone https://github.com/YOUR_USERNAME/trx-python.git - cd trx-python - -3. **Set up development environment**: - - .. code-block:: bash - - pip install -e ".[all]" - spin setup - - The ``spin setup`` command fetches version tags from upstream, which is - required for correct version detection. - -4. **Create a branch** for your changes: - - .. code-block:: bash - - git checkout -b my-feature-branch - -Making Changes --------------- - -Development Workflow -~~~~~~~~~~~~~~~~~~~~ - -We use `spin `_ for development workflow: - -.. code-block:: bash - - spin install # Install in editable mode - spin test # Run all tests - spin lint # Run linting (ruff) - spin docs # Build documentation - -Before Submitting -~~~~~~~~~~~~~~~~~ - -1. **Run tests** to ensure your changes don't break existing functionality: - - .. code-block:: bash - - spin test - -2. **Run linting** to ensure code style compliance: - - .. code-block:: bash - - spin lint - - You can auto-fix many issues with: - - .. code-block:: bash - - spin lint --fix - -3. **Format your code** using ruff: - - .. code-block:: bash - - ruff format . - -4. **Write tests** for any new functionality - -5. **Update documentation** if needed - -Submitting a Pull Request -------------------------- - -1. **Push your changes** to your fork: - - .. code-block:: bash - - git push origin my-feature-branch - -2. **Open a Pull Request** on GitHub against the ``master`` branch - -3. **Describe your changes** in the PR description: - - - What does this PR do? - - Why is this change needed? - - How was it tested? - -4. **Wait for CI checks** to pass - -5. **Address review feedback** if requested - -Code Style ----------- - -We follow these conventions: - -- **PEP 8** style guide -- **Line length**: 88 characters maximum -- **Docstrings**: NumPy style format -- **Type hints**: Encouraged but not required - -Example docstring: - -.. code-block:: python - - def my_function(param1, param2): - """Short description of the function. - - Parameters - ---------- - param1 : int - Description of param1. - param2 : str - Description of param2. - - Returns - ------- - result : bool - Description of return value. - - Examples - -------- - >>> my_function(1, "test") - True - """ - pass - -We use `ruff `_ for linting and formatting. -Configuration is in ``ruff.toml``. - -Testing -------- - -Tests are located in ``trx/tests/``. We use pytest for testing. - -Running Tests -~~~~~~~~~~~~~ - -.. code-block:: bash - - # Run all tests - spin test - - # Run tests matching a pattern - spin test -m memmap - - # Run with verbose output - spin test -v - - # Run a specific test file - pytest trx/tests/test_memmap.py - -Writing Tests -~~~~~~~~~~~~~ - -- Place tests in ``trx/tests/`` -- Name test files ``test_*.py`` -- Name test functions ``test_*`` -- Use pytest fixtures for common setup - -Documentation -------------- - -Documentation is built with Sphinx and hosted on GitHub Pages. - -Building Docs -~~~~~~~~~~~~~ - -.. code-block:: bash - - spin docs # Build documentation - spin docs --clean # Clean build - spin docs --open # Build and open in browser - -Writing Documentation -~~~~~~~~~~~~~~~~~~~~~ - -- Documentation source is in ``docs/source/`` -- Use reStructuredText format -- API documentation is auto-generated from docstrings - -Getting Help ------------- - -- **GitHub Issues**: For bugs and feature requests -- **GitHub Discussions**: For questions and discussions - -Thank you for contributing to TRX-Python! diff --git a/docs/source/dev.md b/docs/source/dev.md new file mode 100644 index 0000000..fe392f1 --- /dev/null +++ b/docs/source/dev.md @@ -0,0 +1,324 @@ +# Developer Guide + +This guide provides detailed information for developers working on TRX-Python. + +```{toctree} +:maxdepth: 1 +contributing +``` + +## Installation for Development + +### Prerequisites + +- Python 3.11 or later (Python 3.12+ recommended) +- Git +- pip + +### Setting Up Your Environment + +1. **Clone the repository**: + + ```bash + # If you're a contributor, fork first then clone your fork + git clone https://github.com/YOUR_USERNAME/trx-python.git + cd trx-python + ``` + +2. **Install with all development dependencies**: + + ```bash + pip install -e ".[all]" + ``` + + This installs: + + - Core dependencies (numpy, nibabel, deepdiff, typer) + - Development tools (spin, setuptools_scm) + - Documentation tools (sphinx, numpydoc) + - Style tools (ruff, pre-commit) + - Testing tools (pytest, pytest-cov) + +3. **Set up the development environment**: + + ```bash + spin setup + ``` + + This command: + + - Adds upstream remote if missing + - Fetches version tags for correct `setuptools_scm` version detection + +## Using Spin + +We use [spin](https://github.com/scientific-python/spin) for development workflow. +Spin provides a consistent interface for common development tasks. + +### Available Commands + +Run `spin` without arguments to see all available commands: + +```bash +spin +``` + +**Setup Commands:** + +```bash +spin setup # Configure development environment +``` + +**Build Commands:** + +```bash +spin install # Install package in editable mode +``` + +**Test Commands:** + +```bash +spin test # Run all tests +spin test -m NAME # Run tests matching pattern +spin test -v # Verbose output +spin lint # Run ruff linting +spin lint --fix # Auto-fix linting issues +``` + +**Documentation Commands:** + +```bash +spin docs # Build documentation +spin docs --clean # Clean and rebuild +spin docs --open # Build and open in browser +``` + +**Cleanup Commands:** + +```bash +spin clean # Remove temporary files and build artifacts +``` + +## Code Quality + +### Linting with Ruff + +We use [ruff](https://docs.astral.sh/ruff/) for linting and formatting. +Configuration is in `ruff.toml`. + +```bash +# Check for issues +spin lint + +# Auto-fix issues +spin lint --fix + +# Format code +ruff format . + +# Check formatting without changes +ruff format --check . +``` + +### Pre-commit Hooks + +We recommend using pre-commit hooks to catch issues before committing: + +```bash +# Install pre-commit hooks +pre-commit install + +# Run hooks manually on all files +pre-commit run --all-files +``` + +The hooks run: + +- `ruff` - Linting with auto-fix +- `ruff-format` - Code formatting +- `codespell` - Spell checking + +## Testing + +### Running Tests + +```bash +# Run all tests +spin test + +# Run tests matching a pattern +spin test -m memmap + +# Run with pytest directly +pytest trx/tests + +# Run with coverage +pytest trx/tests --cov=trx --cov-report=term-missing +``` + +### Test Data + +Test data is automatically downloaded from Figshare on first run. +Data is cached in `~/.tee_ar_ex/`. + +You can manually fetch test data: + +```python +from trx.fetcher import fetch_data, get_testing_files_dict +fetch_data(get_testing_files_dict()) +``` + +### Writing Tests + +- Tests go in `trx/tests/` +- Use pytest fixtures for setup/teardown +- Use `pytest.mark.skipif` for conditional tests + +Example: + +```python +import pytest +import numpy as np +from numpy.testing import assert_array_equal + +def test_my_function(): + result = my_function(input_data) + expected = np.array([1, 2, 3]) + assert_array_equal(result, expected) + +@pytest.mark.skipif(not dipy_available, reason="Dipy required") +def test_with_dipy(): + # Test that requires dipy + pass +``` + +## Documentation + +### Building Documentation + +```bash +# Build docs +spin docs + +# Clean build +spin docs --clean + +# Build and open in browser +spin docs --open +``` + +Documentation is built with Sphinx and uses: + +- `pydata-sphinx-theme` for styling +- `sphinx-autoapi` for API documentation +- `numpydoc` for NumPy-style docstrings + +### Writing Documentation + +- Source files are in `docs/source/` +- Use MyST Markdown format +- API docs are auto-generated from docstrings + +### NumPy Docstring Format + +All functions and classes should be documented using NumPy-style docstrings: + +```python +def load(filename, reference=None): + """Load a tractogram file. + + Parameters + ---------- + filename : str + Path to the tractogram file. + reference : str, optional + Path to reference anatomy for formats that require it. + + Returns + ------- + tractogram : TrxFile or StatefulTractogram + The loaded tractogram. + + Raises + ------ + ValueError + If the file format is not supported. + + See Also + -------- + save : Save a tractogram to file. + + Examples + -------- + >>> from trx.io import load + >>> trx = load("tractogram.trx") + """ + pass +``` + +## Project Structure + +```text +trx-python/ +├── trx/ # Main package +│ ├── __init__.py +│ ├── cli.py # Command-line interface (Typer) +│ ├── fetcher.py # Test data fetching +│ ├── io.py # Unified I/O interface +│ ├── streamlines_ops.py # Streamline operations +│ ├── trx_file_memmap.py # Core TrxFile class +│ ├── utils.py # Utility functions +│ ├── viz.py # Visualization (optional) +│ ├── workflows.py # High-level workflows +│ └── tests/ # Test suite +├── docs/ # Documentation +│ └── source/ +├── .github/ # GitHub Actions workflows +│ └── workflows/ +├── .spin/ # Spin configuration +│ └── cmds.py +├── pyproject.toml # Project configuration +├── ruff.toml # Ruff configuration +└── .pre-commit-config.yaml # Pre-commit hooks +``` + +## Continuous Integration + +GitHub Actions runs on every push and pull request: + +- **test.yml**: Runs tests on Python 3.11-3.13 across Linux, macOS, Windows +- **codeformat.yml**: Checks code formatting with pre-commit/ruff +- **coverage.yml**: Generates code coverage reports +- **docbuild.yml**: Builds and deploys documentation + +## Environment Variables + +### TRX_TMPDIR + +Controls where temporary files are stored during memory-mapped operations. + +```bash +# Use a specific directory +export TRX_TMPDIR=/path/to/tmp + +# Use current working directory +export TRX_TMPDIR=use_working_dir +``` + +Default: System temp directory (`/tmp` on Linux/macOS, `C:\WINDOWS\Temp` on Windows) + +## Release Process + +Releases are managed via GitHub: + +1. Update version in `pyproject.toml` if needed +2. Create a GitHub release with appropriate tag +3. CI automatically publishes to PyPI + +### Version Detection + +We use `setuptools_scm` for automatic version detection from git tags. +This requires: + +- Proper git tags from upstream +- Running `spin setup` after cloning a fork diff --git a/docs/source/dev.rst b/docs/source/dev.rst deleted file mode 100644 index 00df196..0000000 --- a/docs/source/dev.rst +++ /dev/null @@ -1,347 +0,0 @@ -Developer Guide -=============== - -This guide provides detailed information for developers working on TRX-Python. - -.. toctree:: - :maxdepth: 1 - - contributing - -Installation for Development ----------------------------- - -Prerequisites -~~~~~~~~~~~~~ - -- Python 3.11 or later (Python 3.12+ recommended) -- Git -- pip - -Setting Up Your Environment -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -1. **Clone the repository**: - - .. code-block:: bash - - # If you're a contributor, fork first then clone your fork - git clone https://github.com/YOUR_USERNAME/trx-python.git - cd trx-python - -2. **Install with all development dependencies**: - - .. code-block:: bash - - pip install -e ".[all]" - - This installs: - - - Core dependencies (numpy, nibabel, deepdiff, typer) - - Development tools (spin, setuptools_scm) - - Documentation tools (sphinx, numpydoc) - - Style tools (ruff, pre-commit) - - Testing tools (pytest, pytest-cov) - -3. **Set up the development environment**: - - .. code-block:: bash - - spin setup - - This command: - - - Adds upstream remote if missing - - Fetches version tags for correct ``setuptools_scm`` version detection - -Using Spin ----------- - -We use `spin `_ for development workflow. -Spin provides a consistent interface for common development tasks. - -Available Commands -~~~~~~~~~~~~~~~~~~ - -Run ``spin`` without arguments to see all available commands: - -.. code-block:: bash - - spin - -**Setup Commands:** - -.. code-block:: bash - - spin setup # Configure development environment - -**Build Commands:** - -.. code-block:: bash - - spin install # Install package in editable mode - -**Test Commands:** - -.. code-block:: bash - - spin test # Run all tests - spin test -m NAME # Run tests matching pattern - spin test -v # Verbose output - spin lint # Run ruff linting - spin lint --fix # Auto-fix linting issues - -**Documentation Commands:** - -.. code-block:: bash - - spin docs # Build documentation - spin docs --clean # Clean and rebuild - spin docs --open # Build and open in browser - -**Cleanup Commands:** - -.. code-block:: bash - - spin clean # Remove temporary files and build artifacts - -Code Quality ------------- - -Linting with Ruff -~~~~~~~~~~~~~~~~~ - -We use `ruff `_ for linting and formatting. -Configuration is in ``ruff.toml``. - -.. code-block:: bash - - # Check for issues - spin lint - - # Auto-fix issues - spin lint --fix - - # Format code - ruff format . - - # Check formatting without changes - ruff format --check . - -Pre-commit Hooks -~~~~~~~~~~~~~~~~ - -We recommend using pre-commit hooks to catch issues before committing: - -.. code-block:: bash - - # Install pre-commit hooks - pre-commit install - - # Run hooks manually on all files - pre-commit run --all-files - -The hooks run: - -- ``ruff`` - Linting with auto-fix -- ``ruff-format`` - Code formatting -- ``codespell`` - Spell checking - -Testing -------- - -Running Tests -~~~~~~~~~~~~~ - -.. code-block:: bash - - # Run all tests - spin test - - # Run tests matching a pattern - spin test -m memmap - - # Run with pytest directly - pytest trx/tests - - # Run with coverage - pytest trx/tests --cov=trx --cov-report=term-missing - -Test Data -~~~~~~~~~ - -Test data is automatically downloaded from Figshare on first run. -Data is cached in ``~/.tee_ar_ex/``. - -You can manually fetch test data: - -.. code-block:: python - - from trx.fetcher import fetch_data, get_testing_files_dict - fetch_data(get_testing_files_dict()) - -Writing Tests -~~~~~~~~~~~~~ - -- Tests go in ``trx/tests/`` -- Use pytest fixtures for setup/teardown -- Use ``pytest.mark.skipif`` for conditional tests - -Example: - -.. code-block:: python - - import pytest - import numpy as np - from numpy.testing import assert_array_equal - - def test_my_function(): - result = my_function(input_data) - expected = np.array([1, 2, 3]) - assert_array_equal(result, expected) - - @pytest.mark.skipif(not dipy_available, reason="Dipy required") - def test_with_dipy(): - # Test that requires dipy - pass - -Documentation -------------- - -Building Documentation -~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: bash - - # Build docs - spin docs - - # Clean build - spin docs --clean - - # Build and open in browser - spin docs --open - -Documentation is built with Sphinx and uses: - -- ``pydata-sphinx-theme`` for styling -- ``sphinx-autoapi`` for API documentation -- ``numpydoc`` for NumPy-style docstrings - -Writing Documentation -~~~~~~~~~~~~~~~~~~~~~ - -- Source files are in ``docs/source/`` -- Use reStructuredText format -- API docs are auto-generated from docstrings - -NumPy Docstring Format -~~~~~~~~~~~~~~~~~~~~~~ - -All functions and classes should be documented using NumPy-style docstrings: - -.. code-block:: python - - def load(filename, reference=None): - """Load a tractogram file. - - Parameters - ---------- - filename : str - Path to the tractogram file. - reference : str, optional - Path to reference anatomy for formats that require it. - - Returns - ------- - tractogram : TrxFile or StatefulTractogram - The loaded tractogram. - - Raises - ------ - ValueError - If the file format is not supported. - - See Also - -------- - save : Save a tractogram to file. - - Examples - -------- - >>> from trx.io import load - >>> trx = load("tractogram.trx") - """ - pass - -Project Structure ------------------ - -.. code-block:: text - - trx-python/ - ├── trx/ # Main package - │ ├── __init__.py - │ ├── cli.py # Command-line interface (Typer) - │ ├── fetcher.py # Test data fetching - │ ├── io.py # Unified I/O interface - │ ├── streamlines_ops.py # Streamline operations - │ ├── trx_file_memmap.py # Core TrxFile class - │ ├── utils.py # Utility functions - │ ├── viz.py # Visualization (optional) - │ ├── workflows.py # High-level workflows - │ └── tests/ # Test suite - ├── docs/ # Documentation - │ └── source/ - ├── .github/ # GitHub Actions workflows - │ └── workflows/ - ├── .spin/ # Spin configuration - │ └── cmds.py - ├── pyproject.toml # Project configuration - ├── ruff.toml # Ruff configuration - └── .pre-commit-config.yaml # Pre-commit hooks - -Continuous Integration ----------------------- - -GitHub Actions runs on every push and pull request: - -- **test.yml**: Runs tests on Python 3.11-3.13 across Linux, macOS, Windows -- **codeformat.yml**: Checks code formatting with pre-commit/ruff -- **coverage.yml**: Generates code coverage reports -- **docbuild.yml**: Builds and deploys documentation - -Environment Variables ---------------------- - -TRX_TMPDIR -~~~~~~~~~~ - -Controls where temporary files are stored during memory-mapped operations. - -.. code-block:: bash - - # Use a specific directory - export TRX_TMPDIR=/path/to/tmp - - # Use current working directory - export TRX_TMPDIR=use_working_dir - -Default: System temp directory (``/tmp`` on Linux/macOS, ``C:\WINDOWS\Temp`` on Windows) - -Release Process ---------------- - -Releases are managed via GitHub: - -1. Update version in ``pyproject.toml`` if needed -2. Create a GitHub release with appropriate tag -3. CI automatically publishes to PyPI - -Version Detection -~~~~~~~~~~~~~~~~~ - -We use ``setuptools_scm`` for automatic version detection from git tags. -This requires: - -- Proper git tags from upstream -- Running ``spin setup`` after cloning a fork diff --git a/docs/source/index.rst b/docs/source/index.md similarity index 53% rename from docs/source/index.rst rename to docs/source/index.md index 714faad..d5ce474 100644 --- a/docs/source/index.rst +++ b/docs/source/index.md @@ -1,44 +1,38 @@ -.. trx-python documentation master file, created by - sphinx-quickstart on Fri Jun 24 23:14:56 2022. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -TRX: A community-oriented tractography file format -=================================================== +# TRX: A community-oriented tractography file format We propose **TRX**, a tractography file format designed to facilitate dataset exchange, interoperability, and state-of-the-art analyses, acting as a community-driven replacement for the myriad existing file formats. - -Getting Started -~~~~~~~~~~~~~~~ +## Getting Started New to TRX? Start here: -1. **Understand the format**: Read the :doc:`trx_specifications` to understand the TRX file structure -2. **Learn by example**: Follow our :doc:`auto_examples/index` to learn how to read, write, and manipulate TRX files -3. **Use the CLI tools**: Check out the :doc:`scripts` documentation for command-line operations +1. **Understand the format**: Read the {doc}`trx_specifications` to understand the TRX file structure +2. **Learn by example**: Follow our {doc}`auto_examples/index` to learn how to read, write, and manipulate TRX files +3. **Use the CLI tools**: Check out the {doc}`scripts` documentation for command-line operations -.. grid:: 2 +````{grid} 2 - .. grid-item-card:: Tutorials - :link: auto_examples/index - :link-type: doc +```{grid-item-card} Tutorials +:link: auto_examples/index +:link-type: doc - Learn how to work with TRX files through hands-on tutorials covering - reading/writing files, working with groups, and using metadata. +Learn how to work with TRX files through hands-on tutorials covering +reading/writing files, working with groups, and using metadata. +``` - .. grid-item-card:: TRX Specifications - :link: trx_specifications - :link-type: doc +```{grid-item-card} TRX Specifications +:link: trx_specifications +:link-type: doc - Complete technical specifications of the TRX file format including - header fields, array structures, and naming conventions. +Complete technical specifications of the TRX file format including +header fields, array structures, and naming conventions. +``` +```` +## Why TRX? -Why TRX? -~~~~~~~~ File formats that store the results of computational tractography were typically developed within specific software packages. This approach has facilitated a myriad of applications, but this development approach has also generated @@ -58,35 +52,33 @@ that TRX will serve the community well and the growing computational needs of our field. We encourage community members to consider early contributions to our proposal so as to ensure the new standard will cover the needs of the wider audience of software developers, toolboxes, and scientists. Our long-term plan -is to integrate TRX within the `Brain Imaging Data Structure (BIDS) `_ ecosystem. - - -Acknowledgments -~~~~~~~~~~~~~~~~ -Development of TRX is supported by `NIMH grant 1R01MH126699 `_. - - -.. toctree:: - :maxdepth: 2 - :caption: User Guide: - - trx_specifications - scripts - -.. toctree:: - :maxdepth: 2 - :caption: Tutorials: - - auto_examples/index - -.. toctree:: - :maxdepth: 2 - :caption: Development: - - dev - -.. toctree:: - :maxdepth: 2 - :caption: API Reference: - - autoapi/index +is to integrate TRX within the [Brain Imaging Data Structure (BIDS)](https://bids.neuroimaging.io/) ecosystem. + +## Acknowledgments + +Development of TRX is supported by [NIMH grant 1R01MH126699](https://reporter.nih.gov/search/D-c1NJBQFE-BkwP1Cxc2Lg/project-details/10253558). + +```{toctree} +:maxdepth: 2 +:caption: User Guide: +trx_specifications +scripts +``` + +```{toctree} +:maxdepth: 2 +:caption: Tutorials: +auto_examples/index +``` + +```{toctree} +:maxdepth: 2 +:caption: Development: +dev +``` + +```{toctree} +:maxdepth: 2 +:caption: API Reference: +autoapi/index +``` diff --git a/docs/source/scripts.md b/docs/source/scripts.md new file mode 100644 index 0000000..4cd5815 --- /dev/null +++ b/docs/source/scripts.md @@ -0,0 +1,200 @@ +--- +html_theme.sidebar_secondary.remove: +--- + +# Command-line Interface + +The TRX toolkit provides a unified command-line interface `trx` as well as individual standalone commands for backward compatibility. All commands become available on your `PATH` after installing `trx-python`. + +Each command supports `--help` for full options. + +## Unified CLI: `trx` + +The recommended way to use TRX commands is through the unified `trx` CLI: + +```bash +trx --help # Show all available commands +trx --help # Show help for a specific command +``` + +Available subcommands: + +- `trx info` - Display detailed TRX file information +- `trx concatenate` - Concatenate multiple tractograms +- `trx convert` - Convert between tractography formats +- `trx convert-dsi` - Fix DSI-Studio TRK files +- `trx generate` - Generate TRX from raw data files +- `trx manipulate-dtype` - Change array data types +- `trx compare` - Simple tractogram comparison +- `trx validate` - Validate and clean TRX files +- `trx verify-header` - Check header compatibility +- `trx visualize` - Visualize tractogram overlap + +## Standalone Commands + +For backward compatibility, standalone commands are also available: + +### trx_info + +Display detailed information about a TRX file, including file size, compression status, header metadata (affine, dimensions, voxel sizes), streamline/vertex counts, data keys (dpv, dps, dpg), groups, and archive contents. + +- Only `.trx` files are supported. + +```bash +# Using unified CLI +trx info tractogram.trx + +# Using standalone command +trx_info tractogram.trx +``` + +### trx_concatenate_tractograms + +Concatenate multiple tractograms into a single output. + +- Supports `trk`, `tck`, `vtk`, `fib`, `dpy`, and `trx` inputs. +- Flags: `--delete-dpv`, `--delete-dps`, `--delete-groups` to drop mismatched metadata; `--reference` for formats needing an anatomy reference; `-f` to overwrite. + +```bash +# Using unified CLI +trx concatenate in1.trk in2.trk merged.trx + +# Using standalone command +trx_concatenate_tractograms in1.trk in2.trk merged.trx +``` + +### trx_convert_dsi_studio + +Convert a DSI Studio `.trk` with accompanying `.nii.gz` reference into a cleaned `.trk` or TRX. + +```bash +# Using unified CLI +trx convert-dsi input.trk reference.nii.gz cleaned.trk + +# Using standalone command +trx_convert_dsi_studio input.trk reference.nii.gz cleaned.trk +``` + +### trx_convert_tractogram + +General-purpose converter between `trk`, `tck`, `vtk`, `fib`, `dpy`, and `trx`. + +- Flags: `--reference` for formats needing a NIfTI, `--positions-dtype`, `--offsets-dtype`, `-f` to overwrite. + +```bash +# Using unified CLI +trx convert input.trk output.trx --positions-dtype float32 --offsets-dtype uint64 + +# Using standalone command +trx_convert_tractogram input.trk output.trx --positions-dtype float32 --offsets-dtype uint64 +``` + +### trx_generate_from_scratch + +Build a TRX file from raw NumPy arrays or CSV streamline coordinates. + +- Flags: `--positions`, `--offsets`, `--positions-dtype`, `--offsets-dtype`, spatial options (`--space`, `--origin`), and metadata loaders for dpv/dps/groups/dpg. + +```bash +# Using unified CLI +trx generate fa.nii.gz output.trx --positions positions.npy --offsets offsets.npy + +# Using standalone command +trx_generate_from_scratch fa.nii.gz output.trx --positions positions.npy --offsets offsets.npy +``` + +### trx_manipulate_datatype + +Rewrite TRX datasets with new dtypes for positions/offsets/dpv/dps/dpg/groups. + +- Accepts per-field dtype arguments and overwrites with `-f`. + +```bash +# Using unified CLI +trx manipulate-dtype input.trx output.trx --positions-dtype float16 --dpv color,uint8 + +# Using standalone command +trx_manipulate_datatype input.trx output.trx --positions-dtype float16 --dpv color,uint8 +``` + +### trx_simple_compare + +Compare two tractograms for quick difference checks. + +```bash +# Using unified CLI +trx compare first.trk second.trk + +# Using standalone command +trx_simple_compare first.trk second.trk +``` + +### trx_validate + +Validate a TRX file for consistency and remove invalid streamlines. + +```bash +# Using unified CLI +trx validate data.trx --out cleaned.trx + +# Using standalone command +trx_validate data.trx --out cleaned.trx +``` + +### trx_verify_header_compatibility + +Check whether tractogram headers are compatible for operations such as concatenation. + +```bash +# Using unified CLI +trx verify-header file1.trk file2.trk + +# Using standalone command +trx_verify_header_compatibility file1.trk file2.trk +``` + +### trx_visualize_overlap + +Visualize streamline overlap between tractograms (requires visualization dependencies). + +```bash +# Using unified CLI +trx visualize tractogram.trk reference.nii.gz + +# Using standalone command +trx_visualize_overlap tractogram.trk reference.nii.gz +``` + +## Troubleshooting + +If the `trx` command is not working as expected, run `trx --debug` to print +diagnostic information about the Python interpreter, package location, and +whether all required and optional dependencies are installed: + +```bash +trx --debug + +# Example output: +# Environment diagnostics: +# Python executable : /Users/you/myenv/bin/python +# sys.prefix : /Users/you/myenv +# trx-python version: 0.3.1 +# trx package : /Users/you/myenv/lib/python3.11/site-packages/trx +# +# Required dependencies: +# deepdiff found +# nibabel found +# numpy found +# typer found +# +# Optional dependencies: +# dipy found +# fury not found +# vtk not found +``` + +## Notes + +- Test datasets for examples can be fetched with `python -m trx.fetcher` helpers: `fetch_data(get_testing_files_dict())` downloads to `$TRX_HOME` (default `~/.tee_ar_ex`). +- All commands print detailed usage with `--help`. +- The unified `trx` CLI uses [Typer](https://typer.tiangolo.com/) for beautiful terminal output with colors and rich formatting. diff --git a/docs/source/scripts.rst b/docs/source/scripts.rst deleted file mode 100644 index ce62993..0000000 --- a/docs/source/scripts.rst +++ /dev/null @@ -1,202 +0,0 @@ -:html_theme.sidebar_secondary.remove: - -Command-line Interface -====================== - -The TRX toolkit provides a unified command-line interface ``trx`` as well as individual standalone commands for backward compatibility. All commands become available on your ``PATH`` after installing ``trx-python``. - -Each command supports ``--help`` for full options. - -Unified CLI: ``trx`` --------------------- - -The recommended way to use TRX commands is through the unified ``trx`` CLI: - -.. code-block:: bash - - trx --help # Show all available commands - trx --help # Show help for a specific command - -Available subcommands: - -- ``trx info`` - Display detailed TRX file information -- ``trx concatenate`` - Concatenate multiple tractograms -- ``trx convert`` - Convert between tractography formats -- ``trx convert-dsi`` - Fix DSI-Studio TRK files -- ``trx generate`` - Generate TRX from raw data files -- ``trx manipulate-dtype`` - Change array data types -- ``trx compare`` - Simple tractogram comparison -- ``trx validate`` - Validate and clean TRX files -- ``trx verify-header`` - Check header compatibility -- ``trx visualize`` - Visualize tractogram overlap - -Standalone Commands -------------------- - -For backward compatibility, standalone commands are also available: - -trx_info -~~~~~~~~ -Display detailed information about a TRX file, including file size, compression status, header metadata (affine, dimensions, voxel sizes), streamline/vertex counts, data keys (dpv, dps, dpg), groups, and archive contents. - -- Only ``.trx`` files are supported. - -.. code-block:: bash - - # Using unified CLI - trx info tractogram.trx - - # Using standalone command - trx_info tractogram.trx - -trx_concatenate_tractograms -~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Concatenate multiple tractograms into a single output. - -- Supports ``trk``, ``tck``, ``vtk``, ``fib``, ``dpy``, and ``trx`` inputs. -- Flags: ``--delete-dpv``, ``--delete-dps``, ``--delete-groups`` to drop mismatched metadata; ``--reference`` for formats needing an anatomy reference; ``-f`` to overwrite. - -.. code-block:: bash - - # Using unified CLI - trx concatenate in1.trk in2.trk merged.trx - - # Using standalone command - trx_concatenate_tractograms in1.trk in2.trk merged.trx - -trx_convert_dsi_studio -~~~~~~~~~~~~~~~~~~~~~~ -Convert a DSI Studio ``.trk`` with accompanying ``.nii.gz`` reference into a cleaned ``.trk`` or TRX. - -.. code-block:: bash - - # Using unified CLI - trx convert-dsi input.trk reference.nii.gz cleaned.trk - - # Using standalone command - trx_convert_dsi_studio input.trk reference.nii.gz cleaned.trk - -trx_convert_tractogram -~~~~~~~~~~~~~~~~~~~~~~ -General-purpose converter between ``trk``, ``tck``, ``vtk``, ``fib``, ``dpy``, and ``trx``. - -- Flags: ``--reference`` for formats needing a NIfTI, ``--positions-dtype``, ``--offsets-dtype``, ``-f`` to overwrite. - -.. code-block:: bash - - # Using unified CLI - trx convert input.trk output.trx --positions-dtype float32 --offsets-dtype uint64 - - # Using standalone command - trx_convert_tractogram input.trk output.trx --positions-dtype float32 --offsets-dtype uint64 - -trx_generate_from_scratch -~~~~~~~~~~~~~~~~~~~~~~~~~ -Build a TRX file from raw NumPy arrays or CSV streamline coordinates. - -- Flags: ``--positions``, ``--offsets``, ``--positions-dtype``, ``--offsets-dtype``, spatial options (``--space``, ``--origin``), and metadata loaders for dpv/dps/groups/dpg. - -.. code-block:: bash - - # Using unified CLI - trx generate fa.nii.gz output.trx --positions positions.npy --offsets offsets.npy - - # Using standalone command - trx_generate_from_scratch fa.nii.gz output.trx --positions positions.npy --offsets offsets.npy - -trx_manipulate_datatype -~~~~~~~~~~~~~~~~~~~~~~~ -Rewrite TRX datasets with new dtypes for positions/offsets/dpv/dps/dpg/groups. - -- Accepts per-field dtype arguments and overwrites with ``-f``. - -.. code-block:: bash - - # Using unified CLI - trx manipulate-dtype input.trx output.trx --positions-dtype float16 --dpv color,uint8 - - # Using standalone command - trx_manipulate_datatype input.trx output.trx --positions-dtype float16 --dpv color,uint8 - -trx_simple_compare -~~~~~~~~~~~~~~~~~~ -Compare two tractograms for quick difference checks. - -.. code-block:: bash - - # Using unified CLI - trx compare first.trk second.trk - - # Using standalone command - trx_simple_compare first.trk second.trk - -trx_validate -~~~~~~~~~~~~ -Validate a TRX file for consistency and remove invalid streamlines. - -.. code-block:: bash - - # Using unified CLI - trx validate data.trx --out cleaned.trx - - # Using standalone command - trx_validate data.trx --out cleaned.trx - -trx_verify_header_compatibility -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Check whether tractogram headers are compatible for operations such as concatenation. - -.. code-block:: bash - - # Using unified CLI - trx verify-header file1.trk file2.trk - - # Using standalone command - trx_verify_header_compatibility file1.trk file2.trk - -trx_visualize_overlap -~~~~~~~~~~~~~~~~~~~~~ -Visualize streamline overlap between tractograms (requires visualization dependencies). - -.. code-block:: bash - - # Using unified CLI - trx visualize tractogram.trk reference.nii.gz - - # Using standalone command - trx_visualize_overlap tractogram.trk reference.nii.gz - -Troubleshooting ---------------- - -If the ``trx`` command is not working as expected, run ``trx --debug`` to print -diagnostic information about the Python interpreter, package location, and -whether all required and optional dependencies are installed: - -.. code-block:: bash - - trx --debug - - # Example output: - # Environment diagnostics: - # Python executable : /Users/you/myenv/bin/python - # sys.prefix : /Users/you/myenv - # trx-python version: 0.3.1 - # trx package : /Users/you/myenv/lib/python3.11/site-packages/trx - # - # Required dependencies: - # deepdiff found - # nibabel found - # numpy found - # typer found - # - # Optional dependencies: - # dipy found - # fury not found - # vtk not found - -Notes ------ -- Test datasets for examples can be fetched with ``python -m trx.fetcher`` helpers: ``fetch_data(get_testing_files_dict())`` downloads to ``$TRX_HOME`` (default ``~/.tee_ar_ex``). -- All commands print detailed usage with ``--help``. -- The unified ``trx`` CLI uses `Typer `_ for beautiful terminal output with colors and rich formatting. diff --git a/docs/source/trx_specifications.rst b/docs/source/trx_specifications.md similarity index 56% rename from docs/source/trx_specifications.rst rename to docs/source/trx_specifications.md index ef7b5f5..016d15e 100644 --- a/docs/source/trx_specifications.rst +++ b/docs/source/trx_specifications.md @@ -1,14 +1,14 @@ -:html_theme.sidebar_secondary.remove: +--- +html_theme.sidebar_secondary.remove: +--- -TRX File Format Specifications -=============================== +# TRX File Format Specifications This document contains the complete specifications for the TRX (Tractography File Format) as defined by the TRX specification. TRX is a community-oriented tractography file format designed to facilitate dataset exchange, interoperability, and state-of-the-art analyses. -General Properties ------------------- +## General Properties **File Structure** @@ -24,156 +24,145 @@ General Properties - All arrays have a little-endian byte order - Compression is optional: - - Use ``ZIP_STORE`` for uncompressed storage - - Use ``ZIP_DEFLATE`` if compression is desired + - Use `ZIP_STORE` for uncompressed storage + - Use `ZIP_DEFLATE` if compression is desired - Compressed TRX files will have to be decompressed before being loaded -Header ------- +## Header The header contains metadata for readability, run-time checks, and broader compatibility. It is stored as a dictionary in JSON format with the following fields: **Required Fields:** -.. code-block:: text +```text +VOXEL_TO_RASMM : 4x4 transformation matrix (list of 4 lists, each containing 4 floats) +DIMENSIONS : Image dimensions (list of 3 uint16) +NB_STREAMLINES : Number of streamlines (uint32) +NB_VERTICES : Total number of vertices (uint64) +``` - VOXEL_TO_RASMM : 4x4 transformation matrix (list of 4 lists, each containing 4 floats) - DIMENSIONS : Image dimensions (list of 3 uint16) - NB_STREAMLINES : Number of streamlines (uint32) - NB_VERTICES : Total number of vertices (uint64) +## Arrays -Arrays ------- - -positions.{N}.float{16,32,64} -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +### positions.{N}.float{16,32,64} - Written in world space (RASMM), similar to TCK files - Should always be float16/32/64 (default recommended: float16) - Stored as contiguous 3D array with shape (NB_VERTICES, 3) - The {N} dimension specifier can be omitted for 1D arrays for readability -offsets.uint{32,64} -~~~~~~~~~~~~~~~~~~~ +### offsets.uint{32,64} - Should always be uint32 or uint64 - Indicates the starting vertex index for each streamline (starts at 0) - Streamline lengths can be calculated by: 1. Checking the header for total vertices count - 2. Using positions array size: ``positions.shape[0] / 3`` + 2. Using positions array size: `positions.shape[0] / 3` 3. Calculating differences between consecutive elements: append total_vertices to offsets array and compute ediff1d -dpv (data_per_vertex) -~~~~~~~~~~~~~~~~~~~~~ +### dpv (data_per_vertex) - Always of size (NB_VERTICES, 1) or (NB_VERTICES, N) - Contains data associated with each vertex/point along streamlines - Common uses: FA values, colors, curvature, local coordinate systems -dps (data_per_streamline) -~~~~~~~~~~~~~~~~~~~~~~~~~ +### dps (data_per_streamline) - Always of size (NB_STREAMLINES, 1) or (NB_STREAMLINES, N) - Contains data associated with entire streamlines - Common uses: bundle IDs, mean metrics, algorithm information -Groups ------- +## Groups Groups are tables of indices that allow sparse & overlapping representation (clusters, connectomics, bundles). **Properties:** -- All indices must be ``0 <= id < NB_STREAMLINES`` +- All indices must be `0 <= id < NB_STREAMLINES` - Datatype should be uint32 - Allows efficient retrieval of predefined streamline subsets from memmaps - Variables can have different sizes -dpg (data_per_group) -~~~~~~~~~~~~~~~~~~~~ +### dpg (data_per_group) - Each folder corresponds to name of a group - Not all metadata have to be present in all groups - Always of size (1,) or (N,) per group - Contains group-specific metadata like volumes, mean values, color codes -Supported Data Types --------------------- +## Supported Data Types The TRX format supports the following data types: **Integer Types:** -- ``int8``, ``int16``, ``int32``, ``int64`` -- ``uint8``, ``uint16``, ``uint32``, ``uint64`` +- `int8`, `int16`, `int32`, `int64` +- `uint8`, `uint16`, `uint32`, `uint64` **Floating Point Types:** -- ``float16``, ``float32``, ``float64`` +- `float16`, `float32`, `float64` **Boolean Type:** -- ``bit`` (for boolean data) - -Example File Structure ----------------------- - -.. code-block:: text - - OHBM_demo.trx - |-- dpg - | |-- AF_L - | | |-- mean_fa.float16 - | | |-- shuffle_colors.3.uint8 - | | +-- volume.uint32 - | |-- AF_R - | | |-- mean_fa.float16 - | | |-- shuffle_colors.3.uint8 - | | +-- volume.uint32 - | |-- CC - | | |-- mean_fa.float16 - | | |-- shuffle_colors.3.uint8 - | | +-- volume.uint32 - | |-- CST_L - | | +-- shuffle_colors.3.uint8 - | |-- CST_R - | | +-- shuffle_colors.3.uint8 - | |-- SLF_L - | | |-- mean_fa.float16 - | | |-- shuffle_colors.3.uint8 - | | +-- volume.uint32 - | +-- SLF_R - | |-- mean_fa.float16 - | |-- shuffle_colors.3.uint8 - | +-- volume.uint32 - |-- dpv - | |-- color_x.uint8 - | |-- color_y.uint8 - | |-- color_z.uint8 - | +-- fa.float16 - |-- dps - | |-- algo.uint8 - | |-- algo.json - | |-- clusters_QB.uint16 - | |-- commit_colors.3.uint8 - | +-- commit_weights.float32 - |-- groups - | |-- AF_L.uint32 - | |-- AF_R.uint32 - | |-- CC.uint32 - | |-- CST_L.uint32 - | |-- CST_R.uint32 - | |-- SLF_L.uint32 - | +-- SLF_R.uint32 - |-- header.json - |-- offsets.uint64 - +-- positions.3.float16 - -Naming Conventions ------------------- +- `bit` (for boolean data) + +## Example File Structure + +```text +OHBM_demo.trx +|-- dpg +| |-- AF_L +| | |-- mean_fa.float16 +| | |-- shuffle_colors.3.uint8 +| | +-- volume.uint32 +| |-- AF_R +| | |-- mean_fa.float16 +| | |-- shuffle_colors.3.uint8 +| | +-- volume.uint32 +| |-- CC +| | |-- mean_fa.float16 +| | |-- shuffle_colors.3.uint8 +| | +-- volume.uint32 +| |-- CST_L +| | +-- shuffle_colors.3.uint8 +| |-- CST_R +| | +-- shuffle_colors.3.uint8 +| |-- SLF_L +| | |-- mean_fa.float16 +| | |-- shuffle_colors.3.uint8 +| | +-- volume.uint32 +| +-- SLF_R +| |-- mean_fa.float16 +| |-- shuffle_colors.3.uint8 +| +-- volume.uint32 +|-- dpv +| |-- color_x.uint8 +| |-- color_y.uint8 +| |-- color_z.uint8 +| +-- fa.float16 +|-- dps +| |-- algo.uint8 +| |-- algo.json +| |-- clusters_QB.uint16 +| |-- commit_colors.3.uint8 +| +-- commit_weights.float32 +|-- groups +| |-- AF_L.uint32 +| |-- AF_R.uint32 +| |-- CC.uint32 +| |-- CST_L.uint32 +| |-- CST_R.uint32 +| |-- SLF_L.uint32 +| +-- SLF_R.uint32 +|-- header.json +|-- offsets.uint64 ++-- positions.3.float16 +``` + +## Naming Conventions **Files:** @@ -183,13 +172,12 @@ Naming Conventions **Examples:** -- ``positions.3.float16`` - 3D position data as float16 -- ``fa.float16`` - 1D fractional anisotropy values as float16 -- ``colors.3.uint8`` - RGB color values as 8-bit unsigned integers -- ``bundle_id.uint8`` - Bundle identifiers as 8-bit unsigned integers +- `positions.3.float16` - 3D position data as float16 +- `fa.float16` - 1D fractional anisotropy values as float16 +- `colors.3.uint8` - RGB color values as 8-bit unsigned integers +- `bundle_id.uint8` - Bundle identifiers as 8-bit unsigned integers -Memory and Performance Considerations -------------------------------------- +## Memory and Performance Considerations **Memory Efficiency:** @@ -209,8 +197,7 @@ Memory and Performance Considerations - Group-based organization enables efficient subset operations - Flexible metadata structure accommodates various analysis workflows -Compatibility and Integration ------------------------------ +## Compatibility and Integration TRX is designed for integration with existing neuroimaging ecosystems: @@ -228,5 +215,5 @@ TRX is designed for integration with existing neuroimaging ecosystems: For latest updates and community discussions, see: -- `TRX Specification Repository `_ -- `TRX Python Implementation `_ +- [TRX Specification Repository](https://github.com/tee-ar-ex/trx-spec) +- [TRX Python Implementation](https://github.com/tee-ar-ex/trx-python) diff --git a/pyproject.toml b/pyproject.toml index 360cd70..4ad472e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,7 @@ dev = [ ] doc = [ "matplotlib", + "myst-parser", "numpydoc", "pydata-sphinx-theme >= 0.16.1", "sphinx >= 8.2.0", diff --git a/release_notes.md b/release_notes.md new file mode 100644 index 0000000..86106fd --- /dev/null +++ b/release_notes.md @@ -0,0 +1,12 @@ +# 🎁 Release notes (`0.2.9`) + +## Changes +- lowercase +- Merge pull request #71 from frheault/identified_windows_error + +## Metadata +``` +This version -------- 0.2.9 +Previous version ---- 0.2.8 +Total commits ------- 2 +```