Skip to content

Commit 87a2dd6

Browse files
authored
Add REST API for image segmentation (#505)
* Add REST API for image segmentation Add a FastAPI-based REST API module (samgeo/api.py) that exposes endpoints for automatic, prompt-based, and text-based segmentation using SAM, SAM2, and SAM3 models. Includes model caching, multiple output formats (GeoJSON, GeoTIFF, PNG), and a console script entry point (samgeo-api). Also adds an 'api' optional dependency group and tests. * Fix max_size=0 being treated as a size filter When max_size=0 is sent via the API (e.g., from Swagger UI defaults), it filtered out all masks since every mask has size > 0. Normalize max_size <= 0 to None (no limit) in all segmentation endpoints. * Cache image encoding to skip redundant set_image calls Compute a SHA-256 hash of uploaded file bytes and track the last image encoded per model. When the same image is sent again (e.g., trying different prompts or parameters), the expensive image encoder forward pass is skipped entirely. * Add logging for model and image cache hits Log messages indicate whether a model was loaded fresh or served from cache, and whether image encoding was performed or skipped due to a cache hit. * Fix cache log messages not appearing in uvicorn output Use uvicorn.error logger instead of module-level logger so cache hit/miss messages are visible in the server console output. * Log inference time for each segmentation request Show elapsed time in seconds for automatic, prompt-based, and text segmentation endpoints. For text segmentation, the prompt is also included in the log line. * Address Copilot review comments - Validate model_id against _AVAILABLE_MODELS early (400 error) - Stream uploads to disk in chunks to reduce memory pressure - Validate output_format before running segmentation - Clean up temp directories on error and after JSON responses - Fix pip install hint to use correct extras (samgeo/samgeo2/samgeo3) - Add global lock around model cache to prevent duplicate loading - Validate --preload CLI format with friendly error message - Move importorskip before samgeo.api import in tests - Use pytest tmp_path fixture for automatic temp cleanup - Fix test name/docstring mismatch (GeoTIFF -> PNG) - Make invalid output_format test deterministic (assert 400) - Add test for invalid model_id validation * Fix image cache hit failing when source path is stale When set_image() is skipped due to cache hit, model.source still references the previous request's temp file (now cleaned up). Update model.source to the current request's file path even when skipping the encoding step. * Add REST API documentation and update feature lists - Add docs/api.md with full API documentation (endpoints, caching, examples) - Add REST API entry to mkdocs.yml navigation and API Reference - Add REST API feature bullet to README.md and docs/index.md - Add segment-geospatial[api] extra to install lists
1 parent a1bc37b commit 87a2dd6

7 files changed

Lines changed: 1016 additions & 1 deletion

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ The **SamGeo** package draws its inspiration from [segment-anything-eo](https://
3838
- Save input prompts as GeoJSON files
3939
- Visualize segmentation results on interactive maps
4040
- Segment objects from timeseries remote sensing imagery
41+
- REST API for serving segmentation over HTTP (see [API docs](https://samgeo.gishub.org/api))
4142

4243
## QGIS Plugin
4344

@@ -86,6 +87,7 @@ Depending on what tools you need to use, you might want to do:
8687
- `segment-geospatial[text]`: Installs Grounding DINO to use SAMGeo 1 and 2 with text prompts
8788
- `segment-geospatial[fer]`: Installs the dependencies to run the feature
8889
edge reconstruction algorithm
90+
- `segment-geospatial[api]`: Installs FastAPI and Uvicorn for serving segmentation as a REST API
8991

9092
Additionally, these other two optional imports are defined:
9193

docs/api.md

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
# REST API
2+
3+
segment-geospatial includes a built-in REST API powered by [FastAPI](https://fastapi.tiangolo.com/) that allows you to run image segmentation over HTTP. This is useful for integrating segmentation into web applications, pipelines, and non-Python clients.
4+
5+
## Installation
6+
7+
Install the API dependencies with the `api` extra:
8+
9+
```bash
10+
pip install "segment-geospatial[api]"
11+
```
12+
13+
To also install a specific SAM model backend, combine extras:
14+
15+
```bash
16+
pip install "segment-geospatial[api,samgeo3]"
17+
```
18+
19+
## Starting the Server
20+
21+
Use the `samgeo-api` command:
22+
23+
```bash
24+
samgeo-api
25+
```
26+
27+
Options:
28+
29+
```bash
30+
samgeo-api --host 0.0.0.0 --port 8000 # Custom host/port
31+
samgeo-api --preload sam2:sam2-hiera-large # Preload a model at startup
32+
samgeo-api --reload # Auto-reload for development
33+
```
34+
35+
Alternatively, use `uvicorn` directly:
36+
37+
```bash
38+
uvicorn samgeo.api:app --host 0.0.0.0 --port 8000
39+
```
40+
41+
Once running, interactive API docs (Swagger UI) are available at [http://localhost:8000/docs](http://localhost:8000/docs).
42+
43+
## Endpoints
44+
45+
### Health Check
46+
47+
```
48+
GET /health
49+
```
50+
51+
Returns the server status and version.
52+
53+
```bash
54+
curl http://localhost:8000/health
55+
```
56+
57+
```json
58+
{"status": "ok", "version": "1.2.3"}
59+
```
60+
61+
### List Models
62+
63+
```
64+
GET /models
65+
```
66+
67+
Returns available model versions/IDs and which models are currently loaded in memory.
68+
69+
```bash
70+
curl http://localhost:8000/models
71+
```
72+
73+
### Clear Models
74+
75+
```
76+
DELETE /models
77+
```
78+
79+
Clears the model cache and frees GPU memory.
80+
81+
```bash
82+
curl -X DELETE http://localhost:8000/models
83+
```
84+
85+
### Automatic Segmentation
86+
87+
```
88+
POST /segment/automatic
89+
```
90+
91+
Runs automatic mask generation on an uploaded image. Supports SAM, SAM2, and SAM3.
92+
93+
**Parameters (multipart form):**
94+
95+
| Parameter | Type | Default | Description |
96+
|-----------|------|---------|-------------|
97+
| `file` | file | required | Image file (TIFF, PNG, JPEG) |
98+
| `model_version` | string | `sam2` | One of `sam`, `sam2`, `sam3` |
99+
| `model_id` | string | auto | Model identifier (e.g., `sam2-hiera-large`) |
100+
| `output_format` | string | `geojson` | One of `geojson`, `geotiff`, `png` |
101+
| `foreground` | bool | `true` | Extract foreground objects only |
102+
| `unique` | bool | `true` | Assign unique ID to each object |
103+
| `min_size` | int | `0` | Minimum mask size in pixels |
104+
| `max_size` | int | none | Maximum mask size in pixels |
105+
| `points_per_side` | int | `32` | Points sampled per side (SAM/SAM2) |
106+
| `pred_iou_thresh` | float | `0.8` | IoU threshold for filtering |
107+
| `stability_score_thresh` | float | `0.95` | Stability score threshold |
108+
109+
**Example:**
110+
111+
```bash
112+
curl -X POST http://localhost:8000/segment/automatic \
113+
-F "file=@image.tif" \
114+
-F "model_version=sam2" \
115+
-F "output_format=geojson"
116+
```
117+
118+
### Prompt-based Segmentation
119+
120+
```
121+
POST /segment/predict
122+
```
123+
124+
Runs segmentation with point or bounding box prompts. Supports SAM and SAM2.
125+
126+
**Parameters (multipart form):**
127+
128+
| Parameter | Type | Default | Description |
129+
|-----------|------|---------|-------------|
130+
| `file` | file | required | Image file (TIFF, PNG, JPEG) |
131+
| `model_version` | string | `sam2` | One of `sam`, `sam2` |
132+
| `model_id` | string | auto | Model identifier |
133+
| `output_format` | string | `geojson` | One of `geojson`, `geotiff`, `png` |
134+
| `point_coords` | string | none | JSON array of `[[x, y], ...]` |
135+
| `point_labels` | string | none | JSON array of `[1, 0, ...]` (1=foreground, 0=background) |
136+
| `boxes` | string | none | JSON array of `[[xmin, ymin, xmax, ymax], ...]` |
137+
| `point_crs` | string | none | CRS string (e.g., `EPSG:4326`) |
138+
| `multimask_output` | bool | `false` | Return multiple masks per prompt |
139+
140+
**Example with point prompts:**
141+
142+
```bash
143+
curl -X POST http://localhost:8000/segment/predict \
144+
-F "file=@image.tif" \
145+
-F "point_coords=[[100, 200]]" \
146+
-F "point_labels=[1]" \
147+
-F "output_format=geojson"
148+
```
149+
150+
**Example with box prompts:**
151+
152+
```bash
153+
curl -X POST http://localhost:8000/segment/predict \
154+
-F "file=@image.tif" \
155+
-F "boxes=[[10, 20, 300, 400]]" \
156+
-F "output_format=geotiff"
157+
```
158+
159+
### Text-prompt Segmentation
160+
161+
```
162+
POST /segment/text
163+
```
164+
165+
Runs text-prompt segmentation using SAM3.
166+
167+
**Parameters (multipart form):**
168+
169+
| Parameter | Type | Default | Description |
170+
|-----------|------|---------|-------------|
171+
| `file` | file | required | Image file (TIFF, PNG, JPEG) |
172+
| `prompt` | string | required | Text description (e.g., `building`, `tree`) |
173+
| `model_id` | string | auto | SAM3 model identifier |
174+
| `backend` | string | `meta` | One of `meta`, `transformers` |
175+
| `output_format` | string | `geojson` | One of `geojson`, `geotiff`, `png` |
176+
| `confidence_threshold` | float | `0.5` | Detection confidence threshold |
177+
| `min_size` | int | `0` | Minimum mask size in pixels |
178+
| `max_size` | int | none | Maximum mask size in pixels |
179+
180+
**Example:**
181+
182+
```bash
183+
curl -X POST http://localhost:8000/segment/text \
184+
-F "file=@image.tif" \
185+
-F "prompt=building" \
186+
-F "output_format=geojson"
187+
```
188+
189+
## Caching
190+
191+
The API automatically caches models and image encodings for better performance:
192+
193+
- **Model cache**: Models are loaded once and reused across requests. Use `DELETE /models` to free GPU memory.
194+
- **Image cache**: When the same image is sent multiple times (e.g., with different prompts), the expensive image encoding step is skipped. This makes subsequent requests significantly faster.
195+
196+
Example timing with a 13 MB GeoTIFF:
197+
198+
| Request | Description | Time |
199+
|---------|-------------|------|
200+
| 1st | Model load + image encoding | ~7s |
201+
| 2nd | Same image, different prompt | ~0.4s |
202+
| 3rd | Same image, another prompt | ~0.2s |
203+
204+
## Python Client Example
205+
206+
```python
207+
import requests
208+
209+
url = "http://localhost:8000/segment/text"
210+
211+
with open("image.tif", "rb") as f:
212+
response = requests.post(
213+
url,
214+
files={"file": ("image.tif", f, "image/tiff")},
215+
data={"prompt": "building", "output_format": "geojson"},
216+
)
217+
218+
geojson = response.json()
219+
print(f"Found {len(geojson['features'])} features")
220+
```
221+
222+
## API Reference
223+
224+
::: samgeo.api

docs/index.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ The **SamGeo** package draws its inspiration from [segment-anything-eo](https://
3838
- Save input prompts as GeoJSON files
3939
- Visualize segmentation results on interactive maps
4040
- Segment objects from timeseries remote sensing imagery
41+
- REST API for serving segmentation over HTTP (see [API docs](https://samgeo.gishub.org/api))
4142

4243
## QGIS Plugin
4344

@@ -61,6 +62,7 @@ Depending on what tools you need to use, you might want to do:
6162
- `segment-geospatial[text]`: Installs Grounding DINO to use SAMGeo 1 and 2 with text prompts
6263
- `segment-geospatial[fer]`: Installs the dependencies to run the feature
6364
edge reconstruction algorithm
65+
- `segment-geospatial[api]`: Installs FastAPI and Uvicorn for serving segmentation as a REST API
6466

6567
Additionally, these other two optional imports are defined:
6668

mkdocs.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ nav:
4444
- Home: index.md
4545
- Installation: installation.md
4646
- Usage: usage.md
47+
- REST API: api.md
4748
- Contributing: contributing.md
4849
- FAQ: faq.md
4950
- Changelog: changelog.md
@@ -98,4 +99,5 @@ nav:
9899
- hq_sam module: hq_sam.md
99100
- text_sam module: text_sam.md
100101
- detectree2 module: detectree2.md
102+
- api module: api.md
101103
# - fer module: fer.md

pyproject.toml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,14 @@ text = [
8686
"segment_geospatial[samgeo2]",
8787
"groundingdino-py",
8888
]
89+
api = [
90+
"segment_geospatial[core]",
91+
"fastapi>=0.100.0",
92+
"uvicorn[standard]>=0.20.0",
93+
"python-multipart>=0.0.6",
94+
]
8995
all = [
90-
"segment-geospatial[fast, hq, fer, samgeo, samgeo2, samgeo3, text]",
96+
"segment-geospatial[fast, hq, fer, samgeo, samgeo2, samgeo3, text, api]",
9197
]
9298
extra = [
9399
"leafmap",
@@ -133,5 +139,8 @@ max-line-length = 88
133139

134140
[tool.setuptools_scm]
135141

142+
[project.scripts]
143+
samgeo-api = "samgeo.api:main"
144+
136145
[project.urls]
137146
Homepage = "https://github.com/opengeos/segment-geospatial"

0 commit comments

Comments
 (0)