[feat] handle requests with errors
Browse files- .gitignore +6 -0
- Dockerfile +32 -0
- README.md +25 -2
- requirements.txt +5 -0
- requirements_pip.txt +3 -0
- src/__init__.py +0 -0
- src/main.py +115 -0
- src/prediction_api/__init__.py +0 -0
- src/prediction_api/predictor.py +43 -0
- src/utilities/__init__.py +1 -0
- src/utilities/constants.py +19 -0
- src/utilities/measures.py +76 -0
- src/utilities/serialize.py +86 -0
- src/utilities/type_hints.py +30 -0
- src/utilities/utilities.py +100 -0
- static/index.html +24 -0
- static/script.js +17 -0
- static/style.css +0 -0
.gitignore
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
venv/
|
2 |
+
*.pyc
|
3 |
+
__cache__
|
4 |
+
.idea
|
5 |
+
tmp/
|
6 |
+
.env*
|
Dockerfile
ADDED
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM ghcr.io/osgeo/gdal:ubuntu-small-3.7.2
|
2 |
+
|
3 |
+
WORKDIR /code
|
4 |
+
COPY ./requirements.txt /code/requirements.txt
|
5 |
+
COPY ./requirements_pip.txt /code/requirements_pip.txt
|
6 |
+
|
7 |
+
RUN apt update && apt install -y g++ make cmake unzip libcurl4-openssl-dev python3-pip
|
8 |
+
|
9 |
+
# avoid segment-geospatial exception caused by missing libGL.so.1 library
|
10 |
+
RUN apt install -y libgl1 curl
|
11 |
+
RUN ls -ld /usr/lib/x86_64-linux-gnu/libGL.so* || echo "libGL.so* not found..."
|
12 |
+
|
13 |
+
RUN which python
|
14 |
+
RUN python --version
|
15 |
+
RUN python -m pip install --no-cache-dir --upgrade -r /code/requirements_pip.txt
|
16 |
+
RUN python -m pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu
|
17 |
+
RUN python -m pip install --no-cache-dir -r /code/requirements.txt
|
18 |
+
|
19 |
+
RUN useradd -m -u 1000 user
|
20 |
+
|
21 |
+
USER user
|
22 |
+
|
23 |
+
ENV HOME=/home/user \
|
24 |
+
PATH=/home/user/.local/bin:$PATH
|
25 |
+
|
26 |
+
WORKDIR $HOME/app
|
27 |
+
|
28 |
+
RUN curl -o ${HOME}/sam_vit_h_4b8939.pth https://dl.fbaipublicfiles.com/segment_anything/sam_vit_h_4b8939.pth
|
29 |
+
RUN ls -l ${HOME}/
|
30 |
+
COPY --chown=user . $HOME/app
|
31 |
+
|
32 |
+
CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "7860"]
|
README.md
CHANGED
@@ -1,5 +1,5 @@
|
|
1 |
---
|
2 |
-
title: Segment
|
3 |
emoji: 📉
|
4 |
colorFrom: red
|
5 |
colorTo: blue
|
@@ -7,4 +7,27 @@ sdk: docker
|
|
7 |
pinned: false
|
8 |
---
|
9 |
|
10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
---
|
2 |
+
title: Segment Geospatial
|
3 |
emoji: 📉
|
4 |
colorFrom: red
|
5 |
colorTo: blue
|
|
|
7 |
pinned: false
|
8 |
---
|
9 |
|
10 |
+
Build the docker image:
|
11 |
+
|
12 |
+
```bash
|
13 |
+
# clean any old active containers
|
14 |
+
docker stop $(docker ps -a -q); docker rm $(docker ps -a -q)
|
15 |
+
|
16 |
+
# build the image, use the tag "semgeo"
|
17 |
+
docker build . --tag semgeo --progress=plain
|
18 |
+
```
|
19 |
+
|
20 |
+
Run the container (keep it on background) and show logs
|
21 |
+
|
22 |
+
```bash
|
23 |
+
docker run -d --name semgeo -p 7860:7860 semgeo; docker logs -f semgeo
|
24 |
+
```
|
25 |
+
|
26 |
+
Test it with curl:
|
27 |
+
|
28 |
+
```bash
|
29 |
+
curl -X 'POST' \
|
30 |
+
'http://localhost:7860/infer_samgeo' \
|
31 |
+
-H 'accept: application/json' \
|
32 |
+
-d '{}'
|
33 |
+
```
|
requirements.txt
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
fastapi
|
2 |
+
bson
|
3 |
+
python-dotenv
|
4 |
+
segment-geospatial
|
5 |
+
uvicorn[standard]
|
requirements_pip.txt
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
pip
|
2 |
+
wheel
|
3 |
+
setuptools
|
src/__init__.py
ADDED
File without changes
|
src/main.py
ADDED
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import json
|
2 |
+
from typing import List
|
3 |
+
from fastapi import FastAPI, HTTPException, Request, status
|
4 |
+
from fastapi.exceptions import RequestValidationError
|
5 |
+
from fastapi.responses import FileResponse, JSONResponse
|
6 |
+
from fastapi.staticfiles import StaticFiles
|
7 |
+
from pydantic import BaseModel
|
8 |
+
|
9 |
+
from src.utilities.utilities import setup_logging
|
10 |
+
|
11 |
+
|
12 |
+
app = FastAPI()
|
13 |
+
local_logger = setup_logging()
|
14 |
+
|
15 |
+
|
16 |
+
class Input(BaseModel):
|
17 |
+
name: str
|
18 |
+
bbox: List[float]
|
19 |
+
points_coords: List[List[float]]
|
20 |
+
|
21 |
+
|
22 |
+
@app.post("/post_test")
|
23 |
+
async def post_test(input: Input) -> JSONResponse:
|
24 |
+
bbox = input.bbox
|
25 |
+
name = input.name
|
26 |
+
points_coords = input.points_coords
|
27 |
+
return JSONResponse(
|
28 |
+
status_code=200,
|
29 |
+
content={
|
30 |
+
"msg": name, "bbox": bbox, "points_coords": points_coords
|
31 |
+
}
|
32 |
+
)
|
33 |
+
|
34 |
+
|
35 |
+
@app.get("/hello")
|
36 |
+
async def hello() -> JSONResponse:
|
37 |
+
return JSONResponse(status_code=200, content={"msg": "hello"})
|
38 |
+
|
39 |
+
|
40 |
+
@app.post("/infer_samgeo")
|
41 |
+
def samgeo():
|
42 |
+
import subprocess
|
43 |
+
|
44 |
+
from src.prediction_api.predictor import base_predict
|
45 |
+
|
46 |
+
local_logger = setup_logging()
|
47 |
+
local_logger.info("starting inference request...")
|
48 |
+
|
49 |
+
try:
|
50 |
+
import time
|
51 |
+
|
52 |
+
time_start_run = time.time()
|
53 |
+
# debug = True
|
54 |
+
# local_logger = setup_logging(debug)
|
55 |
+
message = "point_coords_segmentation"
|
56 |
+
bbox = [-122.1497, 37.6311, -122.1203, 37.6458]
|
57 |
+
point_coords = [[-122.1419, 37.6383]]
|
58 |
+
try:
|
59 |
+
output = base_predict(bbox=bbox, point_coords=point_coords)
|
60 |
+
|
61 |
+
duration_run = time.time() - time_start_run
|
62 |
+
body = {
|
63 |
+
"message": message,
|
64 |
+
"duration_run": duration_run,
|
65 |
+
# "request_id": request_id
|
66 |
+
}
|
67 |
+
local_logger.info(f"body:{body}.")
|
68 |
+
body["output"] = output
|
69 |
+
# local_logger.info(f"End_request::{request_id}...")
|
70 |
+
return JSONResponse(status_code=200, content={"body": json.dumps(body)})
|
71 |
+
except Exception as inference_exception:
|
72 |
+
home_content = subprocess.run("ls -l /home/user", shell=True, universal_newlines=True, stdout=subprocess.PIPE)
|
73 |
+
local_logger.error(f"/home/user ls -l: {home_content.stdout}.")
|
74 |
+
local_logger.error(f"inference error:{inference_exception}.")
|
75 |
+
return HTTPException(status_code=500, detail="Internal server error on inference")
|
76 |
+
except Exception as generic_exception:
|
77 |
+
local_logger.error(f"generic error:{generic_exception}.")
|
78 |
+
return HTTPException(status_code=500, detail="Generic internal server error")
|
79 |
+
|
80 |
+
|
81 |
+
@app.exception_handler(RequestValidationError)
|
82 |
+
async def request_validation_exception_handler(request: Request, exc: RequestValidationError) -> JSONResponse:
|
83 |
+
local_logger.error(f"exception errors: {exc.errors()}.")
|
84 |
+
local_logger.error(f"exception body: {exc.body}.")
|
85 |
+
headers = request.headers.items()
|
86 |
+
local_logger.error(f'request header: {dict(headers)}.' )
|
87 |
+
params = request.query_params.items()
|
88 |
+
local_logger.error(f'request query params: {dict(params)}.')
|
89 |
+
return JSONResponse(
|
90 |
+
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
91 |
+
content={"msg": "Error - Unprocessable Entity"}
|
92 |
+
)
|
93 |
+
|
94 |
+
|
95 |
+
@app.exception_handler(HTTPException)
|
96 |
+
async def http_exception_handler(request: Request, exc: HTTPException) -> JSONResponse:
|
97 |
+
local_logger.error(f"exception: {str(exc)}.")
|
98 |
+
headers = request.headers.items()
|
99 |
+
local_logger.error(f'request header: {dict(headers)}.' )
|
100 |
+
params = request.query_params.items()
|
101 |
+
local_logger.error(f'request query params: {dict(params)}.')
|
102 |
+
return JSONResponse(
|
103 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
104 |
+
content={"msg": "Error - Internal Server Error"}
|
105 |
+
)
|
106 |
+
|
107 |
+
|
108 |
+
# important: the index() function and the app.mount MUST be at the end
|
109 |
+
app.mount("/", StaticFiles(directory="static", html=True), name="static")
|
110 |
+
|
111 |
+
|
112 |
+
@app.get("/")
|
113 |
+
def index() -> FileResponse:
|
114 |
+
return FileResponse(path="/app/static/index.html", media_type="text/html")
|
115 |
+
|
src/prediction_api/__init__.py
ADDED
File without changes
|
src/prediction_api/predictor.py
ADDED
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Press the green button in the gutter to run the script.
|
2 |
+
import json
|
3 |
+
|
4 |
+
from src.utilities.constants import ROOT
|
5 |
+
from src.utilities.utilities import setup_logging
|
6 |
+
|
7 |
+
|
8 |
+
local_logger = setup_logging()
|
9 |
+
|
10 |
+
|
11 |
+
def base_predict(bbox, point_coords, point_crs="EPSG:4326", zoom=16, model_name:str="vit_h", root_folder:str=ROOT) -> str:
|
12 |
+
from samgeo import SamGeo, tms_to_geotiff
|
13 |
+
|
14 |
+
image = f"{root_folder}/satellite.tif"
|
15 |
+
local_logger.info("start tms_to_geotiff")
|
16 |
+
# bbox: image input coordinate
|
17 |
+
tms_to_geotiff(output=image, bbox=bbox, zoom=zoom, source="Satellite", overwrite=True)
|
18 |
+
|
19 |
+
local_logger.info(f"geotiff created, start to initialize samgeo instance (read model {model_name} from {root_folder})...")
|
20 |
+
predictor = SamGeo(
|
21 |
+
model_type=model_name,
|
22 |
+
checkpoint_dir=root_folder,
|
23 |
+
automatic=False,
|
24 |
+
sam_kwargs=None,
|
25 |
+
)
|
26 |
+
local_logger.info(f"initialized samgeo instance, start to set_image {image}...")
|
27 |
+
predictor.set_image(image)
|
28 |
+
output_name = f"{root_folder}/output.tif"
|
29 |
+
|
30 |
+
local_logger.info(f"done set_image, start prediction...")
|
31 |
+
predictor.predict(point_coords, point_labels=len(point_coords), point_crs=point_crs, output=output_name)
|
32 |
+
|
33 |
+
local_logger.info(f"done prediction, start tiff to geojson conversion...")
|
34 |
+
|
35 |
+
# geotiff to geojson
|
36 |
+
vector = f"{root_folder}/feats.geojson"
|
37 |
+
predictor.tiff_to_geojson(output_name, vector, bidx=1)
|
38 |
+
local_logger.info(f"start reading geojson...")
|
39 |
+
|
40 |
+
with open(vector) as out_gdf:
|
41 |
+
out_gdf_str = json.load(out_gdf)
|
42 |
+
local_logger.info(f"number of fields in geojson output:{len(out_gdf_str)}.")
|
43 |
+
return out_gdf_str
|
src/utilities/__init__.py
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
"""various helpers utilities"""
|
src/utilities/constants.py
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""Project constants"""
|
2 |
+
CHANNEL_EXAGGERATIONS_LIST = [2.5, 1.1, 2.0]
|
3 |
+
INPUT_CRS_STRING = "EPSG:4326"
|
4 |
+
OUTPUT_CRS_STRING = "EPSG:3857"
|
5 |
+
ROOT = "/home/user"
|
6 |
+
NODATA_VALUES = -32768
|
7 |
+
SKIP_CONDITIONS_LIST = [{"skip_key": "confidence", "skip_value": 0.5, "skip_condition": "major"}]
|
8 |
+
FEATURE_SQUARE_TEMPLATE = [
|
9 |
+
{'type': 'Feature', 'properties': {'id': 1},
|
10 |
+
'geometry': {
|
11 |
+
'type': 'MultiPolygon',
|
12 |
+
'coordinates': [[]]
|
13 |
+
}}
|
14 |
+
]
|
15 |
+
GEOJSON_SQUARE_TEMPLATE = {
|
16 |
+
'type': 'FeatureCollection', 'name': 'etna_wgs84p',
|
17 |
+
'crs': {'type': 'name', 'properties': {'name': 'urn:ogc:def:crs:OGC:1.3:CRS84'}},
|
18 |
+
'features': FEATURE_SQUARE_TEMPLATE
|
19 |
+
}
|
src/utilities/measures.py
ADDED
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""helpers for compute measures: hash, time benchmarks"""
|
2 |
+
from pathlib import Path
|
3 |
+
|
4 |
+
|
5 |
+
def hash_calculate(arr: any, debug: bool = False) -> str or bytes:
|
6 |
+
"""
|
7 |
+
Return computed hash from input variable (typically a numpy array).
|
8 |
+
|
9 |
+
Args:
|
10 |
+
arr: input variable
|
11 |
+
debug: logging debug argument
|
12 |
+
|
13 |
+
Returns:
|
14 |
+
str or bytes: computed hash from input variable
|
15 |
+
|
16 |
+
"""
|
17 |
+
import hashlib
|
18 |
+
import numpy as np
|
19 |
+
from base64 import b64encode
|
20 |
+
|
21 |
+
from src.utilities.utilities import setup_logging
|
22 |
+
local_logger = setup_logging(debug)
|
23 |
+
|
24 |
+
if isinstance(arr, np.ndarray):
|
25 |
+
hash_fn = hashlib.sha256(arr.data)
|
26 |
+
elif isinstance(arr, dict):
|
27 |
+
import json
|
28 |
+
from src.utilities.serialize import serialize
|
29 |
+
|
30 |
+
serialized = serialize(arr)
|
31 |
+
variable_to_hash = json.dumps(serialized, sort_keys=True).encode('utf-8')
|
32 |
+
hash_fn = hashlib.sha256(variable_to_hash)
|
33 |
+
elif isinstance(arr, str):
|
34 |
+
try:
|
35 |
+
hash_fn = hashlib.sha256(arr)
|
36 |
+
except TypeError:
|
37 |
+
local_logger.warning(f"TypeError, re-try encoding arg:{arr},type:{type(arr)}.")
|
38 |
+
hash_fn = hashlib.sha256(arr.encode('utf-8'))
|
39 |
+
elif isinstance(arr, bytes):
|
40 |
+
hash_fn = hashlib.sha256(arr)
|
41 |
+
else:
|
42 |
+
raise ValueError(f"variable 'arr':{arr} not yet handled.")
|
43 |
+
return b64encode(hash_fn.digest())
|
44 |
+
|
45 |
+
|
46 |
+
def sha256sum(filename: Path or str) -> str:
|
47 |
+
"""
|
48 |
+
Return computed hash for input file.
|
49 |
+
|
50 |
+
Args:
|
51 |
+
filename: input variable
|
52 |
+
|
53 |
+
Returns:
|
54 |
+
str: computed hash
|
55 |
+
|
56 |
+
"""
|
57 |
+
import hashlib
|
58 |
+
import mmap
|
59 |
+
|
60 |
+
h = hashlib.sha256()
|
61 |
+
with open(filename, 'rb') as f:
|
62 |
+
with mmap.mmap(f.fileno(), 0, prot=mmap.PROT_READ) as mm:
|
63 |
+
h.update(mm)
|
64 |
+
return h.hexdigest()
|
65 |
+
|
66 |
+
|
67 |
+
def perf_counter() -> float:
|
68 |
+
"""
|
69 |
+
Performance counter for benchmarking.
|
70 |
+
|
71 |
+
Returns:
|
72 |
+
float: computed time value at execution time
|
73 |
+
|
74 |
+
"""
|
75 |
+
import time
|
76 |
+
return time.perf_counter()
|
src/utilities/serialize.py
ADDED
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""Serialize objects"""
|
2 |
+
from typing import Mapping
|
3 |
+
|
4 |
+
from src.utilities.type_hints import ts_dict_str3, ts_dict_str2
|
5 |
+
|
6 |
+
|
7 |
+
def serialize(obj:any, include_none:bool=False) -> object:
|
8 |
+
"""
|
9 |
+
Return the input object into a serializable one
|
10 |
+
|
11 |
+
Args:
|
12 |
+
obj: Object to serialize
|
13 |
+
include_none: bool to indicate if include also keys with None values during dict serialization
|
14 |
+
|
15 |
+
Returns:
|
16 |
+
object: serialized object
|
17 |
+
|
18 |
+
"""
|
19 |
+
return _serialize(obj, include_none)
|
20 |
+
|
21 |
+
|
22 |
+
def _serialize(obj:any, include_none:bool) -> any:
|
23 |
+
import numpy as np
|
24 |
+
|
25 |
+
primitive = (int, float, str, bool)
|
26 |
+
# print(type(obj))
|
27 |
+
try:
|
28 |
+
if obj is None:
|
29 |
+
return None
|
30 |
+
elif isinstance(obj, np.integer):
|
31 |
+
return int(obj)
|
32 |
+
elif isinstance(obj, np.floating):
|
33 |
+
return float(obj)
|
34 |
+
elif isinstance(obj, np.ndarray):
|
35 |
+
return obj.tolist()
|
36 |
+
elif isinstance(obj, primitive):
|
37 |
+
return obj
|
38 |
+
elif type(obj) is list:
|
39 |
+
return _serialize_list(obj, include_none)
|
40 |
+
elif type(obj) is tuple:
|
41 |
+
return list(obj)
|
42 |
+
elif type(obj) is bytes:
|
43 |
+
return _serialize_bytes(obj)
|
44 |
+
elif isinstance(obj, Exception):
|
45 |
+
return _serialize_exception(obj)
|
46 |
+
# elif isinstance(obj, object):
|
47 |
+
# return _serialize_object(obj, include_none)
|
48 |
+
else:
|
49 |
+
return _serialize_object(obj, include_none)
|
50 |
+
except Exception as e_serialize:
|
51 |
+
from src.utilities.utilities import setup_logging
|
52 |
+
serialize_logger = setup_logging()
|
53 |
+
serialize_logger.error(f"e_serialize::{e_serialize}, type_obj:{type(obj)}, obj:{obj}.")
|
54 |
+
return f"object_name:{str(obj)}__object_type_str:{str(type(obj))}."
|
55 |
+
|
56 |
+
|
57 |
+
def _serialize_object(obj:Mapping[any, object], include_none:bool) -> dict[any]:
|
58 |
+
from bson import ObjectId
|
59 |
+
|
60 |
+
res = {}
|
61 |
+
if type(obj) is not dict:
|
62 |
+
keys = [i for i in obj.__dict__.keys() if (getattr(obj, i) is not None) or include_none]
|
63 |
+
else:
|
64 |
+
keys = [i for i in obj.keys() if (obj[i] is not None) or include_none]
|
65 |
+
for key in keys:
|
66 |
+
if type(obj) is not dict:
|
67 |
+
res[key] = _serialize(getattr(obj, key), include_none)
|
68 |
+
elif isinstance(obj[key], ObjectId):
|
69 |
+
continue
|
70 |
+
else:
|
71 |
+
res[key] = _serialize(obj[key], include_none)
|
72 |
+
return res
|
73 |
+
|
74 |
+
|
75 |
+
def _serialize_list(ls:list, include_none:bool) -> list:
|
76 |
+
return [_serialize(elem, include_none) for elem in ls]
|
77 |
+
|
78 |
+
|
79 |
+
def _serialize_bytes(b:bytes) -> ts_dict_str2:
|
80 |
+
import base64
|
81 |
+
encoded = base64.b64encode(b)
|
82 |
+
return {"value": encoded.decode('ascii'), "type": "bytes"}
|
83 |
+
|
84 |
+
|
85 |
+
def _serialize_exception(e: Exception) -> ts_dict_str3:
|
86 |
+
return {"msg": str(e), "type": str(type(e)), **e.__dict__}
|
src/utilities/type_hints.py
ADDED
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""custom type hints"""
|
2 |
+
from typing import List, Dict, Tuple
|
3 |
+
|
4 |
+
import numpy as np
|
5 |
+
|
6 |
+
# ts_ddict1, ts_float64_1, ts_float64_2, ts_dict_str3, ts_dict_str2
|
7 |
+
input_floatlist = List[float]
|
8 |
+
input_floatlist2 = List[input_floatlist]
|
9 |
+
ts_ddict1 = Dict[str, Dict[str, any], Dict, Dict, any]
|
10 |
+
ts_dict_str2 = Dict[str, str]
|
11 |
+
ts_dict_str3 = Dict[str, str, any]
|
12 |
+
ts_float64_1 = Tuple[np.float64, np.float64, np.float64, np.float64, np.float64, np.float64]
|
13 |
+
ts_float64_2 = Tuple[np.float64, np.float64, np.float64, np.float64, np.float64, np.float64, np.float64]
|
14 |
+
|
15 |
+
"""
|
16 |
+
ts_list_str1 = List[str]
|
17 |
+
ts_http2 = Tuple[ts_list_str1, ts_list_str1]
|
18 |
+
ts_list_float2 = List[float, float]
|
19 |
+
ts_llist_float2 = List[ts_list_float2, ts_list_float2]
|
20 |
+
ts_geojson = Dict[str, str, Dict[str, Dict[str]], List[str, Dict[int], Dict[str, List]]]
|
21 |
+
ts_dict_str2b = Dict[str, any]
|
22 |
+
ts_ddict2 = Dict[str, Dict, Dict[str, List]]
|
23 |
+
ts_tuple_str2 = Tuple[str, str]
|
24 |
+
ts_tuple_arr2 = Tuple[np.ndarray, np.ndarray]
|
25 |
+
ts_tuple_flat2 = Tuple[float, float]
|
26 |
+
ts_tuple_flat4 = Tuple[float, float, float, float]
|
27 |
+
ts_list_float4 = List[float, float, float, float]
|
28 |
+
ts_tuple_int4 = Tuple[int, int, int, int]
|
29 |
+
ts_ddict3 = Dict[List[Dict[float | int | str]], Dict[float | int]]
|
30 |
+
"""
|
src/utilities/utilities.py
ADDED
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""Various utilities (logger, time benchmark, args dump, numerical and stats info)"""
|
2 |
+
import logging
|
3 |
+
|
4 |
+
from src.utilities.constants import ROOT
|
5 |
+
|
6 |
+
|
7 |
+
def setup_logging(debug: bool = False, formatter: str = '%(asctime)s - %(name)s - %(funcName)s(): line %(lineno)d - %(levelname)s - %(message)s') -> logging.Logger:
|
8 |
+
"""
|
9 |
+
Create a logging instance with log string formatter.
|
10 |
+
|
11 |
+
Args:
|
12 |
+
debug: logging debug argument
|
13 |
+
formatter: log string formatter
|
14 |
+
|
15 |
+
Returns:
|
16 |
+
Logger
|
17 |
+
|
18 |
+
"""
|
19 |
+
import logging
|
20 |
+
import sys
|
21 |
+
|
22 |
+
logger = logging.getLogger()
|
23 |
+
for h in logger.handlers:
|
24 |
+
logger.removeHandler(h)
|
25 |
+
|
26 |
+
h = logging.StreamHandler(sys.stdout)
|
27 |
+
|
28 |
+
h.setFormatter(logging.Formatter(formatter))
|
29 |
+
logger.addHandler(h)
|
30 |
+
logger.setLevel(logging.INFO)
|
31 |
+
|
32 |
+
if debug:
|
33 |
+
logger.setLevel(logging.DEBUG)
|
34 |
+
logger.debug(f"type_logger:{type(logger)}.")
|
35 |
+
return logger
|
36 |
+
|
37 |
+
|
38 |
+
def get_constants(event: dict, root: str = ROOT, dotenv_filename: str = ".env", debug=False) -> dict:
|
39 |
+
"""
|
40 |
+
Return constants we need to use from event, context and environment variables (both production and test).
|
41 |
+
|
42 |
+
Args:
|
43 |
+
event: request event
|
44 |
+
root: path containing the dotenv file
|
45 |
+
dotenv_filename: dotenv filename
|
46 |
+
debug: logging debug argument
|
47 |
+
|
48 |
+
Returns:
|
49 |
+
dict: project constants object
|
50 |
+
|
51 |
+
"""
|
52 |
+
import json
|
53 |
+
import os
|
54 |
+
# from dotenv import dotenv_values
|
55 |
+
|
56 |
+
from src.utilities.constants import SKIP_CONDITIONS_LIST
|
57 |
+
local_logger = setup_logging(debug)
|
58 |
+
try:
|
59 |
+
body = event["body"]
|
60 |
+
except Exception as e_constants1:
|
61 |
+
local_logger.error(f"e_constants1:{e_constants1}.")
|
62 |
+
body = event
|
63 |
+
|
64 |
+
if isinstance(body, str):
|
65 |
+
body = json.loads(event["body"])
|
66 |
+
|
67 |
+
try:
|
68 |
+
debug = body["debug"]
|
69 |
+
local_logger.info(f"re-try get debug value:{debug}, log_level:{local_logger.level}.")
|
70 |
+
local_logger = setup_logging(debug)
|
71 |
+
except KeyError:
|
72 |
+
local_logger.error("get_constants:: no debug key, pass...")
|
73 |
+
local_logger.debug(f"constants debug:{debug}, log_level:{local_logger.level}, body:{body}.")
|
74 |
+
|
75 |
+
try:
|
76 |
+
dotenv_file_path = os.path.join(root, dotenv_filename)
|
77 |
+
local_logger.info(f"root_path:{root}, dotenv file:{dotenv_file_path}.")
|
78 |
+
# secrets = dotenv_values(dotenv_file_path)
|
79 |
+
|
80 |
+
try:
|
81 |
+
skip_conditions_list = body["skip_conditions_list"]
|
82 |
+
local_logger.info(f"found skip_conditions_list, using it: {skip_conditions_list}.")
|
83 |
+
except KeyError:
|
84 |
+
skip_conditions_list = SKIP_CONDITIONS_LIST
|
85 |
+
|
86 |
+
return {
|
87 |
+
"bounding_box": body["bounding_box"],
|
88 |
+
"zoom": body["zoom"],
|
89 |
+
"debug": debug,
|
90 |
+
"slope_cellsize": body["slope_cellsize"],
|
91 |
+
"model_project_name": body["model_project_name"],
|
92 |
+
"model_version": body["model_version"],
|
93 |
+
"skip_conditions_list": skip_conditions_list
|
94 |
+
}
|
95 |
+
except KeyError as e_key_constants2:
|
96 |
+
local_logger.error(f"e_key_constants2:{e_key_constants2}.")
|
97 |
+
raise KeyError(f"e_key_constants2:{e_key_constants2}.")
|
98 |
+
except Exception as e_constants2:
|
99 |
+
local_logger.error(f"e_constants2:{e_constants2}.")
|
100 |
+
raise e_constants2
|
static/index.html
ADDED
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<main>
|
2 |
+
<section id="coords-gen">
|
3 |
+
<h2>Segment Geospatial: Instance segmentation within images based on Segment Anything</h2>
|
4 |
+
<p>
|
5 |
+
Model:
|
6 |
+
<a
|
7 |
+
href="https://github.com/opengeos/segment-geospatial/"
|
8 |
+
rel="noreferrer"
|
9 |
+
target="_blank"
|
10 |
+
>opengeos/segment-geospatial
|
11 |
+
</a>
|
12 |
+
</p>
|
13 |
+
<form class="coords-gen-form">
|
14 |
+
<label for="coords-gen-input">Text prompt</label>
|
15 |
+
<input
|
16 |
+
id="coords-gen-input"
|
17 |
+
type="coords"
|
18 |
+
value=""
|
19 |
+
/>
|
20 |
+
<button id="coords-gen-submit">Submit</button>
|
21 |
+
<p class="coords-gen-output"></p>
|
22 |
+
</form>
|
23 |
+
</section>
|
24 |
+
</main>
|
static/script.js
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const coordsGenForm = document.querySelector(".coords-gen-form");
|
2 |
+
|
3 |
+
const segmentCoords = async (coords) => {
|
4 |
+
const inferResponse = await fetch(`infer_samgeo?input=${coords}`);
|
5 |
+
const inferJson = await inferResponse.json();
|
6 |
+
|
7 |
+
return inferJson.output;
|
8 |
+
};
|
9 |
+
|
10 |
+
coordsGenForm.addEventListener("submit", async (event) => {
|
11 |
+
event.preventDefault();
|
12 |
+
|
13 |
+
const coordsGenInput = document.getElementById("coords-gen-input");
|
14 |
+
const coordsGenParagraph = document.querySelector(".coords-gen-output");
|
15 |
+
|
16 |
+
coordsGenParagraph.coordsContent = await segmentCoords(coordsGenInput.value);
|
17 |
+
});
|
static/style.css
ADDED
File without changes
|