Project Setup + First Endpoint
Install packages, scaffold the project, and serve your first GET /health response. Each cell is one runnable step.
Install packages
python1!pip install fastapi uvicorn python-multipart pydantic \2 rasterio pillow onnxruntime numpy nest_asyncio pyngrok
Plain-English explanation
In Google Colab this installs every Python library the app needs in one shell command.
Why it matters
Without these libraries, FastAPI cannot serve requests, Rasterio cannot read GeoTIFF, and ONNX Runtime cannot run your model.
Line by line
!pip installThe ! tells Colab to run a shell command instead of Python.fastapi uvicornThe web framework and the ASGI server that actually accepts HTTP requests.python-multipartLets FastAPI accept file uploads (multipart/form-data).rasterio pillowRasterio reads GeoTIFF; Pillow handles JPG/PNG.onnxruntime numpyLoads .onnx models and runs them on CPU.nest_asyncio pyngrokLets Uvicorn run inside a Colab notebook and exposes a public URL.
Common errors
Why is python-multipart required?
Create the folder structure
python1import os2folders = ["app", "app/routers", "app/models", "app/utils", "uploads", "outputs"]3for f in folders:4 os.makedirs(f, exist_ok=True)5print("Folders ready:", folders)
Plain-English explanation
Make sure every directory the app writes to or imports from exists before any other code runs.
Why it matters
FastAPI imports from app/, saves uploads to uploads/, and writes result PNGs to outputs/. A missing folder crashes the server on first request.
Line by line
import osStandard-library module for filesystem operations.folders = [...]Plain list of every directory the project needs.os.makedirs(f, exist_ok=True)Creates the folder; exist_ok=True means 'don't error if it already exists'.
Common errors
What does exist_ok=True do?
Create app/main.py
python1from fastapi import FastAPI2from fastapi.middleware.cors import CORSMiddleware34app = FastAPI(title="CEAMLS Building Damage Assessment API",5 version="1.0.0")67app.add_middleware(8 CORSMiddleware,9 allow_origins=["*"],10 allow_methods=["*"],11 allow_headers=["*"],12)1314@app.get("/")15def root():16 return {"app": "CEAMLS Damage Assessment", "status": "running"}
Plain-English explanation
This is the application object. Every endpoint, router, and middleware attaches to this single app variable.
Why it matters
Without app = FastAPI(...) there is no server to register routes onto and Uvicorn cannot start anything.
Line by line
from fastapi import FastAPIBrings the framework class into scope.app = FastAPI(title=...)Creates the application instance; the title appears in /docs (auto Swagger UI).CORSMiddlewareAllows the browser frontend (served from a different origin) to call this API.@app.get("/")Decorator: registers the function below as a handler for HTTP GET on /.return {"app": ...}FastAPI auto-serializes the dict to JSON.
Common errors
What does the @app.get("/") decorator do?
Create app/schemas.py
python1from pydantic import BaseModel2from typing import List, Optional34class DamageClass(BaseModel):5 label: str6 probability: float78class ClassifyResponse(BaseModel):9 filename: str10 placard: str # GREEN | YELLOW | ORANGE | RED11 top_class: str12 confidence: float13 entropy: float14 classes: List[DamageClass]15 recommendation: str1617class HealthResponse(BaseModel):18 status: str19 model_loaded: bool20 version: str
Plain-English explanation
Pydantic models describe exactly what every request and response looks like. FastAPI uses them to validate input and generate the /docs page.
Why it matters
Schemas prevent the entire class of bugs where the frontend sends the wrong field name or wrong type — Pydantic rejects it with a clear 422 error before your handler runs.
Line by line
class DamageClass(BaseModel)One predicted class with a name and a probability between 0 and 1.classes: List[DamageClass]A list of all four class predictions returned to the browser.placard: strWill be 'GREEN', 'YELLOW', 'ORANGE', or 'RED'.entropy: floatUncertainty score — higher means the model is less sure.
Common errors
Why use Pydantic schemas instead of plain dicts?
Create the health router
python1# app/routers/health.py2from fastapi import APIRouter3from app.schemas import HealthResponse45router = APIRouter(prefix="/health", tags=["health"])67@router.get("", response_model=HealthResponse)8def health():9 return HealthResponse(status="ok", model_loaded=False, version="1.0.0")
Plain-English explanation
Routers are mini-FastAPI apps. The health router groups everything under /health into its own file.
Why it matters
Splitting routers per concern keeps main.py short and makes Week 3 (loading the ONNX model) a one-line change instead of a refactor.
Line by line
APIRouter(prefix="/health")Every route inside this file will live under /health.response_model=HealthResponseFastAPI will coerce the return value into a HealthResponse before sending JSON.model_loaded=FalseWe will flip this to True once Week 3 wires up ModelManager.
Common errors
What is the purpose of APIRouter?
Create the /classify stub
python1# app/routers/classify.py2from fastapi import APIRouter, UploadFile, File, HTTPException3from app.schemas import ClassifyResponse, DamageClass45router = APIRouter(prefix="/classify", tags=["classify"])67@router.post("", response_model=ClassifyResponse)8async def classify(file: UploadFile = File(...)):9 if not file.filename:10 raise HTTPException(400, "No file uploaded")1112 # TODO Week 2: validate, preprocess. Week 3: run model.13 fake = [14 DamageClass(label="no-damage", probability=0.72),15 DamageClass(label="minor-damage", probability=0.18),16 DamageClass(label="major-damage", probability=0.08),17 DamageClass(label="destroyed", probability=0.02),18 ]19 return ClassifyResponse(20 filename=file.filename, placard="GREEN", top_class="no-damage",21 confidence=0.72, entropy=0.83, classes=fake,22 recommendation="Safe to enter — routine inspection only.",23 )
Plain-English explanation
A stub endpoint that accepts a file upload and returns hard-coded values. Lets the frontend integrate immediately while you build the model pipeline.
Why it matters
Frontend and backend work in parallel when the contract (the schema) is locked in early — even before the AI model exists.
Line by line
file: UploadFile = File(...)Declares an uploaded file parameter. The (...) means it is required.raise HTTPException(400, "...")Returns HTTP 400 with a JSON error body.return ClassifyResponse(...)FastAPI serializes the Pydantic model to JSON automatically.
Common errors
Why build a stub before the real model?
Create the /compare router stub
python1# app/routers/compare.py2from fastapi import APIRouter3router = APIRouter(prefix="/compare", tags=["compare"])45@router.get("/models")6def list_models():7 return {"models": ["baseline", "ablation_a", "ablation_b",8 "ablation_c", "ablation_d", "ablation_e"]}
Plain-English explanation
A placeholder endpoint that will eventually return a side-by-side comparison of all six ablation experiments on the same image.
Why it matters
The comparison endpoint is what graders use to see how augmentation, loss function, and backbone choices affect predictions on the same input.
Line by line
prefix="/compare"All routes here live under /compare.@router.get("/models")Combined with the prefix this is GET /compare/models.
What URL does this endpoint serve?
Create the /batch and /visualise routers
python1# app/routers/batch.py2from fastapi import APIRouter, UploadFile, File3from typing import List4router = APIRouter(prefix="/batch", tags=["batch"])56@router.post("")7async def batch_classify(files: List[UploadFile] = File(...)):8 return {"received": [f.filename for f in files], "status": "stub"}910# app/routers/visualise.py11from fastapi import APIRouter12router = APIRouter(prefix="/visualise", tags=["visualise"])1314@router.get("/probabilities")15def probabilities():16 return {"chart": "stub-png-url"}
Plain-English explanation
Two more stub routers so every URL in the final app exists from day one, returning placeholder data until the real implementations land.
Why it matters
Reserving URLs early prevents URL churn later — frontend code that calls /batch will not need to change when the real implementation ships.
Line by line
files: List[UploadFile]Accept many files in a single request — same form field name repeated.[f.filename for f in files]List comprehension that returns just the filenames.
How does FastAPI know to accept multiple files?
Launch the server
python1# Mount routers, then run uvicorn (Colab-friendly)2from app.routers import health, classify, compare, batch, visualise34app.include_router(health.router)5app.include_router(classify.router)6app.include_router(compare.router)7app.include_router(batch.router)8app.include_router(visualise.router)910import nest_asyncio, uvicorn11nest_asyncio.apply()12uvicorn.run(app, host="0.0.0.0", port=8000)
Plain-English explanation
include_router() attaches each router to the main app. Then Uvicorn starts the actual HTTP server.
Why it matters
Until include_router() is called, FastAPI does not know your endpoints exist — visiting /health would return 404.
Line by line
app.include_router(health.router)Adds the /health endpoint to the app.nest_asyncio.apply()Patches the Colab event loop so uvicorn.run() does not crash.uvicorn.run(app, host="0.0.0.0", port=8000)Starts the ASGI server on every interface, port 8000.
Common errors
What does uvicorn do?
Test every endpoint
python1import requests2BASE = "http://localhost:8000"34print(requests.get(f"{BASE}/").json())5print(requests.get(f"{BASE}/health").json())6print(requests.get(f"{BASE}/compare/models").json())78with open("uploads/sample.tif", "rb") as f:9 r = requests.post(f"{BASE}/classify", files={"file": f})10print(r.json())
Plain-English explanation
Smoke-tests every URL you just registered, including the file upload to /classify.
Why it matters
If any of these returns a 404 or 422 you know the issue is in your routing or schema — not the model.
Line by line
requests.get(f"{BASE}/health")Hits GET /health and parses the JSON response.files={"file": f}requests sends the open file as the 'file' form field.
Common errors
If GET /health returns 404, what is most likely wrong?