A lightweight Flask service that computes the most salient (attention-grabbing) point in an image using OpenCV saliency detectors, and returns JSON:
{
"focus": { "x": 1432, "y": 514 },
"width": 1920,
"height": 1080,
"algorithm": "fine_grained"
}It supports remote images (URL), local files (filesystem path), and multipart uploads. Ships with a Dockerfile for containerized deployment.
Deployment note: Designed for private / VPC use.
path=is intentionally supported for internal callers. Resource limits protect workers from oversized inputs; there is no public-internet SSRF IP blocklist.
- Saliency-based focus detection via OpenCV contrib (
fine_graineddefault, optionalspectral_residual) - Mild center bias + peak-based focus (more stable than raw contour-only picking)
- Optional crop box around the focus point
- Supports URL, local path, or multipart file upload
/healthfor liveness checks- Docker-ready with Gunicorn
- Pytest suite + GitHub Actions CI
- Python 3.12+ (3.12 recommended)
- Git
- Docker (optional)
git clone https://your.git.host/flask-saliency-focus.git
cd flask-saliency-focus
python3 -m venv venv
source venv/bin/activate # Windows: .\venv\Scripts\Activate.ps1pip install --upgrade pip
pip install -r requirements.txt # runtime
pip install -r requirements-dev.txt # tests + lintexport FLASK_APP=app.main
export LOG_LEVEL=INFO
flask run --host=0.0.0.0 --port=5000Or directly:
python -m app.maindocker build -t saliency-focus .
docker run --rm -p 5000:5000 saliency-focusWith a local images directory:
docker run --rm -v $(pwd)/images:/data -p 5000:5000 saliency-focus
curl "http://localhost:5000/focus?path=/data/image1.jpg"Health check: GET /health → {"status":"ok"}.
Service metadata: version, endpoints, and supported algorithms.
{ "status": "ok" }| Param | Type | Required? | Description |
|---|---|---|---|
url |
string | one of url / path / file | HTTP(S) URL of an image |
path |
string | one of url / path / file | Filesystem path to a local image |
file |
file | POST multipart only | Uploaded image file |
algorithm |
string | no | fine_grained (default) or spectral_residual |
center_bias |
float | no | 0..1 pull toward image center (default 0.15) |
include_crop_box |
bool | no | If true, include a suggested crop box |
crop_aspect_ratio |
float | no | Crop width/height (e.g. 1.5). Defaults to source aspect |
# Remote image
curl "http://localhost:5000/focus?url=https://example.com/pic.jpg"
# Local file
curl "http://localhost:5000/focus?path=image1.jpg"
# Spectral residual + crop box
curl "http://localhost:5000/focus?path=image1.jpg&algorithm=spectral_residual&include_crop_box=true&crop_aspect_ratio=1.5"
# Upload
curl -F "file=@image1.jpg" "http://localhost:5000/focus?include_crop_box=true"Success (200):
{
"focus": { "x": 1432, "y": 514 },
"width": 1920,
"height": 1080,
"algorithm": "fine_grained",
"saliency_score": 0.9922,
"crop_box": { "x1": 800, "y1": 200, "x2": 1600, "y2": 800 }
}saliency_score(0..1) is the normalized saliency at the focus point — a rough confidence signal.crop_boxis only present wheninclude_crop_box=true.
Error (4xx/5xx):
{ "error": "Could not load image at path: /invalid.jpg", "code": "PATH_NOT_FOUND" }Common codes: MISSING_SOURCE, INVALID_ALGORITHM, INVALID_NUMBER, PATH_NOT_FOUND, IMAGE_TOO_SMALL, IMAGE_TOO_LARGE, URL_FETCH_FAILED, DECODE_FAILED, NOT_FOUND, METHOD_NOT_ALLOWED, INTERNAL_ERROR.
| Variable | Default | Meaning |
|---|---|---|
LOG_LEVEL |
INFO |
Logging level |
MAX_IMAGE_BYTES |
15728640 (15 MB) |
Max download / upload size |
MAX_IMAGE_DIMENSION |
8192 |
Max width or height in pixels |
MIN_IMAGE_DIMENSION |
16 |
Min width or height in pixels |
URL_TIMEOUT_SECONDS |
10 |
HTTP fetch timeout |
DEFAULT_ALGORITHM |
fine_grained |
Default saliency algorithm |
CENTER_BIAS |
0.15 |
Default center bias (0..1) |
INCLUDE_CROP_BOX |
false |
Always include crop box |
CROP_ASPECT_RATIO |
unset | Default crop aspect ratio |
fine_grained— OpenCVStaticSaliencyFineGrained. Detail-sensitive; can latch onto busy textures (murals, lattices).spectral_residual— OpenCVStaticSaliencySpectralResidual. Coarser “blob” attention; often better for interiors.- Both apply mild center bias and prefer a smoothed saliency peak (with contour fallback).
Classical OpenCV saliency is contrast-based, not semantic. For human-like fixation quality on hard property photos, evaluate lighter deep models (e.g. FastSal) separately — DeepGaze-class models need GPU and are not drop-in cheap.
flask-saliency-focus/
├── Dockerfile
├── requirements.txt
├── requirements-dev.txt
├── pyproject.toml
├── .github/workflows/ci.yml
├── docs/QUALITY_AND_TESTING_SPEC.md
├── app/
│ ├── __init__.py # create_app export
│ ├── main.py # Flask factory, routes, error handlers
│ ├── saliency.py # image load + saliency + crop box
│ ├── config.py # env settings
│ └── errors.py # typed AppError hierarchy
└── tests/
├── conftest.py
├── test_api.py
└── test_saliency.py
pytest
pytest --cov=app --cov-report=term-missing
ruff check app tests-
ImportError: cv2 has no attribute saliencyInstall the contrib headless package (not plainopencv-python):pip uninstall -y opencv-python opencv-contrib-python opencv-python-headless opencv-contrib-python-headless pip install -r requirements.txt
-
Timeout downloading remote images Raise
URL_TIMEOUT_SECONDSor check URL reachability from the host/container. -
413 Payload too large Raise
MAX_IMAGE_BYTES/MAX_IMAGE_DIMENSIONif your pipeline needs larger inputs.