Contributing to PyAMA
This guide covers contributing to PyAMA, including coding guidelines, development workflow, and contribution process.
Development Environment
Prerequisites
Python 3.11+
UV package manager
Git
Code editor (VS Code recommended with Python extension)
Setup
Clone the Repository
git clone https://github.com/SoftmatterLMU-RaedlerGroup/pyama.git cd pyama
Install Dependencies
uv sync --all-extras
Install in Development Mode
uv pip install -e pyama/
Verify Installation
uv run pyama --version uv run pytest tests/
Recommended VS Code Extensions
Python
Pylance
Python Docstring Generator
GitLens
EditorConfig
Coding Guidelines
Style Guide
PyAMA follows PEP 8 with additional conventions:
# Use snake_case for variables and functions
def process_cell_data(input_data: np.ndarray) -> Dict[str, float]:
"""Process cell data and return metrics.
Args:
input_data: Array of cell measurements
Returns:
Dictionary with calculated metrics
"""
return {"mean": np.mean(input_data), "std": np.std(input_data)}
# Use PascalCase for classes
class CellProcessor:
"""Handles processing of cell data."""
def __init__(self, config: ProcessingConfig):
self.config = config
def process(self, data: CellData) -> ProcessedData:
"""Process cell data according to configuration."""
return ProcessedData(data, self.config)
# Use UPPER_CASE for constants
DEFAULT_MIN_CELL_SIZE = 50
MAX_WORKERS = 8
Type Hints
Use built-in generics and union types:
# Preferred
def load_config(path: Path) -> Dict[str, Any]:
"""Load configuration from file."""
return {}
def process_data(
items: List[str],
options: Optional[Dict] = None
) -> Tuple[List[str],]:
"""Process items with optional options."""
pass
# Avoid
from typing import Dict, List, Tuple, Optional
def load_config(path: 'Dict[str, str]'):
pass
Import Organization
All imports at the top of the file:
# Standard library
import os
from pathlib import Path
from typing import Any, Dict, List
# Third-party
import numpy as np
from scipy import ndimage
# Local imports
from pyama.types.processing import Channels, ChannelSelection
from pyama.utils import validate_config
Docstring Format
Use Google-style docstrings:
def segment_cells(
image: np.ndarray,
method: str = "logstd",
parameters: Optional[Dict[str, Any]] = None
) -> np.ndarray:
"""Segment cells in microscopy image.
Args:
image: Input microscopy image (2D array)
method: Segmentation method to use ('logstd', 'threshold')
parameters: Optional method-specific parameters
Returns:
Binary mask where True indicates cell pixels
Raises:
ValueError: If method is not supported
ValueError: If image is not 2D
Example:
>>> image = np.random.random((100, 100))
>>> mask = segment_cells(image, method="threshold")
>>> print(mask.shape)
(100, 100)
"""
if image.ndim != 2:
raise ValueError("Image must be 2D")
if method not in ["logstd", "threshold"]:
raise ValueError(f"Method {method} not supported")
# Implementation
return binary_mask
Frontend State/Events (React)
Use typed events and focused hooks instead of ad-hoc global state:
type TaskUpdate = {
taskId: string;
progressPercent: number;
phase: string;
};
function useTaskStatus(taskId: string) {
const [task, setTask] = useState<TaskUpdate | null>(null);
useEffect(() => {
if (!taskId) return;
const timer = setInterval(async () => {
const next = await api.getTask(taskId);
setTask({
taskId: next.id,
progressPercent: next.progress_percent ?? 0,
phase: next.phase ?? "pending",
});
}, 1000);
return () => clearInterval(timer);
}, [taskId]);
return task;
}
Testing Guidelines
Test Structure
# pyama/tests/processing/test_seg.py
import pytest
import numpy as np
from pyama.processing.segmentation import segment_cells
class TestSegmentation:
"""Test cell segmentation functionality."""
@pytest.fixture
def sample_image(self):
"""Create test image with known cells."""
image = np.zeros((100, 100), dtype=np.float32)
# Add circular cells
y, x = np.ogrid[-50:50, -50:50]
mask = x*x + y*y <= 20*20
image[mask] = 1.0
return image
def test_segment_cells_logstd(self, sample_image):
"""Test LOG-STD segmentation."""
result = segment_cells(sample_image, method="logstd")
# Verify basic properties
assert result.dtype == bool
assert result.shape == sample_image.shape
assert np.any(result) # Should detect cells
# Verify specific properties
n_cells = len(np.unique(result)) - 1 # Exclude background
assert n_cells == 1
def test_segment_cells_invalid_method(self, sample_image):
"""Test error handling for invalid method."""
with pytest.raises(ValueError, match="not supported"):
segment_cells(sample_image, method="invalid")
Visual Testing Rules
From AGENTS.md testing requirements:
Essential tests only: Implement minimal tests that demonstrate correctness
Save plots to
{package}/tests/_plots/directory (e.g.,pyama/tests/_plots/for core tests)Use deterministic RNG with
np.random.seed()Keep assertions robust: Avoid tight numerical tolerances
pyama/tests/
├── _plots/ # Generated test plots
├── analysis/ # Analysis model tests
│ ├── test_event.py
│ └── test_kinetic.py
├── features/ # Feature extraction tests
│ ├── test_area.py
│ ├── test_intensity_total.py
│ └── test_particle_num.py
├── processing/ # Processing workflow tests
│ ├── test_merge.py
│ ├── test_normalization.py
│ ├── test_seg.py
│ └── test_track.py
└── utils/ # Utility function tests
└── progress.py
Running Tests
# Run all tests
uv run pytest
# Run specific test file
uv run pytest pyama/tests/processing/test_merge.py
# Run with coverage
uv run pytest --cov=pyama
# Run visual tests
uv run pytest pyama/tests/processing/test_seg.py -v
Documentation Guidelines
Code Documentation
Every public function/class has a docstring
Document all parameters and return types
Include examples for complex functions
Use cross-references with
:class:,:func:, etc.
README Files
Each package has minimal README with:
Brief description
Installation instructions
Usage example
Link to full documentation
Changes Documentation
Update:
CHANGELOG.mdfor user-facing changesInline code comments for technical notes
Documentation for new features
Git Workflow
Branch Structure
main # Stable release branch
├── develop # Integration branch
├── feature/xxx # Feature branches
├── bugfix/xxx # Bug fix branches
└── release/vX.Y.Z # Release branches
Commit Messages
Follow conventional commits:
type(scope): description
feat(core): add support for new file format
fix(pro): fix crash when loading invalid config
docs(readme): update installation instructions
refactor(backend): improve error handling
test(visualization): add tests for trace plotting
Development Workflow
Create Branch
git checkout -b feature/my-feature
Make Changes
Write code
Add tests
Update documentation
Test Changes
uv run pytest uv run ruff check uv run ruff format
Commit Changes
git add . git commit -m "feat: implement awesome feature"
Push and Create PR
git push origin feature/my-feature # Create PR on GitHub
Pull Request Process
PR Requirements
All tests pass
Code formatted with ruff
Documentation updated
At least one review
PR Template
## Description Brief description of changes ## Changes - What was changed - Why it was changed ## Testing - How tested - Test coverage ## Checklist - [ ] Tests pass - [ ] Code formatted - [ ] Documentation updated
Code Quality
Linting and Formatting
# Check code style
uv run ruff check
# Format code
uv run ruff format
# Type checking
uv run ty check
Pre-commit Hooks (Optional)
# Install pre-commit
pip install pre-commit
# Setup hooks
pre-commit install
# Hooks will run automatically on commit
Code Review Focus Areas
Correctness: Does the code work as intended?
Performance: Is it efficient for large datasets?
Readability: Is the code clear and maintainable?
Testing: Are edge cases covered?
Documentation: Is it well-documented?
Package-specific Guidelines
pyama
Focus on pure Python algorithms
Minimal dependencies
Extensive type hints
Performance-critical sections use numpy
pyama-react
Keep components typed and focused
Move side effects to hooks in
src/hooks/Provide clear status/error feedback for all backend operations
Preserve API compatibility with
pyamaserver routes
Performance Considerations
Memory Management
# Good: Use memory mapping for large arrays
image_stack = np.load("large_file.npy", mmap_mode="r")
# Good: Process frames one at a time
for frame in frames:
process_frame(frame)
del frame # Explicit cleanup
# Avoid: Loading entire dataset into memory
all_data = [load_file(f) for f in file_list] # Bad for large datasets
Threading
# Correct: Use ThreadPoolExecutor for CPU work
with ThreadPoolExecutor(max_workers=4) as executor:
futures = [executor.submit(process_fov, fov) for fov in fovs]
# Avoid: Creating too many threads
threads = [Thread(target=worker) for _ in range(100)] # Bad
Vectorization
# Good: Use numpy operations
result = np.sum(image > threshold) # Fast
# Avoid: Python loops
count = 0
for i in range(image.shape[0]):
for j in range(image.shape[1]):
if image[i, j] > threshold:
count += 1 # Slow
Release Process
Version Management
Use semantic versioning (MAJOR.MINOR.PATCH):
MAJOR: Breaking changes
MINOR: New features
PATCH: Bug fixes
Release Checklist
Update Version
Update
versionin pyproject.tomlUpdate version in each package’s
__init__.py
Changelog
Update
CHANGELOG.mdDocument all changes
Testing
Run full test suite
Test installation from source
Documentation
Update user documentation
Check for broken links
Tag and Push
git tag -a v1.2.0 -m "Release v1.2.0" git push origin v1.2.0
Publishing
Each package can be published independently:
cd pyama
uv publish
cd pyama-acdc
uv publish
# etc.
Getting Help
Resources
Documentation: Full reference at https://pyama.readthedocs.io
Issues: Report bugs at https://github.com/SoftmatterLMU-RaedlerGroup/pyama/issues
Discussions: Feature requests and questions
Discord: Real-time chat (if available)
Asking Good Questions
Check existing issues and documentation
Provide minimal reproducible example
Include system information (OS, Python version)
Share error messages and logs
Recognition
Contributors are recognized in:
CONTRIBUTORS.mdfileRelease notes
git commit history (co-authorship)
Thank you for contributing to PyAMA! Your improvements help the microscopy community analyze data more effectively.