.. _contributing:

======================
Contributing to MedTDA
======================

Thank you for your interest in contributing to MedTDA! This document provides guidelines and instructions for contributing.

.. contents:: Contents
   :local:
   :depth: 2

Ways to Contribute
==================

There are many ways to contribute to MedTDA:

🐛 **Bug Reports**
  Found a bug? Report it!

✨ **Feature Requests**
  Have an idea? Suggest it!

📝 **Documentation**
  Improve docs, fix typos, add examples

💻 **Code**
  Fix bugs, implement features, optimize performance

🧪 **Testing**
  Write tests, test on different platforms

📊 **Examples**
  Share notebooks, case studies, applications

💬 **Community**
  Answer questions, help other users

All contributions are valued and appreciated!

Getting Started
===============

Quick Start
-----------

1. **Fork** the repository on GitHub
2. **Clone** your fork locally
3. **Create** a branch for your changes
4. **Make** your changes
5. **Test** your changes
6. **Commit** with clear messages
7. **Push** to your fork
8. **Submit** a pull request

Detailed instructions below.

Prerequisites
-------------

**Required:**

- Python 3.8 or later
- Git
- GitHub account

**Recommended:**

- Familiarity with Git/GitHub workflow
- Python development experience
- Understanding of medical imaging (for some contributions)

Development Setup
=================

1. Fork and Clone
-----------------

**Fork the repository:**

Visit https://github.com/dashtiali/medtda and click "Fork".

**Clone your fork:**

.. code-block:: bash

    git clone https://github.com/YOUR_USERNAME/medtda.git
    cd medtda

**Add upstream remote:**

.. code-block:: bash

    git remote add upstream https://github.com/dashtiali/medtda.git

2. Create Environment
---------------------

**Using venv:**

.. code-block:: bash

    python -m venv venv
    source venv/bin/activate  # On Windows: venv\Scripts\activate

**Using conda:**

.. code-block:: bash

    conda create -n medtda-dev python=3.9
    conda activate medtda-dev

3. Install in Development Mode
-------------------------------

.. code-block:: bash

    # Install package in editable mode with dev dependencies
    pip install -e ".[dev]"
    
    # Or install from requirements
    pip install -r requirements-dev.txt
    pip install -e .

**Development dependencies include:**

- ``pytest`` - Testing framework
- ``pytest-cov`` - Coverage reporting
- ``black`` - Code formatting
- ``flake8`` - Linting
- ``mypy`` - Type checking
- ``sphinx`` - Documentation
- ``pre-commit`` - Git hooks

4. Install Pre-commit Hooks
----------------------------

.. code-block:: bash

    pre-commit install

This will automatically run checks before each commit.

Development Workflow
====================

1. Create a Branch
------------------

**Always create a new branch for your work:**

.. code-block:: bash

    git checkout -b feature/add-new-vectorizer
    # or
    git checkout -b fix/memory-leak-in-batch

**Branch naming conventions:**

- ``feature/description`` - New features
- ``fix/description`` - Bug fixes
- ``docs/description`` - Documentation
- ``refactor/description`` - Code refactoring
- ``test/description`` - Adding tests

2. Make Your Changes
--------------------

**Follow coding standards:**

- Use **Black** for formatting (runs automatically with pre-commit)
- Follow **PEP 8** style guide
- Write **docstrings** for all public functions/classes
- Add **type hints** where appropriate
- Keep functions **focused and small**
- Write **clear variable names**

**Example:**

.. code-block:: python

    def compute_persistence_stats(barcodes: Dict[int, np.ndarray]) -> np.ndarray:
        """Compute summary statistics from persistence barcodes.
        
        Parameters
        ----------
        barcodes : Dict[int, np.ndarray]
            Dictionary mapping homology dimension to persistence barcodes.
            Each barcode is an array of shape (n_features, 2) with birth-death pairs.
        
        Returns
        -------
        np.ndarray
            Array of shape (13,) containing summary statistics.
        
        Examples
        --------
        >>> barcodes = {0: np.array([[0.0, 0.5], [0.1, 0.8]])}
        >>> stats = compute_persistence_stats(barcodes)
        >>> stats.shape
        (13,)
        """
        # Implementation
        pass

3. Write Tests
--------------

**All new code should include tests!**

**Create test file:**

.. code-block:: bash

    # For new module medtda/newmodule.py
    # Create tests/test_newmodule.py

**Write tests:**

.. code-block:: python

    import pytest
    import numpy as np
    from medtda.newmodule import new_function
    
    def test_new_function_basic():
        """Test basic functionality."""
        result = new_function(input_data)
        assert result.shape == (10, 10)
    
    def test_new_function_edge_case():
        """Test edge case handling."""
        with pytest.raises(ValueError):
            new_function(invalid_input)
    
    def test_new_function_with_different_inputs():
        """Test with various inputs."""
        for input_val in [1, 5, 10]:
            result = new_function(input_val)
            assert result > 0

**Run tests:**

.. code-block:: bash

    # Run all tests
    pytest
    
    # Run specific test file
    pytest tests/test_newmodule.py
    
    # Run with coverage
    pytest --cov=medtda
    
    # Run with verbose output
    pytest -v

**Test coverage:**

Aim for >80% code coverage. Check coverage:

.. code-block:: bash

    pytest --cov=medtda --cov-report=html
    # Open htmlcov/index.html in browser

4. Update Documentation
-----------------------

**Docstrings:**

All public functions/classes need docstrings in NumPy style:

.. code-block:: python

    def my_function(param1: str, param2: int = 5) -> bool:
        """Short one-line description.
        
        Longer description if needed. Can span multiple lines.
        Explain what the function does, not how it does it.
        
        Parameters
        ----------
        param1 : str
            Description of param1.
        param2 : int, optional
            Description of param2 (default is 5).
        
        Returns
        -------
        bool
            Description of return value.
        
        Raises
        ------
        ValueError
            If param1 is empty string.
        
        Examples
        --------
        >>> my_function("test", 10)
        True
        
        Notes
        -----
        Additional information about the function.
        
        See Also
        --------
        related_function : Related functionality.
        """
        pass

**User documentation:**

If adding new features, update relevant documentation files:

- **User guide:** ``docs/user_guide/``
- **API reference:** ``docs/api/``
- **Examples:** ``docs/examples/``

**Build documentation locally:**

.. code-block:: bash

    cd docs
    make html
    # Open docs/_build/html/index.html

5. Run Code Quality Checks
---------------------------

**Format code:**

.. code-block:: bash

    black medtda tests

**Lint:**

.. code-block:: bash

    flake8 medtda tests

**Type check:**

.. code-block:: bash

    mypy medtda

**All checks:**

.. code-block:: bash

    # Run all checks (like CI will)
    black --check medtda tests
    flake8 medtda tests
    mypy medtda
    pytest --cov=medtda

6. Commit Your Changes
-----------------------

**Make focused commits:**

.. code-block:: bash

    git add medtda/newmodule.py tests/test_newmodule.py
    git commit -m "Add new persistence vectorizer"

**Commit message guidelines:**

- **First line:** Short summary (50 chars or less)
- **Body:** Detailed explanation if needed
- **Reference issues:** "Fixes #123" or "Relates to #456"

**Good commit messages:**

.. code-block:: text

    Add silhouette vectorization method
    
    Implements silhouette representation for persistence diagrams.
    Includes tests and documentation.
    
    Fixes #45

**Bad commit messages:**

.. code-block:: text

    fixed stuff
    WIP
    asdf

7. Push and Create Pull Request
--------------------------------

**Push to your fork:**

.. code-block:: bash

    git push origin feature/add-new-vectorizer

**Create pull request:**

1. Go to https://github.com/dashtiali/medtda
2. Click "New Pull Request"
3. Select your branch
4. Fill in the template
5. Submit!

**Pull request template:**

.. code-block:: markdown

    ## Description
    Brief description of changes.
    
    ## Type of Change
    - [ ] Bug fix
    - [ ] New feature
    - [ ] Documentation
    - [ ] Other (describe)
    
    ## Checklist
    - [ ] Code follows style guidelines
    - [ ] Tests added/updated
    - [ ] Documentation updated
    - [ ] All tests pass
    - [ ] No merge conflicts
    
    ## Related Issues
    Fixes #123

Code Guidelines
===============

Style Guide
-----------

**Follow PEP 8** with these specifics:

- **Line length:** 88 characters (Black default)
- **Indentation:** 4 spaces
- **Imports:** Grouped and sorted
- **Quotes:** Double quotes preferred
- **Naming:**
  
  - ``snake_case`` for functions and variables
  - ``PascalCase`` for classes
  - ``UPPER_CASE`` for constants

**Example:**

.. code-block:: python

    """Module docstring."""
    
    import os
    from pathlib import Path
    from typing import Dict, List, Optional, Union
    
    import numpy as np
    from scipy import stats
    
    from medtda.base import BaseVectorizer
    
    
    MAX_FEATURES = 1000  # Constant
    
    
    class NewVectorizer(BaseVectorizer):
        """Class docstring."""
        
        def __init__(self, resolution: int = 100):
            """Initialize vectorizer."""
            self.resolution = resolution
        
        def vectorize(self, barcodes: Dict[int, np.ndarray]) -> np.ndarray:
            """Vectorize barcodes."""
            # Implementation
            pass

Type Hints
----------

**Use type hints for all public functions:**

.. code-block:: python

    from typing import Dict, List, Optional, Union
    import numpy as np
    
    def process_image(
        image: np.ndarray,
        mask: Optional[np.ndarray] = None,
        normalize: bool = True
    ) -> Dict[str, np.ndarray]:
        """Process image with optional mask."""
        pass

Error Handling
--------------

**Provide informative error messages:**

.. code-block:: python

    def load_image(path: str) -> np.ndarray:
        """Load image from file."""
        if not os.path.exists(path):
            raise FileNotFoundError(
                f"Image file not found: {path}"
            )
        
        if not path.endswith(('.nii', '.nii.gz')):
            raise ValueError(
                f"Unsupported file format: {path}. "
                "Supported formats: .nii, .nii.gz"
            )
        
        # Load image
        pass

Testing Guidelines
==================

Test Structure
--------------

**Organize tests to mirror code structure:**

.. code-block:: text

    medtda/
        featureextractor.py
        preprocessor.py
    tests/
        test_featureextractor.py
        test_preprocessor.py

**Test file structure:**

.. code-block:: python

    """Tests for medtda.featureextractor module."""
    
    import pytest
    import numpy as np
    from medtda import FeatureExtractor
    
    
    @pytest.fixture
    def sample_image():
        """Create sample 3D image for testing."""
        return np.random.rand(50, 50, 50)
    
    
    class TestFeatureExtractor:
        """Tests for FeatureExtractor class."""
        
        def test_initialization(self):
            """Test extractor initialization."""
            extractor = FeatureExtractor()
            assert extractor is not None
        
        def test_execute_basic(self, sample_image):
            """Test basic feature extraction."""
            extractor = FeatureExtractor()
            features = extractor.execute(sample_image)
            assert len(features) > 0
        
        def test_execute_with_mask(self, sample_image):
            """Test extraction with mask."""
            mask = np.zeros_like(sample_image)
            mask[10:40, 10:40, 10:40] = 1
            
            extractor = FeatureExtractor(crop_to_roi=True)
            features = extractor.execute(sample_image, mask)
            assert features is not None

Test Types
----------

**Unit tests:**

Test individual functions in isolation.

.. code-block:: python

    def test_normalize_minmax():
        """Test minmax normalization."""
        from medtda.utils import normalize
        
        data = np.array([1, 2, 3, 4, 5])
        normalized = normalize(data, method='minmax')
        
        assert normalized.min() == 0
        assert normalized.max() == 1

**Integration tests:**

Test components working together.

.. code-block:: python

    def test_full_pipeline():
        """Test complete feature extraction pipeline."""
        image = load_sample_image()
        mask = load_sample_mask()
        
        extractor = FeatureExtractor(
            normalize=True,
            crop_to_roi=True,
            vectorization_method='PersImage'
        )
        
        features = extractor.execute(image, mask)
        
        # features is a flat dict with keys like 'PersImage_H0_f0', ...
        pi_features = {k: v for k, v in features.items() if k.startswith('PersImage')}
        assert len(pi_features) > 0

**Parametrized tests:**

Test with multiple inputs.

.. code-block:: python

    @pytest.mark.parametrize("method,expected_count", [
        ('PersStats', 13),
        ('BettiCurve', 100),
        ('PersImage', 400),
    ])
    def test_vectorization_methods(method, expected_count, sample_image):
        """Test different vectorization methods."""
        extractor = FeatureExtractor(vectorization_method=method)
        features = extractor.execute(sample_image)
        method_features = {k: v for k, v in features.items() if k.startswith(method)}
        assert len(method_features) == expected_count

Fixtures
--------

**Use fixtures for common test data:**

.. code-block:: python

    @pytest.fixture
    def sample_3d_image():
        """3D test image."""
        return np.random.rand(50, 50, 50)
    
    @pytest.fixture
    def sample_2d_image():
        """2D test image."""
        return np.random.rand(128, 128)
    
    @pytest.fixture
    def sample_mask():
        """Binary mask."""
        mask = np.zeros((50, 50, 50))
        mask[10:40, 10:40, 10:40] = 1
        return mask

Documentation Guidelines
========================

Documentation Types
-------------------

1. **Docstrings** - In-code documentation (NumPy style)
2. **User Guide** - Conceptual tutorials (RST files)
3. **API Reference** - Auto-generated from docstrings
4. **Examples** - Practical code examples
5. **Theory** - Mathematical background

Writing Style
-------------

**User documentation:**

- Write for your audience (beginners vs. experts)
- Use active voice
- Be concise but clear
- Include examples
- Link to related content

**Good:**

    Use the ``FeatureExtractor`` class to extract topological features from your medical images. It handles preprocessing, persistent homology computation, and vectorization automatically.

**Bad:**

    The FeatureExtractor is a class that can be utilized for the purpose of extracting features from images using topological data analysis methods.

Code Examples
-------------

**Include runnable examples:**

.. code-block:: python

    """
    Examples
    --------
    Basic usage:
    
    >>> from medtda import FeatureExtractor
    >>> extractor = FeatureExtractor()
    >>> features = extractor.execute('image.nii.gz')
    
    With custom parameters:
    
    >>> extractor = FeatureExtractor(
    ...     normalize=True,
    ...     vectorization_method='PersImage'
    ... )
    >>> features = extractor.execute('image.nii.gz')
    """

Pull Request Process
====================

Checklist Before Submitting
----------------------------

✅ **Code:**

- [ ] Code follows style guide
- [ ] All tests pass
- [ ] New tests added for new code
- [ ] No decrease in code coverage
- [ ] Type hints added

✅ **Documentation:**

- [ ] Docstrings for all new functions/classes
- [ ] User documentation updated (if needed)
- [ ] Examples added (if adding features)
- [ ] Changelog updated

✅ **Git:**

- [ ] Commits are focused and well-described
- [ ] Branch is up to date with main
- [ ] No merge conflicts

Review Process
--------------

1. **Automated checks** run (tests, linting, coverage)
2. **Maintainers review** code and documentation
3. **Feedback** provided (if changes needed)
4. **Iteration** - address feedback
5. **Approval** - once requirements met
6. **Merge** - maintainers merge to main

**Tips for faster reviews:**

- Keep PRs focused and small
- Write clear descriptions
- Respond to feedback promptly
- Be patient and respectful

Community Guidelines
====================

Code of Conduct
---------------

All contributors must follow our Code of Conduct:

- **Be respectful** and professional
- **Be inclusive** and welcoming
- **Accept constructive criticism** gracefully
- **Focus on what's best** for the community
- **Show empathy** towards others

Reporting Issues
----------------

**Bug reports:**

Use the bug report template:

- Clear title
- Steps to reproduce
- Expected vs. actual behavior
- MedTDA version, Python version, OS
- Minimal code example
- Error messages/traceback

**Feature requests:**

Use the feature request template:

- Clear description of feature
- Use case / motivation
- Proposed solution (if any)
- Alternatives considered

Getting Help
============

**Questions about contributing:**

- Read this guide thoroughly
- Check existing issues and PRs
- Ask in GitHub Discussions
- Contact maintainers

**Stuck on something:**

- Don't hesitate to ask for help!
- Comment on your PR
- Open a discussion topic
- We're here to help you succeed!

Recognition
===========

All contributors are recognized:

- Listed in ``AUTHORS.md``
- Acknowledged in release notes
- GitHub contributors page
- Our gratitude and appreciation! 🎉

See Also
========

- :doc:`installation` - Development setup
- :doc:`changelog` - Release history
- :doc:`faq` - Frequently asked questions
- `GitHub Repository <https://github.com/dashtiali/medtda>`_
- `Issue Tracker <https://github.com/dashtiali/medtda/issues>`_
- `Discussions <https://github.com/dashtiali/medtda/discussions>`_
