pdm run pytest # Run all tests
pdm run pytest tests/file.py::test_name # Run specific test
pdm run cov # Run tests with coverage
pdm run typecheck # Run type checking
pre-commit run --all-files # Run all linters/formatters
pdm run docs # Build documentationFormatting: Do NOT manually fix formatting/linting errors. Write code first, then run pre-commit run --all-files at the end to auto-fix.
Application: ScanTailor Advanced - document scanning post-processor Migration: C++ → Python Status: In progress (UI shell complete, backend not started)
src/app/ - UI dialogs (37 files)
src/core/ - Core engine (205 files)
src/core/filters/ - 6 filter stages (240 files)
src/imageproc/ - Image algorithms (102 files)
src/math/ - Math utilities (24 files)
src/dewarping/ - Page dewarping (22 files)
src/foundation/ - Utilities (43 files)
src/scantailor/ - Main package
src/scantailor/app/ui/ - UI implementations
src/scantailor/resources/ - Qt resources
src/scantailor/translations/
tests/
Do NOT blindly replicate C++ patterns. The C++ code is reference material, not a template.
- Critically evaluate each C++ design - many are workarounds for C++ limitations
- Use Pythonic idioms (iterators, context managers, dataclasses, etc.)
- Simplify where possible - Python has richer built-ins
- Question the need for each class/abstraction
- Bundle related changes in single commits: new Python code + tests + removal of corresponding C++ code
from __future__ import annotationsin every file- Type hints on all functions
- Google-style docstrings with type info (Args must include types, not just param names)
- Pydantic for data models
- JSON for persistence (NOT XML)
- loguru for logging
- Tests for every module
- Self-describing code that reads like English
- Preserve C++ filenames where sensible, use PEP8 variable naming
- Comments explain WHY, not what
- If a comment breaks up a function, extract to a named function instead
- Wrapper classes around OpenCV/NumPy (use directly)
- XML for any data storage
print()statements- Bare
except:clauses - Mutable default arguments
from x import *- Keep deprecated/dead code (rely on git for rollback)
| C++ | Python |
|---|---|
| BinaryImage | np.ndarray (uint8/bool) |
| GrayImage | np.ndarray (uint8) |
| LinearSolver | np.linalg.solve() |
| Morphology | cv2.morphologyEx() |
| GaussBlur | cv2.GaussianBlur() |
| Binarize | cv2.threshold() / skimage.filters |
| Transform | cv2.warpAffine() / cv2.warpPerspective() |
| XmlMarshaller | Pydantic + JSON |
These have no library equivalent:
- X-splines (
src/math/XSpline.cpp) - Cylindrical dewarping (
src/dewarping/) - Text line tracing
- Mokji binarization
- Verbose, descriptive tests
- Cover: normal, edge cases, errors, round-trips
- Test data in
tests/fixtures/ - pytest + pytest-qt
For task tracking: plan/MIGRATION_CHECKLIST.md
For detailed strategy: plan/MIGRATION_PLAN.md
Brief messages grouping related changes. For migration work, one commit should include:
- New Python code + tests
- Removal of corresponding C++ code (if applicable)
Examples:
Migrate binarization algorithmsPort page split filterAdd project JSON schema
CI/pre-commit already enforces: formatting, imports, type hint presence, docstring presence.
Reviewers should focus on what linters can't catch:
- Is this the right abstraction? Question over-engineering
- Is this a C++ pattern that should be more Pythonic?
- Filter classes must be stateless; Settings classes must be thread-safe
- Are we using Python stdlib where C++ had manual implementations?
- Settings with mutable state must use
threading.Lock - Lock should protect minimal critical section only
- Watch for: nested locks (deadlock), lock held during I/O or image processing
- Check dtype handling (uint8, float32, bool)
- Avoid mutating input arrays - make copies when needed
- Memory usage for large images
- Document expected input/output dtypes in docstrings
- Docstring content helpful? (not just present)
- Args descriptions meaningful, not restating parameter names?
- Complex algorithms have WHY comments?
- Magic numbers explained?
- Test names describe scenario AND expected outcome?
- Edge cases covered (empty, boundary, error)?
- Assertions checking the right thing?
- Avoid: no-assertion tests, exact float comparisons, duplicated tests