stroke-viewer-frontend / docs /specs /01-phase-0-repo-bootstrap.md
VibecoderMcSwaggins's picture
docs: document SEALS model selection and scientific rationale
211e2f6
|
raw
history blame
11 kB

phase 0: repo bootstrap

purpose

Set up the foundational project structure with 2025 Python best practices. At the end of this phase, we have a working skeleton that can be installed, linted, type-checked, and tested (even if tests are empty).

deliverables

  • pyproject.toml with uv + hatchling backend
  • src/stroke_deepisles_demo/ package structure
  • tests/ directory with pytest configuration
  • Development tooling: ruff, mypy, pre-commit
  • Basic README.md with clinical disclaimer
  • .gitignore updates if needed

repo structure

stroke-deepisles-demo/
β”œβ”€β”€ pyproject.toml              # Project metadata, deps, tool config
β”œβ”€β”€ uv.lock                     # Locked dependencies (auto-generated)
β”œβ”€β”€ .python-version             # Python version (3.12)
β”œβ”€β”€ README.md                   # Project overview + disclaimer
β”œβ”€β”€ .gitignore                  # Standard Python ignores
β”œβ”€β”€ .pre-commit-config.yaml     # Pre-commit hooks
β”‚
β”œβ”€β”€ src/
β”‚   └── stroke_deepisles_demo/
β”‚       β”œβ”€β”€ __init__.py         # Package version, exports
β”‚       β”œβ”€β”€ py.typed            # PEP 561 marker
β”‚       β”‚
β”‚       β”œβ”€β”€ core/               # Shared utilities
β”‚       β”‚   β”œβ”€β”€ __init__.py
β”‚       β”‚   β”œβ”€β”€ config.py       # Pydantic settings (stub)
β”‚       β”‚   β”œβ”€β”€ types.py        # Shared type definitions (stub)
β”‚       β”‚   └── exceptions.py   # Custom exceptions (stub)
β”‚       β”‚
β”‚       β”œβ”€β”€ data/               # Data loading (stub)
β”‚       β”‚   └── __init__.py
β”‚       β”‚
β”‚       β”œβ”€β”€ inference/          # DeepISLES integration (stub)
β”‚       β”‚   └── __init__.py
β”‚       β”‚
β”‚       └── ui/                 # Gradio app (stub)
β”‚           └── __init__.py
β”‚
β”œβ”€β”€ tests/
β”‚   β”œβ”€β”€ __init__.py
β”‚   β”œβ”€β”€ conftest.py             # Shared fixtures
β”‚   └── test_package.py         # Smoke test: package imports
β”‚
└── docs/
    └── specs/                  # These spec documents
        β”œβ”€β”€ 00-context.md
        β”œβ”€β”€ 01-phase-0-repo-bootstrap.md
        └── ...

pyproject.toml specification

[project]
name = "stroke-deepisles-demo"
version = "0.1.0"
description = "Demo: HF datasets + DeepISLES stroke segmentation + Gradio visualization"
readme = "README.md"
license = { text = "MIT" }
requires-python = ">=3.11"
authors = [
    { name = "Your Name", email = "you@example.com" }
]
classifiers = [
    "Development Status :: 3 - Alpha",
    "Intended Audience :: Science/Research",
    "License :: OSI Approved :: MIT License",
    "Programming Language :: Python :: 3.11",
    "Programming Language :: Python :: 3.12",
    "Topic :: Scientific/Engineering :: Medical Science Apps.",
]
keywords = ["stroke", "neuroimaging", "segmentation", "BIDS", "NIfTI", "deep-learning"]

dependencies = [
    # Core - pinned to Tobias's fork for BIDS + NIfTI lazy loading
    "datasets @ git+https://github.com/CloseChoice/datasets.git@feat/bids-loader-streaming-upload-fix",
    "huggingface-hub>=0.25.0",

    # NIfTI handling
    "nibabel>=5.2.0",
    "numpy>=1.26.0",

    # Configuration
    "pydantic>=2.5.0",
    "pydantic-settings>=2.1.0",

    # UI (Gradio 5.x)
    "gradio>=5.0.0",
]

[dependency-groups]
dev = [
    "pytest>=8.0.0",
    "pytest-cov>=4.1.0",
    "pytest-mock>=3.12.0",
    "mypy>=1.8.0",
    "ruff>=0.8.0",
    "pre-commit>=3.6.0",
    # Type stubs
    "types-requests",
]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.build.targets.wheel]
packages = ["src/stroke_deepisles_demo"]

[tool.uv]
dev-dependencies = [
    "pytest>=8.0.0",
    "pytest-cov>=4.1.0",
    "pytest-mock>=3.12.0",
    "mypy>=1.8.0",
    "ruff>=0.8.0",
    "pre-commit>=3.6.0",
]

# ─────────────────────────────────────────────────────────────────
# Tool configurations
# ─────────────────────────────────────────────────────────────────

[tool.ruff]
target-version = "py311"
line-length = 100
src = ["src", "tests"]

[tool.ruff.lint]
select = [
    "E",      # pycodestyle errors
    "W",      # pycodestyle warnings
    "F",      # pyflakes
    "I",      # isort
    "B",      # flake8-bugbear
    "C4",     # flake8-comprehensions
    "UP",     # pyupgrade
    "ARG",    # flake8-unused-arguments
    "SIM",    # flake8-simplify
    "TCH",    # flake8-type-checking
    "PTH",    # flake8-use-pathlib
    "RUF",    # ruff-specific
]
ignore = [
    "E501",   # line too long (handled by formatter)
]

[tool.ruff.lint.isort]
known-first-party = ["stroke_deepisles_demo"]

[tool.mypy]
python_version = "3.11"
strict = true
warn_return_any = true
warn_unused_ignores = true
disallow_untyped_defs = true
plugins = ["pydantic.mypy"]

[[tool.mypy.overrides]]
module = [
    "nibabel.*",
    "gradio.*",
    "datasets.*",
    "niivue.*",
]
ignore_missing_imports = true

[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
python_functions = ["test_*"]
addopts = [
    "-v",
    "--tb=short",
    "--strict-markers",
]
markers = [
    "integration: marks tests requiring external resources (Docker, network)",
    "slow: marks tests that take >10s to run",
]
filterwarnings = [
    "ignore::DeprecationWarning",
]

[tool.coverage.run]
source = ["src/stroke_deepisles_demo"]
branch = true

[tool.coverage.report]
exclude_lines = [
    "pragma: no cover",
    "if TYPE_CHECKING:",
    "raise NotImplementedError",
]

module stubs

src/stroke_deepisles_demo/__init__.py

"""stroke-deepisles-demo: HF datasets + DeepISLES + Gradio visualization."""

__version__ = "0.1.0"

__all__ = ["__version__"]

src/stroke_deepisles_demo/core/config.py

"""Application configuration using pydantic-settings."""

from __future__ import annotations

from pydantic_settings import BaseSettings


class Settings(BaseSettings):
    """Application settings loaded from environment variables."""

    # HuggingFace
    hf_dataset_id: str = "YongchengYAO/ISLES24-MR-Lite"
    hf_cache_dir: str | None = None

    # DeepISLES
    deepisles_docker_image: str = "isleschallenge/deepisles"
    deepisles_fast_mode: bool = True  # SEALS-only (ISLES'22 winner, no FLAIR needed)

    # Paths
    temp_dir: str | None = None

    class Config:
        env_prefix = "STROKE_DEMO_"
        env_file = ".env"


settings = Settings()

src/stroke_deepisles_demo/core/types.py

"""Shared type definitions."""

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path
from typing import TypedDict


class CaseFiles(TypedDict):
    """Paths to NIfTI files for a single case."""

    dwi: Path
    adc: Path
    flair: Path | None
    ground_truth: Path | None


@dataclass(frozen=True)
class InferenceResult:
    """Result of running DeepISLES on a case."""

    case_id: str
    input_files: CaseFiles
    prediction_mask: Path
    elapsed_seconds: float

src/stroke_deepisles_demo/core/exceptions.py

"""Custom exceptions for stroke-deepisles-demo."""

from __future__ import annotations


class StrokeDemoError(Exception):
    """Base exception for stroke-deepisles-demo."""


class DataLoadError(StrokeDemoError):
    """Failed to load data from HuggingFace Hub."""


class DockerNotAvailableError(StrokeDemoError):
    """Docker is not installed or not running."""


class DeepISLESError(StrokeDemoError):
    """DeepISLES inference failed."""


class MissingInputError(StrokeDemoError):
    """Required input files are missing."""

pre-commit configuration

.pre-commit-config.yaml

repos:
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.8.0
    hooks:
      - id: ruff
        args: [--fix]
      - id: ruff-format

  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v1.8.0
    hooks:
      - id: mypy
        additional_dependencies:
          - pydantic>=2.5.0
          - pydantic-settings>=2.1.0
        args: [--config-file=pyproject.toml]

  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.5.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
      - id: check-yaml
      - id: check-added-large-files
        args: [--maxkb=1000]

tdd plan

tests to write first

  1. tests/test_package.py - Smoke test that package imports work
"""Smoke tests for package structure."""

from __future__ import annotations


def test_package_imports() -> None:
    """Verify the package can be imported."""
    import stroke_deepisles_demo

    assert stroke_deepisles_demo.__version__ == "0.1.0"


def test_core_modules_import() -> None:
    """Verify core modules can be imported without side effects."""
    from stroke_deepisles_demo.core import config, exceptions, types

    assert config.settings is not None
    assert types.CaseFiles is not None
    assert exceptions.StrokeDemoError is not None


def test_subpackages_exist() -> None:
    """Verify subpackage structure exists."""
    from stroke_deepisles_demo import data, inference, ui

    # These are stubs, just verify they exist
    assert data is not None
    assert inference is not None
    assert ui is not None

what to mock

  • Nothing needed for Phase 0 - these are pure import tests

what to test for real

  • Package imports
  • Module structure
  • Type definitions load correctly
  • Pydantic settings initialize with defaults

"done" criteria

Phase 0 is complete when:

  1. uv sync succeeds and creates virtual environment
  2. uv run pytest passes all smoke tests
  3. uv run ruff check . reports no errors
  4. uv run ruff format --check . reports no changes needed
  5. uv run mypy src/ passes with no errors
  6. uv run pre-commit run --all-files passes
  7. Package can be imported: uv run python -c "import stroke_deepisles_demo"

commands cheatsheet

# Initialize (if starting fresh)
uv init --package stroke-deepisles-demo

# Install dependencies
uv sync

# Run tests
uv run pytest

# Run tests with coverage
uv run pytest --cov

# Lint
uv run ruff check .

# Format
uv run ruff format .

# Type check
uv run mypy src/

# Install pre-commit hooks
uv run pre-commit install

# Run all pre-commit hooks
uv run pre-commit run --all-files

notes

  • We use hatchling as the build backend (current uv default, stable)
  • uv_build is newer but hatchling is battle-tested
  • The datasets dependency is pinned to Tobias's fork via git URL
  • Gradio 5.x for latest features (SSR, improved components)
  • Python 3.11+ for modern typing features (X | None syntax)