-
Notifications
You must be signed in to change notification settings - Fork 2
FastAPI for Landsat vessel detection #34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 10 commits
5a163ca
7877e59
c400985
8f05ca3
7b53fdb
2bfc38e
19421b5
02ab349
8c2136b
f7081e4
b64ab97
cbdeec9
9df535d
a1b6d1e
8691f53
1d7d4d6
164e895
0babbbe
12d6a5b
67f29b5
3f546bc
2fd1847
5a1149c
3a9cad1
ee16650
dd674b9
ec987af
d505f18
c0396a3
e2790b7
eb0b5a5
850429c
b1cea89
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,8 @@ | ||
beaker-py | ||
python-dotenv | ||
pytest | ||
uvicorn | ||
fastapi | ||
pydantic | ||
typing-extensions | ||
ruff |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
# Base image | ||
FROM base-image:latest | ||
|
||
# Environment variables | ||
ENV PYTHONPATH="/opt/rslearn_projects:${PYTHONPATH}" | ||
ENV LANDSAT_PORT=5555 | ||
|
||
# Make port 5555 available to the world outside this container | ||
EXPOSE $LANDSAT_PORT | ||
|
||
# Run app.py when the container launches | ||
CMD ["python3", "rslp/landsat_vessels/api_main.py"] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
"""Landsat Vessel Detection Service.""" | ||
|
||
from __future__ import annotations | ||
|
||
import logging | ||
import multiprocessing | ||
import os | ||
|
||
import uvicorn | ||
from fastapi import FastAPI, Response | ||
from pydantic import BaseModel | ||
from typing_extensions import TypedDict | ||
|
||
from rslp.landsat_vessels import predict_pipeline | ||
|
||
app = FastAPI() | ||
|
||
# Set up the logger | ||
logging.basicConfig(level=logging.INFO) | ||
logger = logging.getLogger(__name__) | ||
|
||
LANDSAT_HOST = "0.0.0.0" | ||
LANDSAT_PORT = 5555 | ||
|
||
|
||
class FormattedPrediction(TypedDict): | ||
"""Formatted prediction for a single vessel detection.""" | ||
|
||
latitude: float | ||
longitude: float | ||
score: float | ||
rgb_fname: str | ||
b8_fname: str | ||
|
||
|
||
class LandsatResponse(BaseModel): | ||
"""Response object for vessel detections.""" | ||
|
||
status: list[str] | ||
predictions: list[FormattedPrediction] | ||
|
||
|
||
class LandsatRequest(BaseModel): | ||
"""Request object for vessel detections.""" | ||
|
||
scene_id: str | None = None | ||
image_files: dict[str, str] | None = None | ||
crop_path: str | ||
|
||
scratch_path: str | ||
|
||
json_path: str | ||
|
||
|
||
|
||
@app.on_event("startup") | ||
async def rslp_init() -> None: | ||
"""Landsat Vessel Service Initialization.""" | ||
logger.info("Initializing") | ||
multiprocessing.set_start_method("forkserver", force=True) | ||
multiprocessing.set_forkserver_preload( | ||
[ | ||
"rslp.utils.rslearn.materialize_dataset", | ||
"rslp.utils.rslearn.run_model_predict", | ||
] | ||
) | ||
|
||
|
||
@app.get("/") | ||
async def home() -> dict: | ||
"""Returns a simple message to indicate the service is running.""" | ||
return {"message": "Landsat Detections App"} | ||
|
||
|
||
@app.post("/detections", response_model=LandsatResponse) | ||
async def get_detections(info: LandsatRequest, response: Response) -> LandsatResponse: | ||
"""Returns vessel detections Response object for a given Request object.""" | ||
try: | ||
logger.info(f"Received request with scene_id: {info.scene_id}") | ||
json_data = predict_pipeline( | ||
crop_path=info.crop_path, | ||
scene_id=info.scene_id, | ||
image_files=info.image_files, | ||
scratch_path=info.scratch_path, | ||
json_path=info.json_path, | ||
) | ||
return LandsatResponse(status=["success"], predictions=json_data) | ||
except ValueError as e: | ||
logger.error(f"Value error during prediction pipeline: {e}") | ||
return LandsatResponse(status=["error"], predictions=[]) | ||
except Exception as e: | ||
logger.error(f"Unexpected error during prediction pipeline: {e}") | ||
return LandsatResponse(status=["error"], predictions=[]) | ||
|
||
|
||
if __name__ == "__main__": | ||
uvicorn.run( | ||
"api_main:app", | ||
host=os.getenv("LANDSAT_HOST", default="0.0.0.0"), | ||
port=int(os.getenv("LANDSAT_PORT", default=5555)), | ||
proxy_headers=True, | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
"""Use this script to inference the API with locally stored data.""" | ||
favyen2 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
||
import json | ||
import os | ||
|
||
import requests | ||
|
||
PORT = os.getenv("LANDSAT_PORT", default=5555) | ||
LANDSAT_ENDPOINT = f"http://localhost:{PORT}/detections" | ||
TIMEOUT_SECONDS = 60000 | ||
SCENE_ID = "LC09_L1GT_106084_20241002_20241002_02_T2" | ||
CROP_PATH = "/home/yawenz/rslearn_projects/rslp/landsat_vessels/temp_crops" | ||
SCRATCH_PATH = "/home/yawenz/rslearn_projects/rslp/landsat_vessels/temp_scratch" | ||
JSON_PATH = "/home/yawenz/rslearn_projects/rslp/landsat_vessels/vessels.json" | ||
|
||
|
||
def sample_request() -> None: | ||
"""Sample request for files stored locally.""" | ||
REQUEST_BODY = { | ||
"scene_id": SCENE_ID, | ||
"crop_path": CROP_PATH, | ||
"scratch_path": SCRATCH_PATH, | ||
"json_path": JSON_PATH, | ||
"image_files": None, | ||
} | ||
|
||
response = requests.post( | ||
LANDSAT_ENDPOINT, json=REQUEST_BODY, timeout=TIMEOUT_SECONDS | ||
) | ||
output_filename = os.path.join( | ||
os.path.dirname(os.path.realpath(__file__)), "response.json" | ||
) | ||
if response.ok: | ||
with open(output_filename, "w") as outfile: | ||
json.dump(response.json(), outfile) | ||
else: | ||
print(f"Error: {response.status_code} - {response.text}") | ||
|
||
|
||
if __name__ == "__main__": | ||
sample_request() |
Uh oh!
There was an error while loading. Please reload this page.