Contributing to MedTDA
Thank you for your interest in contributing to MedTDA! This document provides guidelines and instructions for contributing.
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
Fork the repository on GitHub
Clone your fork locally
Create a branch for your changes
Make your changes
Test your changes
Commit with clear messages
Push to your fork
Submit a pull request
Detailed instructions below.
Prerequisites
Required:
Python 3.10 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:
git clone https://github.com/YOUR_USERNAME/medtda.git
cd medtda
Add upstream remote:
git remote add upstream https://github.com/dashtiali/medtda.git
2. Create Environment
Using venv:
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
Using conda:
conda create -n medtda-dev python=3.9
conda activate medtda-dev
3. Install in Development Mode
# 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 frameworkpytest-cov- Coverage reportingblack- Code formattingflake8- Lintingmypy- Type checkingsphinx- Documentationpre-commit- Git hooks
4. Install Pre-commit Hooks
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:
git checkout -b feature/add-new-vectorizer
# or
git checkout -b fix/memory-leak-in-batch
Branch naming conventions:
feature/description- New featuresfix/description- Bug fixesdocs/description- Documentationrefactor/description- Code refactoringtest/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:
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:
# For new module medtda/newmodule.py
# Create tests/test_newmodule.py
Write tests:
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:
# 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:
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:
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:
cd docs
make html
# Open docs/_build/html/index.html
5. Run Code Quality Checks
Format code:
black medtda tests
Lint:
flake8 medtda tests
Type check:
mypy medtda
All checks:
# 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:
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:
Add silhouette vectorization method
Implements silhouette representation for persistence diagrams.
Includes tests and documentation.
Fixes #45
Bad commit messages:
fixed stuff
WIP
asdf
7. Push and Create Pull Request
Push to your fork:
git push origin feature/add-new-vectorizer
Create pull request:
Click “New Pull Request”
Select your branch
Fill in the template
Submit!
Pull request template:
## 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_casefor functions and variablesPascalCasefor classesUPPER_CASEfor constants
Example:
"""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:
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:
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:
medtda/
featureextractor.py
preprocessor.py
tests/
test_featureextractor.py
test_preprocessor.py
Test file structure:
"""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.
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.
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.
@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:
@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
Docstrings - In-code documentation (NumPy style)
User Guide - Conceptual tutorials (RST files)
API Reference - Auto-generated from docstrings
Examples - Practical code examples
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
FeatureExtractorclass 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:
"""
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
Automated checks run (tests, linting, coverage)
Maintainers review code and documentation
Feedback provided (if changes needed)
Iteration - address feedback
Approval - once requirements met
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.mdAcknowledged in release notes
GitHub contributors page
Our gratitude and appreciation! 🎉
See Also
Installation - Development setup
Changelog - Release history
Frequently Asked Questions - Frequently asked questions