FastAPI service that serves three FastAI image-classification learners for
X-ray cephalometric analysis. The entire backend lives in a single file
(main.py). There is no requirements.txt or pyproject.toml; all
dependencies are declared in the Dockerfile.
main.py # All application code
models/
xtype-simple_resnet18_fp16_01 # X-ray type classifier
lateral_fliprot_resnet18_fp16_07 # Lateral ceph flip/rotation classifier
frontal_fliprot_resnet18_fp16_03 # Frontal ceph flip/rotation classifier
static/
pako.min.js # Vendored – do not modify
UTIF.js # Vendored – do not modify
BFD9020.html # Browser endpoint tester (also served at /test)
Dockerfile # Multi-stage; python:3.13-slim base
docker-compose.yml
.github/workflows/publish-ghcr.yml
Three FastAI exported learners are loaded at startup:
| Key | Path | Purpose |
|---|---|---|
xray |
models/xtype-simple_resnet18_fp16_01 |
Coarse X-ray type classification |
lateral |
models/lateral_fliprot_resnet18_fp16_07 |
Flip/rotation inference for lateral ceph |
frontal |
models/frontal_fliprot_resnet18_fp16_03 |
Flip/rotation inference for frontal ceph |
All three are ResNet-18 models trained with FP16 and exported via fastai's
export(). They are committed as binary blobs (not Python packages). The
label_func stub in main.py exists solely to satisfy pickle deserialization.
# Directly with uvicorn (models must be present locally)
uvicorn main:app --host 0.0.0.0 --port 9020 --reload
# Or via the __main__ guard
python main.py
# Via Docker (recommended for CI/production)
docker build -t bfd9020:test .
docker run --rm -p 9020:9020 bfd9020:test
# Via Compose
docker compose up --buildThere is no pip install -r requirements.txt. Dependencies are managed
exclusively through the Dockerfile. To add a dependency:
- Add
pip install --no-cache-dir <pkg>to thebasestage inDockerfile. - Import the package in
main.pyas needed. - Document the addition in
CHANGES.md.
Current runtime dependencies (from Dockerfile):
torch,torchvision,torchaudio(CPU wheels)fastai<2.8scikit-imageimageiofastapi[standard](includesuvicorn,python-multipart, etc.)numpy(pulled in transitively)
There is no enforced linter or formatter configuration in the repository. Follow these conventions to match the existing code style:
- Use
flake8orrufflocally if desired; no CI lint step exists yet. - Line length: keep under ~100 characters where practical.
- Two blank lines between top-level definitions (PEP 8).
- One blank line between logically distinct blocks inside a function.
- Use
f-stringformatting for log messages only when the string is not passed tologger.*as a format argument; prefer%-style withloggercalls (e.g.,logger.info("foo %s", bar)) to avoid eager evaluation.
There is no automated test suite at this time. Manual testing is done via:
# Browser tester (requires running API)
open http://localhost:9020/test
# Health probe
curl http://localhost:9020/healthz
# Classify a single image with curl
curl -X POST http://localhost:9020/xray-class \
-F "image=@/path/to/image.jpg"
curl -X POST http://localhost:9020/lateral-fliprot \
-F "image=@/path/to/lateral.tiff"
curl -X POST http://localhost:9020/frontal-fliprot \
-F "image=@/path/to/frontal.png"
curl -X POST http://localhost:9020/xray-info \
-F "image=@/path/to/image.jpg"When adding tests in the future, use pytest with httpx and
fastapi.testclient.TestClient. A single test can be run with:
pytest tests/test_main.py::test_function_name -v- Standard library imports first, then third-party, then local — each group separated by a blank line (PEP 8 / isort convention).
- No wildcard imports (
from module import *). - Prefer specific imports over blanket namespace imports where practical
(e.g.,
from fastai.vision.all import load_learner, PILImage).
- All public functions and async endpoints must have full type annotations on parameters and return types.
- Use
str,int,float,bool,dict,list(built-in generics, Python 3.10+ style) rather thantyping.Dict,typing.List, etc. UploadFileparameters use FastAPI'sFile(...)default.
- Functions and variables:
snake_case. - Constants:
UPPER_SNAKE_CASE(e.g.,MAX_IMAGE_SIZE,ALLOWED_MIME_TYPES). - FastAPI endpoint functions: descriptive verb-noun form
(e.g.,
classify_xray,get_xray_info). - Model dictionary keys: short lowercase strings (
"xray","lateral","frontal").
- All FastAPI endpoint handlers must be
async def. - CPU-bound FastAI inference calls (
Learner.predict) must be wrapped inrun_in_threadpoolto avoid blocking the event loop. - Helper functions called only from async endpoints should also be
async def. Pure utility/mapping functions with no I/O may be synchronous.
- Raise
HTTPExceptionfor all user-visible errors with a cleardetailstring and an appropriate HTTP status code. - Re-raise caught
HTTPExceptioninstances directly (raise he). - Catch broad
Exceptiononly at endpoint boundaries; log withlogger.exception(...)(captures traceback) then raise a generic 500. - Never swallow exceptions silently.
- Use the module-level
logger = logging.getLogger(__name__). - Entry into every public endpoint:
logger.info("<endpoint> endpoint called."). - Preprocessing steps:
logger.debug(...). - Successful predictions:
logger.info(...)with class and probability. - Unexpected model keys / unrecognized classes:
logger.warning(...). - Errors:
logger.error(...)for expected failure paths;logger.exception(...)(includes traceback) for unexpected exceptions.
- Every public function and async endpoint must have a Google-style or
reStructuredText-style docstring describing purpose,
Args,Returns, andRaiseswhere applicable. - Inline comments should explain why, not restate what the code does.
| Variable | Default | Description |
|---|---|---|
LOG_LEVEL |
INFO |
Python logging level (DEBUG/INFO/WARNING/ERROR/CRITICAL) |
ROOT_PATH |
"" |
FastAPI root path for reverse-proxy deployments |
ENABLE_DOCS |
false |
Set true to expose /docs, /redoc, /openapi.json |
Follow git-flow:
git flow release start vX.Y.Z
# bump changelog, verify endpoints
git flow release finish vX.Y.Z
git push origin main develop --tagsPushing an annotated v* tag triggers .github/workflows/publish-ghcr.yml,
which builds and pushes the Docker image to GHCR with latest, semver, and
SHA tags.
- No test suite yet — validate changes manually via
/testandcurl. - Models are binary blobs — do not edit files under
models/; replace them wholesale when retraining. label_funcstub — must remain inmain.pyand registered onsys.modules["__main__"]or FastAI pickle deserialization will fail.- File pointer reset — after reading an
UploadFilefor size validation, callimage.seek(0)before any subsequent read or preprocessing. - CORS is wide open — intentional for current deployment context; revisit before any public-facing exposure.
- Docs disabled by default — set
ENABLE_DOCS=trueonly in development or trusted internal environments.