|
| 1 | +# NGLUI Development Guide |
| 2 | + |
| 3 | +This guide documents the development workflow, testing strategies, and coding standards for the NGLUI package. |
| 4 | + |
| 5 | +## Development Environment |
| 6 | + |
| 7 | +### Package Management: UV + Poe |
| 8 | + |
| 9 | +This project uses **UV** for dependency management and **Poe** for task running instead of the system Python environment: |
| 10 | + |
| 11 | +```bash |
| 12 | +# Install dependencies (automatically creates virtual environment) |
| 13 | +uv sync |
| 14 | + |
| 15 | +# Run tasks via Poe (preferred over direct python commands) |
| 16 | +poe test # Run tests with coverage |
| 17 | +poe doc-preview # Preview documentation |
| 18 | +poe bump patch # Bump version (patch/minor/major) |
| 19 | +poe drybump patch # Dry run version bump |
| 20 | +``` |
| 21 | + |
| 22 | +### Key Commands |
| 23 | + |
| 24 | +From `pyproject.toml`: |
| 25 | + |
| 26 | +- **Testing**: `poe test` → `uv run pytest --cov=nglui tests` |
| 27 | +- **Documentation**: `poe doc-preview` → `uv run mkdocs serve` |
| 28 | +- **Version Management**: `poe bump patch/minor/major` |
| 29 | +- **Linting**: `uv run ruff check src/ tests/` |
| 30 | + |
| 31 | +## Python Version Requirements |
| 32 | + |
| 33 | +- **Minimum**: Python 3.10+ (leverages match statements and improved type annotations) |
| 34 | +- **Tested**: Python 3.10, 3.11, 3.12 |
| 35 | +- **Compatibility**: Use `typing-extensions` for Python < 3.11 features |
| 36 | + |
| 37 | +## Type Hinting Standards |
| 38 | + |
| 39 | +### Required Practices |
| 40 | + |
| 41 | +- **All functions/methods** must have complete type annotations |
| 42 | +- **All class attributes** must have type hints (use `attrs` with type annotations) |
| 43 | +- **Import patterns**: |
| 44 | + ```python |
| 45 | + from typing import Optional, Union, Literal, Any, Dict, List, Tuple |
| 46 | + from typing_extensions import Self # For Python < 3.11 |
| 47 | + ``` |
| 48 | + |
| 49 | +### Common Patterns |
| 50 | + |
| 51 | +```python |
| 52 | +from typing import Optional, Union, Literal |
| 53 | +import attrs |
| 54 | +import numpy as np |
| 55 | +import pandas as pd |
| 56 | + |
| 57 | +@attrs.define |
| 58 | +class ExampleClass: |
| 59 | + # Required attributes with types |
| 60 | + name: str |
| 61 | + values: List[float] = attrs.field(factory=list) |
| 62 | + |
| 63 | + # Optional attributes |
| 64 | + description: Optional[str] = None |
| 65 | + |
| 66 | + # Use converters for type safety |
| 67 | + point: List[float] = attrs.field(converter=strip_numpy_types) |
| 68 | + |
| 69 | + # Use validators for constraints |
| 70 | + resolution: Optional[np.ndarray] = attrs.field( |
| 71 | + default=None, |
| 72 | + converter=attrs.converters.optional(np.array) |
| 73 | + ) |
| 74 | + |
| 75 | +def process_data( |
| 76 | + data: pd.DataFrame, |
| 77 | + column: Union[str, List[str]], |
| 78 | + optional_param: Optional[int] = None |
| 79 | +) -> Tuple[pd.DataFrame, Dict[str, Any]]: |
| 80 | + """Process dataframe with proper type annotations.""" |
| 81 | + pass |
| 82 | +``` |
| 83 | + |
| 84 | +## Testing Strategy |
| 85 | + |
| 86 | +### Dual-Level Testing Approach |
| 87 | + |
| 88 | +Always implement **both** high-level integration tests and low-level unit tests: |
| 89 | + |
| 90 | +#### High-Level Integration Tests |
| 91 | +Focus on real-world workflows and end-to-end functionality: |
| 92 | + |
| 93 | +```python |
| 94 | +def test_complete_annotation_workflow(self): |
| 95 | + """Test full annotation workflow with real data.""" |
| 96 | + # Create realistic DataFrame |
| 97 | + df = pd.DataFrame({ |
| 98 | + 'x': [100, 200, 300], |
| 99 | + 'y': [150, 250, 350], |
| 100 | + 'z': [10, 20, 30], |
| 101 | + 'segment_id': [12345, 67890, 11111] |
| 102 | + }) |
| 103 | + |
| 104 | + # Test complete workflow |
| 105 | + vs = ViewerState() |
| 106 | + vs.add_points( |
| 107 | + data=df, |
| 108 | + point_column=['x', 'y', 'z'], |
| 109 | + segment_column='segment_id' |
| 110 | + ) |
| 111 | + |
| 112 | + # Verify end-to-end behavior |
| 113 | + assert len(vs.layers) == 1 |
| 114 | + assert vs.layers[0].layer_type == 'annotation' |
| 115 | +``` |
| 116 | + |
| 117 | +#### Low-Level Unit Tests |
| 118 | +Focus on individual methods, edge cases, and error conditions: |
| 119 | + |
| 120 | +```python |
| 121 | +def test_scale_points_edge_cases(self): |
| 122 | + """Test scaling with edge cases.""" |
| 123 | + point = PointAnnotation(point=[100, 200, 300]) |
| 124 | + |
| 125 | + # Test zero scaling |
| 126 | + point._scale_points([0, 1, 2]) |
| 127 | + assert point.point == [0.0, 200.0, 600.0] |
| 128 | + |
| 129 | + # Test negative scaling |
| 130 | + point = PointAnnotation(point=[100, 200, 300]) |
| 131 | + point._scale_points([-1, -1, -1]) |
| 132 | + assert point.point == [-100.0, -200.0, -300.0] |
| 133 | +``` |
| 134 | + |
| 135 | +### Testing Guidelines |
| 136 | + |
| 137 | +- **Coverage Target**: Aim for >90% line coverage, >85% branch coverage |
| 138 | +- **Test Organization**: Mirror source structure in `tests/` directory |
| 139 | +- **Mocking Strategy**: Mock external dependencies (neuroglancer), test actual behavior for internal logic |
| 140 | +- **Parametrization**: Use `@pytest.mark.parametrize` for testing multiple inputs |
| 141 | +- **Fixtures**: Create reusable test data in `conftest.py` |
| 142 | + |
| 143 | +### Running Tests |
| 144 | + |
| 145 | +```bash |
| 146 | +# Full test suite with coverage |
| 147 | +poe test |
| 148 | + |
| 149 | +# Specific test file |
| 150 | +uv run pytest tests/test_ngl_annotations.py -v |
| 151 | + |
| 152 | +# Coverage report |
| 153 | +uv run pytest --cov=nglui --cov-report=html tests/ |
| 154 | +``` |
| 155 | + |
| 156 | +## Code Architecture Patterns |
| 157 | + |
| 158 | +### Core Components |
| 159 | + |
| 160 | +- **StateBuilder**: Main entry point for creating neuroglancer states |
| 161 | +- **Annotations**: Point, Line, Ellipsoid, BoundingBox with neuroglancer conversion |
| 162 | +- **Components**: ImageLayer, AnnotationLayer, SegmentationLayer |
| 163 | +- **DataMap**: Dynamic data binding with priority system |
| 164 | +- **Utils**: Type conversion, color parsing, coordinate handling |
| 165 | + |
| 166 | +### Key Design Patterns |
| 167 | + |
| 168 | +1. **Attrs Classes**: Use `@attrs.define` for all data classes |
| 169 | +2. **Converter Functions**: `strip_numpy_types`, coordinate transformers |
| 170 | +3. **Method Chaining**: Fluent API for building complex states |
| 171 | +4. **DataMap Priority**: Higher numbers override lower numbers for dynamic updates |
| 172 | +5. **Column Handling**: Support both string columns and list column specifications |
| 173 | + |
| 174 | +### Point Column Feature |
| 175 | + |
| 176 | +Support flexible coordinate specification: |
| 177 | + |
| 178 | +```python |
| 179 | +# Explicit column list |
| 180 | +point_column=['x', 'y', 'z'] |
| 181 | + |
| 182 | +# Prefix expansion |
| 183 | +point_column='position' # → ['position_x', 'position_y', 'position_z'] |
| 184 | +``` |
| 185 | + |
| 186 | +## Common Development Workflows |
| 187 | + |
| 188 | +### Adding New Annotation Types |
| 189 | + |
| 190 | +1. Create class inheriting from `AnnotationBase` |
| 191 | +2. Add type hints for all attributes |
| 192 | +3. Implement `_scale_points()` method |
| 193 | +4. Implement `to_neuroglancer()` method |
| 194 | +5. Add comprehensive tests (unit + integration) |
| 195 | +6. Update documentation |
| 196 | + |
| 197 | +### Adding New Layer Types |
| 198 | + |
| 199 | +1. Create class inheriting from `LayerBase` |
| 200 | +2. Implement required abstract methods |
| 201 | +3. Add DataMap integration |
| 202 | +4. Create builder methods in StateBuilder |
| 203 | +5. Add comprehensive tests |
| 204 | +6. Update documentation |
| 205 | + |
| 206 | +### Bug Fixes |
| 207 | + |
| 208 | +1. Write failing test first (TDD approach) |
| 209 | +2. Implement minimal fix |
| 210 | +3. Ensure all tests pass |
| 211 | +4. Check type annotations are correct |
| 212 | +5. Run full test suite: `poe test` |
| 213 | + |
| 214 | +## Documentation Standards |
| 215 | + |
| 216 | +- **Docstrings**: Use Google/NumPy style docstrings |
| 217 | +- **Examples**: Include usage examples in docstrings |
| 218 | +- **Type Information**: Document parameter and return types in docstrings |
| 219 | +- **API Documentation**: Use mkdocs with mkdocstrings for auto-generation |
| 220 | + |
| 221 | +```python |
| 222 | +def add_points( |
| 223 | + self, |
| 224 | + data: pd.DataFrame, |
| 225 | + point_column: Union[str, List[str]], |
| 226 | + segment_column: Optional[str] = None |
| 227 | +) -> Self: |
| 228 | + """Add point annotations to the viewer state. |
| 229 | + |
| 230 | + Args: |
| 231 | + data: DataFrame containing point data |
| 232 | + point_column: Column name(s) for coordinates. Can be: |
| 233 | + - Single string for prefix (e.g., 'pos' → ['pos_x', 'pos_y', 'pos_z']) |
| 234 | + - List of column names (e.g., ['x', 'y', 'z']) |
| 235 | + segment_column: Optional column containing segment IDs |
| 236 | + |
| 237 | + Returns: |
| 238 | + Self for method chaining |
| 239 | + |
| 240 | + Examples: |
| 241 | + >>> vs = ViewerState() |
| 242 | + >>> vs.add_points(df, point_column=['x', 'y', 'z']) |
| 243 | + >>> vs.add_points(df, point_column='position') # Uses position_x, position_y, position_z |
| 244 | + """ |
| 245 | +``` |
| 246 | + |
| 247 | +## Pre-Commit Workflow |
| 248 | + |
| 249 | +Before committing changes: |
| 250 | + |
| 251 | +```bash |
| 252 | +# Run full test suite |
| 253 | +poe test |
| 254 | + |
| 255 | +# Run linting |
| 256 | +uv run ruff check src/ tests/ |
| 257 | + |
| 258 | +# Check type annotations |
| 259 | +uv run mypy src/ --ignore-missing-imports # if mypy is configured |
| 260 | + |
| 261 | +# Ensure documentation builds |
| 262 | +poe doc-preview |
| 263 | +``` |
| 264 | + |
| 265 | +## Release Process |
| 266 | + |
| 267 | +```bash |
| 268 | +# Check what will be bumped |
| 269 | +poe drybump patch # or minor/major |
| 270 | + |
| 271 | +# Bump version and create tag |
| 272 | +poe bump patch # or minor/major |
| 273 | +``` |
| 274 | + |
| 275 | +This automatically: |
| 276 | +- Updates version in `pyproject.toml` and `src/nglui/__init__.py` |
| 277 | +- Creates git commit and tag |
| 278 | +- Runs pre/post commit hooks including `uv sync` |
0 commit comments