Skip to content

CFMateo/CloudVisionServe

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CloudVisionServe

Upload an image, choose a model and get its best guess at what the image contains.

CloudVisionServe is a web application for image classification. Someone uploads a picture, chooses one of four pretrained models and receives a predicted category. It might be a tabby cat, a sports car or an espresso. The first version packaged the interface and prediction service separately and deployed them on Google Cloud.

That first version focused on making the complete path work. I came back to it because a working demo left more interesting questions open: what should the service accept from users, and what happens when two requests choose different models? The current version tackles those questions with direct file uploads, model choice isolated to each request and focused tests around the input boundary.

Current status: a local test suite verifies image validation and the V2 model-selection contract without loading model weights. The recorded cloud images predate this hardening work; no current live service or CI run is claimed.

One V2 request, end to end

flowchart LR
    image["JPEG / PNG"] --> ui["Streamlit<br/>upload + model choice"]
    ui -->|"multipart/form-data"| api["Flask + Gunicorn<br/>request boundary"]
    api --> decode["Pillow<br/>verify · load · RGB"]
    decode --> cache["TorchVision model cache<br/>request key"]
    cache --> label["ImageNet<br/>top-1 category"]

    source["source"] -. "historical build path" .-> build["Google Cloud Build"]
    build -.-> artifacts["Artifact Registry"]
    artifacts -.-> ui
    artifacts -.-> api
Loading

The solid line is the inference request. The dotted line is the original delivery path for independently built frontend and backend containers.

What changed after the first deployment

Original behavior Current behavior Engineering consequence
The backend fetched a submitted image URL. The client uploads the bytes as one multipart file. No user-controlled server-side fetch.
Application code logged the submitted JSON payload. Prediction routes no longer log request data. Former image URLs and request bodies are not copied into application logs.
V2 changed one process-wide active model. Every prediction carries its own model key. One caller cannot switch the model selected by another request.
V2 stored one active model in mutable global variables. A small locked cache loads models by name without changing a shared selection. Repeated requests can reuse weights while model choice stays request-scoped.

This is a targeted refactor of the input and model-state boundaries. It does not add authentication, rate limiting or a complete public-deployment security model.

Three serving contracts

Version Model contract Prediction endpoint Role
V1 Fixed resnet152 POST /predict with file Single-model baseline.
V2 resnet152, convnext_base, swin_b or vgg16, selected per request POST /predict with model + file Main multi-model implementation.
V3 One model configured through MODEL_NAME; the route identifier must match it POST /model/<model_id>/predict Experimental routing sketch; no gateway or deployment configuration.

All backends expose GET /hello. V2 also exposes GET /models; its former mutation route remains as a 410 Gone migration response. All four classifiers use the installed TorchVision release's DEFAULT weights, transforms and ImageNet category metadata. I use these models as provided by TorchVision. This project does not train or fine-tune them.

Run V2 locally

Use Python 3.10 or newer. From the repository root:

git clone https://github.com/CFMateo/CloudVisionServe.git
cd CloudVisionServe
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -r main/requirements.txt

Start the API from main/:

cd main
PYTHONPATH=. flask --app backend_v2.app run --port 8000

In a second terminal:

cd CloudVisionServe/main
source ../.venv/bin/activate
SERVING_URL=http://127.0.0.1:8000 \
streamlit run frontend_v2/app.py --server.port 8501 --server.maxUploadSize 6

Open http://localhost:8501. When weights are absent from the local Torch cache, the first prediction downloads them; that cold request can exceed the frontend's 120-second read timeout.

For V1, replace backend_v2.app with backend.app and frontend_v2/app.py with frontend/app.py.

API and input contract

curl http://localhost:8000/models

curl -X POST http://localhost:8000/predict \
  -F "model=swin_b" \
  -F "file=@image.png;type=image/png"

A successful response contains the top upstream category:

{"category": "tabby"}

Invalid uploads return an HTTP 400, 413 or 415 response before inference.

Boundary Enforced contract
Multipart body At most 6 MiB, with an uploaded-file part named file; V2 also requires one text model value.
Image bytes Non-empty and at most 5 MiB.
Encoded format A loadable JPEG or PNG, determined from the decoded file rather than its extension.
Decoded image At most 4,096 pixels per side and 16 million pixels in total; Pillow decompression-bomb warnings are rejected.
Model selection One exact identifier from the V2 allowlist.

Accepted images are verified, reopened, loaded and converted to RGB before inference. JSON requests containing a URL are rejected rather than fetched.

Model lifecycle and concurrency

The backend obtains get_model_weights(name).DEFAULT, applies weights.transforms() and switches the model to evaluation mode before serving predictions.

  • V1 retains one ResNet-152 model.
  • V2 uses a lock only while looking up or loading a model in its per-process cache.
  • V3 retains the single model configured through MODEL_NAME.
  • The V1/V2 containers use one Gunicorn worker and two threads so cached weights are not duplicated between worker processes.
  • No device transfer is configured, so the normal path runs on CPU.
  • Responses contain the top category only, without a confidence score.

This favors predictable state over throughput. Four warmed models have not been profiled against the historical 4 GiB limit, and dependencies plus DEFAULT weight aliases are unpinned. Those facts rule out a reproducible performance claim today.

Verification

From the repository root, the model-free suite needs Flask, Pillow and pytest:

python -m pip install flask pillow pytest
python -m pytest -q

The focused suite contains 12 cases; no CI workflow is configured.

Surface Cases What it exercises
V2 boundary 4 Model listing, retired global switching, missing uploads and cache reuse.
Image boundary 8 JPEG/PNG decoding, RGB normalization, corrupt files, byte limits and decoded dimensions.

The cache test replaces the model loader with a fake. The suite therefore does not exercise real weights, numerical inference, the Streamlit UI, simultaneous mixed-model requests, container builds, memory, latency, throughput or Google Cloud behavior.

Google Cloud deployment record

The first version produced four Google Cloud Build IDs and four Cloud Run URLs: frontend/backend pairs for V1 and V2. Its build configuration creates a shared dependency image and a service image, pushes them to Artifact Registry and deploys public Cloud Run services with 4 GiB and 2 CPUs.

That record is historical. The referenced image tags predate the current input-handling changes, the shared base installs the same large unpinned dependency set for every service, and the first cache pull can fail against an empty registry. The scripts under main/deployment/ also retain legacy project identifiers and should not be treated as a current release path.

Before a new cloud claim, the current commit needs pinned dependencies, a successful backend/frontend container build, immutable image digests, reviewed IAM/ingress/cost settings, a real-weight smoke test, and cold/warm latency plus peak-memory measurements.

About

Classify uploaded images with four pretrained PyTorch/TorchVision models through Flask and Streamlit, packaged with Docker for Google Cloud Run.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors