[feat] wip workflow from request to response
Browse files- dockerfiles/dockerfile-lambda-fastsam-api +2 -2
- events/payload_point2.json +11 -0
- requirements_dev.txt +6 -2
- src/app.py +30 -16
- src/io/coordinates_pixel_conversion.py +15 -4
- src/io/helpers.py +618 -0
- src/io/tiles_to_tiff.py +195 -0
- src/prediction_api/predictors.py +110 -45
- src/prediction_api/sam_onnx.py +61 -12
- src/utilities/constants.py +8 -2
- src/utilities/type_hints.py +19 -3
- src/utilities/utilities.py +103 -8
dockerfiles/dockerfile-lambda-fastsam-api
CHANGED
|
@@ -21,8 +21,8 @@ RUN ls -l ${LAMBDA_TASK_ROOT}/models
|
|
| 21 |
RUN python -c "import sys; print(sys.path)"
|
| 22 |
RUN python -c "import osgeo"
|
| 23 |
RUN python -c "import cv2"
|
| 24 |
-
RUN python -c "import geopandas"
|
| 25 |
-
RUN python -c "import onnxruntime"
|
| 26 |
# RUN python -c "import rasterio"
|
| 27 |
RUN python -c "import awslambdaric"
|
| 28 |
RUN python -m pip list
|
|
|
|
| 21 |
RUN python -c "import sys; print(sys.path)"
|
| 22 |
RUN python -c "import osgeo"
|
| 23 |
RUN python -c "import cv2"
|
| 24 |
+
# RUN python -c "import geopandas"
|
| 25 |
+
# RUN python -c "import onnxruntime"
|
| 26 |
# RUN python -c "import rasterio"
|
| 27 |
RUN python -c "import awslambdaric"
|
| 28 |
RUN python -m pip list
|
events/payload_point2.json
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"ne": {"lat": 38.03932961278458, "lng": 15.36808069832851},
|
| 3 |
+
"sw": {"lat": 37.455509218936974, "lng": 14.632807441554068},
|
| 4 |
+
"prompt": [{
|
| 5 |
+
"type": "point",
|
| 6 |
+
"data": {"lat": 37.0, "lng": 15.0},
|
| 7 |
+
"label": 0
|
| 8 |
+
}],
|
| 9 |
+
"zoom": 10,
|
| 10 |
+
"source_type": "Satellite"
|
| 11 |
+
}
|
requirements_dev.txt
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
|
|
|
|
|
| 1 |
aws-lambda-powertools
|
| 2 |
awslambdaric
|
| 3 |
bson
|
| 4 |
geopandas
|
| 5 |
-
httpx
|
| 6 |
jmespath
|
| 7 |
numpy
|
| 8 |
onnxruntime
|
| 9 |
opencv-python
|
| 10 |
pillow
|
| 11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
aiofiles
|
| 2 |
+
aiohttp
|
| 3 |
aws-lambda-powertools
|
| 4 |
awslambdaric
|
| 5 |
bson
|
| 6 |
geopandas
|
|
|
|
| 7 |
jmespath
|
| 8 |
numpy
|
| 9 |
onnxruntime
|
| 10 |
opencv-python
|
| 11 |
pillow
|
| 12 |
+
pyproj
|
| 13 |
+
rasterio
|
| 14 |
+
requests
|
| 15 |
+
shapely
|
src/app.py
CHANGED
|
@@ -7,7 +7,7 @@ from aws_lambda_powertools.event_handler import content_types
|
|
| 7 |
from aws_lambda_powertools.utilities.typing import LambdaContext
|
| 8 |
|
| 9 |
from src import app_logger
|
| 10 |
-
from src.io.coordinates_pixel_conversion import
|
| 11 |
from src.prediction_api.predictors import samexporter_predict
|
| 12 |
from src.utilities.constants import CUSTOM_RESPONSE_MESSAGES
|
| 13 |
from src.utilities.utilities import base64_decode
|
|
@@ -27,7 +27,7 @@ def get_response(status: int, start_time: float, request_id: str, response_body:
|
|
| 27 |
str: json response
|
| 28 |
|
| 29 |
"""
|
| 30 |
-
app_logger.
|
| 31 |
response_body["duration_run"] = time.time() - start_time
|
| 32 |
response_body["message"] = CUSTOM_RESPONSE_MESSAGES[status]
|
| 33 |
response_body["request_id"] = request_id
|
|
@@ -44,8 +44,12 @@ def get_response(status: int, start_time: float, request_id: str, response_body:
|
|
| 44 |
|
| 45 |
def get_parsed_bbox_points(request_input: Dict) -> Dict:
|
| 46 |
app_logger.info(f"try to parsing input request {request_input}...")
|
| 47 |
-
|
| 48 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
ne_latlng = [float(ne["lat"]), float(ne["lng"])]
|
| 50 |
sw_latlng = [float(sw["lat"]), float(sw["lng"])]
|
| 51 |
bbox = [ne_latlng, sw_latlng]
|
|
@@ -53,14 +57,23 @@ def get_parsed_bbox_points(request_input: Dict) -> Dict:
|
|
| 53 |
for prompt in request_input["prompt"]:
|
| 54 |
app_logger.info(f"current prompt: {type(prompt)}, value:{prompt}.")
|
| 55 |
data = prompt["data"]
|
| 56 |
-
app_logger.info(f"current data
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
|
| 65 |
app_logger.info(f"unpacking elaborated {request_input}...")
|
| 66 |
return {
|
|
@@ -78,8 +91,8 @@ def lambda_handler(event: dict, context: LambdaContext):
|
|
| 78 |
app_logger.info(f"event version: {event['version']}.")
|
| 79 |
|
| 80 |
try:
|
| 81 |
-
app_logger.
|
| 82 |
-
app_logger.
|
| 83 |
|
| 84 |
try:
|
| 85 |
body = event["body"]
|
|
@@ -87,17 +100,18 @@ def lambda_handler(event: dict, context: LambdaContext):
|
|
| 87 |
app_logger.error(f"e_constants1:{e_constants1}.")
|
| 88 |
body = event
|
| 89 |
|
| 90 |
-
app_logger.
|
| 91 |
|
| 92 |
if isinstance(body, str):
|
| 93 |
body_decoded_str = base64_decode(body)
|
| 94 |
-
app_logger.
|
| 95 |
body = json.loads(body_decoded_str)
|
| 96 |
|
| 97 |
app_logger.info(f"body, #2: {type(body)}, {body}...")
|
| 98 |
|
| 99 |
try:
|
| 100 |
body_request = get_parsed_bbox_points(body)
|
|
|
|
| 101 |
body_response = samexporter_predict(body_request["bbox"], body_request["prompt"], body_request["zoom"])
|
| 102 |
app_logger.info(f"output body_response:{body_response}.")
|
| 103 |
response = get_response(HTTPStatus.OK.value, start_time, context.aws_request_id, body_response)
|
|
|
|
| 7 |
from aws_lambda_powertools.utilities.typing import LambdaContext
|
| 8 |
|
| 9 |
from src import app_logger
|
| 10 |
+
from src.io.coordinates_pixel_conversion import get_latlng_to_pixel_coordinates
|
| 11 |
from src.prediction_api.predictors import samexporter_predict
|
| 12 |
from src.utilities.constants import CUSTOM_RESPONSE_MESSAGES
|
| 13 |
from src.utilities.utilities import base64_decode
|
|
|
|
| 27 |
str: json response
|
| 28 |
|
| 29 |
"""
|
| 30 |
+
app_logger.info(f"response_body:{response_body}.")
|
| 31 |
response_body["duration_run"] = time.time() - start_time
|
| 32 |
response_body["message"] = CUSTOM_RESPONSE_MESSAGES[status]
|
| 33 |
response_body["request_id"] = request_id
|
|
|
|
| 44 |
|
| 45 |
def get_parsed_bbox_points(request_input: Dict) -> Dict:
|
| 46 |
app_logger.info(f"try to parsing input request {request_input}...")
|
| 47 |
+
bbox = request_input["bbox"]
|
| 48 |
+
app_logger.info(f"request bbox: {type(bbox)}, value:{bbox}.")
|
| 49 |
+
ne = bbox["ne"]
|
| 50 |
+
sw = bbox["sw"]
|
| 51 |
+
app_logger.info(f"request ne: {type(ne)}, value:{ne}.")
|
| 52 |
+
app_logger.info(f"request sw: {type(sw)}, value:{sw}.")
|
| 53 |
ne_latlng = [float(ne["lat"]), float(ne["lng"])]
|
| 54 |
sw_latlng = [float(sw["lat"]), float(sw["lng"])]
|
| 55 |
bbox = [ne_latlng, sw_latlng]
|
|
|
|
| 57 |
for prompt in request_input["prompt"]:
|
| 58 |
app_logger.info(f"current prompt: {type(prompt)}, value:{prompt}.")
|
| 59 |
data = prompt["data"]
|
| 60 |
+
app_logger.info(f"current data points: {type(data)}, value:{data}.")
|
| 61 |
+
data_ne = data["ne"]
|
| 62 |
+
app_logger.info(f"current data_ne point: {type(data_ne)}, value:{data_ne}.")
|
| 63 |
+
data_sw = data["sw"]
|
| 64 |
+
app_logger.info(f"current data_sw point: {type(data_sw)}, value:{data_sw}.")
|
| 65 |
+
|
| 66 |
+
diff_pixel_coords_origin_data_ne = get_latlng_to_pixel_coordinates(ne, data_ne, zoom, "ne")
|
| 67 |
+
app_logger.info(f'current diff prompt ne: {type(data)}, {data} => {diff_pixel_coords_origin_data_ne}.')
|
| 68 |
+
diff_pixel_coords_origin_data_sw = get_latlng_to_pixel_coordinates(ne, data_sw, zoom, "sw")
|
| 69 |
+
app_logger.info(f'current diff prompt sw: {type(data)}, {data} => {diff_pixel_coords_origin_data_sw}.')
|
| 70 |
+
prompt["data"] = [
|
| 71 |
+
diff_pixel_coords_origin_data_ne["x"], diff_pixel_coords_origin_data_ne["y"],
|
| 72 |
+
diff_pixel_coords_origin_data_sw["x"], diff_pixel_coords_origin_data_sw["y"]
|
| 73 |
+
]
|
| 74 |
+
|
| 75 |
+
app_logger.info(f"bbox => {bbox}.")
|
| 76 |
+
app_logger.info(f'## request_input["prompt"] updated => {request_input["prompt"]}.')
|
| 77 |
|
| 78 |
app_logger.info(f"unpacking elaborated {request_input}...")
|
| 79 |
return {
|
|
|
|
| 91 |
app_logger.info(f"event version: {event['version']}.")
|
| 92 |
|
| 93 |
try:
|
| 94 |
+
app_logger.info(f"event:{json.dumps(event)}...")
|
| 95 |
+
app_logger.info(f"context:{context}...")
|
| 96 |
|
| 97 |
try:
|
| 98 |
body = event["body"]
|
|
|
|
| 100 |
app_logger.error(f"e_constants1:{e_constants1}.")
|
| 101 |
body = event
|
| 102 |
|
| 103 |
+
app_logger.info(f"body, #1: {type(body)}, {body}...")
|
| 104 |
|
| 105 |
if isinstance(body, str):
|
| 106 |
body_decoded_str = base64_decode(body)
|
| 107 |
+
app_logger.info(f"body_decoded_str: {type(body_decoded_str)}, {body_decoded_str}...")
|
| 108 |
body = json.loads(body_decoded_str)
|
| 109 |
|
| 110 |
app_logger.info(f"body, #2: {type(body)}, {body}...")
|
| 111 |
|
| 112 |
try:
|
| 113 |
body_request = get_parsed_bbox_points(body)
|
| 114 |
+
app_logger.info(f"body_request=> {type(body_request)}, {body_request}.")
|
| 115 |
body_response = samexporter_predict(body_request["bbox"], body_request["prompt"], body_request["zoom"])
|
| 116 |
app_logger.info(f"output body_response:{body_response}.")
|
| 117 |
response = get_response(HTTPStatus.OK.value, start_time, context.aws_request_id, body_response)
|
src/io/coordinates_pixel_conversion.py
CHANGED
|
@@ -33,9 +33,9 @@ def get_latlng2pixel_projection(latlng) -> PixelCoordinate:
|
|
| 33 |
def get_point_latlng_to_pixel_coordinates(latlng, zoom: int) -> PixelCoordinate:
|
| 34 |
try:
|
| 35 |
world_coordinate: PixelCoordinate = get_latlng2pixel_projection(latlng)
|
| 36 |
-
app_logger.
|
| 37 |
scale: int = pow(2, zoom)
|
| 38 |
-
app_logger.
|
| 39 |
return PixelCoordinate(
|
| 40 |
x=math.floor(world_coordinate["x"] * scale),
|
| 41 |
y=math.floor(world_coordinate["y"] * scale)
|
|
@@ -45,9 +45,20 @@ def get_point_latlng_to_pixel_coordinates(latlng, zoom: int) -> PixelCoordinate:
|
|
| 45 |
raise e_format_latlng_to_pixel_coordinates
|
| 46 |
|
| 47 |
|
| 48 |
-
def get_latlng_to_pixel_coordinates(latlng_origin, latlng_current_point, zoom):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
latlng_map_origin = get_point_latlng_to_pixel_coordinates(latlng_origin, zoom)
|
| 50 |
latlng_map_current_point = get_point_latlng_to_pixel_coordinates(latlng_current_point, zoom)
|
| 51 |
diff_coord_x = abs(latlng_map_origin["x"] - latlng_map_current_point["x"])
|
| 52 |
diff_coord_y = abs(latlng_map_origin["y"] - latlng_map_current_point["y"])
|
| 53 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
def get_point_latlng_to_pixel_coordinates(latlng, zoom: int) -> PixelCoordinate:
|
| 34 |
try:
|
| 35 |
world_coordinate: PixelCoordinate = get_latlng2pixel_projection(latlng)
|
| 36 |
+
app_logger.info(f"world_coordinate:{world_coordinate}.")
|
| 37 |
scale: int = pow(2, zoom)
|
| 38 |
+
app_logger.info(f"scale:{scale}.")
|
| 39 |
return PixelCoordinate(
|
| 40 |
x=math.floor(world_coordinate["x"] * scale),
|
| 41 |
y=math.floor(world_coordinate["y"] * scale)
|
|
|
|
| 45 |
raise e_format_latlng_to_pixel_coordinates
|
| 46 |
|
| 47 |
|
| 48 |
+
def get_latlng_to_pixel_coordinates(latlng_origin, latlng_current_point, zoom, k: str):
|
| 49 |
+
# latlng_origin_list = get_latlng_coords_list(latlng_origin, k)
|
| 50 |
+
# latlng_current_point_list = get_latlng_coords_list(latlng_current_point, k)
|
| 51 |
+
app_logger.info(f"latlng_origin - {k}: {type(latlng_origin)}, value:{latlng_origin}.")
|
| 52 |
+
app_logger.info(f"latlng_current_point - {k}: {type(latlng_current_point)}, value:{latlng_current_point}.")
|
| 53 |
latlng_map_origin = get_point_latlng_to_pixel_coordinates(latlng_origin, zoom)
|
| 54 |
latlng_map_current_point = get_point_latlng_to_pixel_coordinates(latlng_current_point, zoom)
|
| 55 |
diff_coord_x = abs(latlng_map_origin["x"] - latlng_map_current_point["x"])
|
| 56 |
diff_coord_y = abs(latlng_map_origin["y"] - latlng_map_current_point["y"])
|
| 57 |
+
point = PixelCoordinate(x=diff_coord_x, y=diff_coord_y)
|
| 58 |
+
app_logger.info(f"point - {k}: {point}.")
|
| 59 |
+
return point
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def get_latlng_coords_list(latlng_point, k: str):
|
| 63 |
+
latlng_current_point = latlng_point[k]
|
| 64 |
+
return [latlng_current_point["lat"], latlng_current_point["lng"]]
|
src/io/helpers.py
ADDED
|
@@ -0,0 +1,618 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Helpers dedicated to georeferencing duties"""
|
| 2 |
+
import base64
|
| 3 |
+
import glob
|
| 4 |
+
import json
|
| 5 |
+
import os
|
| 6 |
+
import zlib
|
| 7 |
+
from math import log, tan, radians, cos, pi, floor, degrees, atan, sinh
|
| 8 |
+
|
| 9 |
+
import rasterio
|
| 10 |
+
|
| 11 |
+
from src import app_logger
|
| 12 |
+
from src.utilities.constants import GEOJSON_SQUARE_TEMPLATE, OUTPUT_CRS_STRING, INPUT_CRS_STRING, SKIP_CONDITIONS_LIST
|
| 13 |
+
from src.utilities.type_hints import ts_llist_float2, ts_geojson, ts_dict_str2b, ts_tuple_flat2, ts_tuple_flat4, \
|
| 14 |
+
ts_list_float4, ts_llist2, ts_tuple_int4, ts_ddict2
|
| 15 |
+
|
| 16 |
+
ZIPJSON_KEY = 'base64(zip(o))'
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def get_geojson_square_angles(bounding_box:ts_llist_float2, name:str="buffer", debug:bool=False) -> ts_geojson:
|
| 20 |
+
"""
|
| 21 |
+
Create a geojson-like dict rectangle from the input latitude/longitude bounding box
|
| 22 |
+
|
| 23 |
+
Args:
|
| 24 |
+
bounding_box: float latitude/longitude bounding box
|
| 25 |
+
name: geojson-like rectangle name
|
| 26 |
+
debug: bool, default=False
|
| 27 |
+
logging debug argument
|
| 28 |
+
|
| 29 |
+
Returns:
|
| 30 |
+
dict: geojson-like object rectangle
|
| 31 |
+
|
| 32 |
+
"""
|
| 33 |
+
import copy
|
| 34 |
+
#from src.surferdtm_prediction_api.utilities.utilities import setup_logging
|
| 35 |
+
|
| 36 |
+
#app_logger = setup_logging(debug)
|
| 37 |
+
app_logger.info(f"bounding_box:{bounding_box}.")
|
| 38 |
+
top = bounding_box[0][0]
|
| 39 |
+
right = bounding_box[0][1]
|
| 40 |
+
bottom = bounding_box[1][0]
|
| 41 |
+
left = bounding_box[1][1]
|
| 42 |
+
bottom_left = [left, bottom]
|
| 43 |
+
top_left = [left, top]
|
| 44 |
+
top_right = [right, top]
|
| 45 |
+
bottom_right = [right, bottom]
|
| 46 |
+
coords = [bottom_left, top_left, top_right, bottom_right]
|
| 47 |
+
app_logger.info(f"coords:{coords}.")
|
| 48 |
+
geojson = copy.copy(GEOJSON_SQUARE_TEMPLATE)
|
| 49 |
+
geojson["name"] = name
|
| 50 |
+
geojson["features"][0]["geometry"]["coordinates"] = [[coords]]
|
| 51 |
+
app_logger.info(f"geojson:{geojson}.")
|
| 52 |
+
return geojson
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def crop_raster(merged_raster_path:str, area_crop_geojson:dict, debug:bool=False) -> ts_dict_str2b:
|
| 56 |
+
"""
|
| 57 |
+
Crop a raster using a geojson-like object rectangle
|
| 58 |
+
|
| 59 |
+
Args:
|
| 60 |
+
merged_raster_path: filename path pointing string to the raster to crop
|
| 61 |
+
area_crop_geojson: geojson-like object rectangle
|
| 62 |
+
debug: bool, default=False
|
| 63 |
+
logging debug argument
|
| 64 |
+
|
| 65 |
+
Returns:
|
| 66 |
+
dict: the cropped raster numpy array and the transform object with the georeferencing reference
|
| 67 |
+
|
| 68 |
+
"""
|
| 69 |
+
#from src.surferdtm_prediction_api.utilities.utilities import setup_logging
|
| 70 |
+
|
| 71 |
+
#app_logger = setup_logging(debug)
|
| 72 |
+
try:
|
| 73 |
+
import rasterio
|
| 74 |
+
from rasterio.mask import mask
|
| 75 |
+
|
| 76 |
+
app_logger.info(f"area_crop_geojson::{area_crop_geojson}.")
|
| 77 |
+
geojson_reprojected = get_geojson_reprojected(area_crop_geojson, debug=debug)
|
| 78 |
+
shapes = [feature["geometry"] for feature in geojson_reprojected["features"]]
|
| 79 |
+
app_logger.info(f"geojson_reprojected:{geojson_reprojected}.")
|
| 80 |
+
|
| 81 |
+
app_logger.info(f"reading merged_raster_path while masking it from path:{merged_raster_path}.")
|
| 82 |
+
with rasterio.open(merged_raster_path, "r") as src:
|
| 83 |
+
masked_raster, masked_transform = mask(src, shapes, crop=True)
|
| 84 |
+
masked_meta = src.meta
|
| 85 |
+
app_logger.info(f"merged_raster_path, src:{src}.")
|
| 86 |
+
masked_meta.update({
|
| 87 |
+
"driver": "GTiff", "height": masked_raster.shape[1],
|
| 88 |
+
"width": masked_raster.shape[2], "transform": masked_transform}
|
| 89 |
+
)
|
| 90 |
+
return {"masked_raster": masked_raster, "masked_meta": masked_meta, "masked_transform": masked_transform}
|
| 91 |
+
except Exception as e:
|
| 92 |
+
app_logger.error(e)
|
| 93 |
+
raise e
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
def get_geojson_reprojected(geojson:dict, output_crs:str=OUTPUT_CRS_STRING, debug:bool=False) -> dict:
|
| 97 |
+
"""
|
| 98 |
+
change projection for input geojson-like object polygon
|
| 99 |
+
|
| 100 |
+
Args:
|
| 101 |
+
geojson: input geojson-like object polygon
|
| 102 |
+
output_crs: output crs string - Coordinate Reference Systems
|
| 103 |
+
debug: logging debug argument
|
| 104 |
+
|
| 105 |
+
Returns:
|
| 106 |
+
dict: reprojected geojson-like object
|
| 107 |
+
|
| 108 |
+
"""
|
| 109 |
+
#from src.surferdtm_prediction_api.utilities.utilities import setup_logging
|
| 110 |
+
|
| 111 |
+
#app_logger = setup_logging(debug)
|
| 112 |
+
if not isinstance(geojson, dict):
|
| 113 |
+
raise ValueError(f"geojson here should be a dict, not of type {type(geojson)}.")
|
| 114 |
+
app_logger.info(f"start reprojecting geojson:{geojson}.")
|
| 115 |
+
try:
|
| 116 |
+
features = geojson['features']
|
| 117 |
+
|
| 118 |
+
output_crs_json = {"type": "name", "properties": {"name": f"urn:ogc:def:crs:{output_crs}"}}
|
| 119 |
+
geojson_output = {'features': [], 'type': 'FeatureCollection', "name": "converted", "crs": output_crs_json}
|
| 120 |
+
|
| 121 |
+
# Iterate through each feature of the feature collection
|
| 122 |
+
for feature in features:
|
| 123 |
+
feature_out = feature.copy()
|
| 124 |
+
new_coords = []
|
| 125 |
+
feat = feature['geometry']
|
| 126 |
+
app_logger.info(f"feat:{feat}.")
|
| 127 |
+
coords = feat['coordinates']
|
| 128 |
+
app_logger.info(f"coordinates:{coords}.")
|
| 129 |
+
# iterate over "coordinates" lists with 3 nested loops, practically with only one element but last loop
|
| 130 |
+
for coord_a in coords:
|
| 131 |
+
new_coords_a = []
|
| 132 |
+
for cord_b in coord_a:
|
| 133 |
+
new_coords_b = []
|
| 134 |
+
# Project/transform coordinate pairs of each ring
|
| 135 |
+
# (iteration required in case geometry type is MultiPolygon, or there are holes)
|
| 136 |
+
for xconv, yconf in cord_b:
|
| 137 |
+
app_logger.info(f"xconv, yconf:{xconv},{yconf}.")
|
| 138 |
+
x2, y2 = latlon_to_mercator(xconv, yconf)
|
| 139 |
+
app_logger.info(f"x2, y2:{x2},{y2}.")
|
| 140 |
+
new_coords_b.append([x2, y2])
|
| 141 |
+
new_coords_a.append(new_coords_b)
|
| 142 |
+
new_coords.append(new_coords_a)
|
| 143 |
+
feature_out['geometry']['coordinates'] = new_coords
|
| 144 |
+
geojson_output['features'].append(feature_out)
|
| 145 |
+
app_logger.info(f"geojson_output:{geojson_output}.")
|
| 146 |
+
return geojson_output
|
| 147 |
+
except KeyError as ke_get_geojson_reprojected:
|
| 148 |
+
msg = f"ke_get_geojson_reprojected:{ke_get_geojson_reprojected}."
|
| 149 |
+
app_logger.error(msg)
|
| 150 |
+
raise KeyError(msg)
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
def latlon_to_mercator(
|
| 154 |
+
lat:float, lon:float, input_crs:str=INPUT_CRS_STRING, output_crs:str=OUTPUT_CRS_STRING, always_xy:bool=True, debug:bool=False
|
| 155 |
+
) -> ts_tuple_flat2:
|
| 156 |
+
"""
|
| 157 |
+
Return a tuple of latitude, longitude float coordinates values transformed to mercator
|
| 158 |
+
|
| 159 |
+
Args:
|
| 160 |
+
|
| 161 |
+
lat: input latitude float value
|
| 162 |
+
lon: input longitude float value
|
| 163 |
+
input_crs: string, input Coordinate Reference Systems
|
| 164 |
+
output_crs: string, output Coordinate Reference Systems
|
| 165 |
+
always_xy: bool, default=True.
|
| 166 |
+
If true, the transform method will accept as input and return as output
|
| 167 |
+
coordinates using the traditional GIS order, that is longitude, latitude
|
| 168 |
+
for geographic CRS and easting, northing for most projected CRS.
|
| 169 |
+
debug: bool, default=False.
|
| 170 |
+
logging debug argument
|
| 171 |
+
|
| 172 |
+
Returns:
|
| 173 |
+
tuple latitude/longitude float values
|
| 174 |
+
|
| 175 |
+
"""
|
| 176 |
+
#from src.surferdtm_prediction_api.utilities.utilities import setup_logging
|
| 177 |
+
#app_logger = setup_logging(debug)
|
| 178 |
+
try:
|
| 179 |
+
from pyproj import Transformer
|
| 180 |
+
app_logger.info(f"lat:{lat},lon:{lon}.")
|
| 181 |
+
transformer = Transformer.from_crs(input_crs, output_crs, always_xy=always_xy)
|
| 182 |
+
out_lat, out_lon = transformer.transform(lat, lon)
|
| 183 |
+
app_logger.info(f"out_lat:{out_lat},out_lon:{out_lon}.")
|
| 184 |
+
return out_lat, out_lon
|
| 185 |
+
except Exception as e_latlon_to_mercator:
|
| 186 |
+
app_logger.error(f"e_latlon_to_mercator:{e_latlon_to_mercator}.")
|
| 187 |
+
raise e_latlon_to_mercator
|
| 188 |
+
|
| 189 |
+
|
| 190 |
+
def sec(x:float) -> float:
|
| 191 |
+
"""
|
| 192 |
+
Return secant (the reciprocal of the cosine) for given value
|
| 193 |
+
|
| 194 |
+
Args:
|
| 195 |
+
x: input float value
|
| 196 |
+
|
| 197 |
+
Returns:
|
| 198 |
+
float: secant of given float value
|
| 199 |
+
|
| 200 |
+
"""
|
| 201 |
+
return 1 / cos(x)
|
| 202 |
+
|
| 203 |
+
|
| 204 |
+
def latlon_to_xyz(lat:float, lon:float, z:int) -> ts_tuple_flat2:
|
| 205 |
+
"""
|
| 206 |
+
Return x/y coordinates points for tiles from latitude/longitude values point.
|
| 207 |
+
|
| 208 |
+
Args:
|
| 209 |
+
lon: float longitude value
|
| 210 |
+
lat: float latitude value
|
| 211 |
+
z: float zoom value
|
| 212 |
+
|
| 213 |
+
Returns:
|
| 214 |
+
tuple: x, y values tiles coordinates
|
| 215 |
+
|
| 216 |
+
"""
|
| 217 |
+
tile_count = pow(2, z)
|
| 218 |
+
x = (lon + 180) / 360
|
| 219 |
+
y = (1 - log(tan(radians(lat)) + sec(radians(lat))) / pi) / 2
|
| 220 |
+
return tile_count * x, tile_count * y
|
| 221 |
+
|
| 222 |
+
|
| 223 |
+
def bbox_to_xyz(lon_min:float, lon_max:float, lat_min:float, lat_max:float, z:int) -> ts_tuple_flat4:
|
| 224 |
+
"""
|
| 225 |
+
Return xyz reference coordinates for tiles from latitude/longitude min and max values.
|
| 226 |
+
|
| 227 |
+
Args:
|
| 228 |
+
lon_min: float min longitude value
|
| 229 |
+
lon_max: float max longitude value
|
| 230 |
+
lat_min: float min latitude value
|
| 231 |
+
lat_max: float max latitude value
|
| 232 |
+
z: float zoom value
|
| 233 |
+
|
| 234 |
+
Returns:
|
| 235 |
+
tuple: float x min, x max, y min, y max values tiles coordinates
|
| 236 |
+
|
| 237 |
+
"""
|
| 238 |
+
x_min, y_max = latlon_to_xyz(lat_min, lon_min, z)
|
| 239 |
+
x_max, y_min = latlon_to_xyz(lat_max, lon_max, z)
|
| 240 |
+
return (floor(x_min), floor(x_max),
|
| 241 |
+
floor(y_min), floor(y_max))
|
| 242 |
+
|
| 243 |
+
|
| 244 |
+
def mercator_to_lat(mercator_y:float) -> float:
|
| 245 |
+
"""
|
| 246 |
+
Return latitude value coordinate from mercator coordinate value
|
| 247 |
+
|
| 248 |
+
Args:
|
| 249 |
+
mercator_y: float mercator value coordinate
|
| 250 |
+
|
| 251 |
+
Returns:
|
| 252 |
+
float: latitude value coordinate
|
| 253 |
+
|
| 254 |
+
"""
|
| 255 |
+
return degrees(atan(sinh(mercator_y)))
|
| 256 |
+
|
| 257 |
+
|
| 258 |
+
def y_to_lat_edges(y:float, z:int) -> ts_tuple_flat2:
|
| 259 |
+
"""
|
| 260 |
+
Return edge float latitude values coordinates from x,z tiles coordinates
|
| 261 |
+
|
| 262 |
+
Args:
|
| 263 |
+
y: float x tile value coordinate
|
| 264 |
+
z: float zoom tile value coordinate
|
| 265 |
+
|
| 266 |
+
Returns:
|
| 267 |
+
tuple: two float latitude values coordinates
|
| 268 |
+
|
| 269 |
+
"""
|
| 270 |
+
tile_count = pow(2, z)
|
| 271 |
+
unit = 1 / tile_count
|
| 272 |
+
relative_y1 = y * unit
|
| 273 |
+
relative_y2 = relative_y1 + unit
|
| 274 |
+
lat1 = mercator_to_lat(pi * (1 - 2 * relative_y1))
|
| 275 |
+
lat2 = mercator_to_lat(pi * (1 - 2 * relative_y2))
|
| 276 |
+
return lat1, lat2
|
| 277 |
+
|
| 278 |
+
|
| 279 |
+
def x_to_lon_edges(x:float, z:int) -> ts_tuple_flat2:
|
| 280 |
+
"""
|
| 281 |
+
Return edge float longitude values coordinates from x,z tiles coordinates
|
| 282 |
+
|
| 283 |
+
Args:
|
| 284 |
+
x: float x tile value coordinate
|
| 285 |
+
z: float zoom tile value coordinate
|
| 286 |
+
|
| 287 |
+
Returns:
|
| 288 |
+
tuple: two float longitude values coordinates
|
| 289 |
+
|
| 290 |
+
"""
|
| 291 |
+
tile_count = pow(2, z)
|
| 292 |
+
unit = 360 / tile_count
|
| 293 |
+
lon1 = -180 + x * unit
|
| 294 |
+
lon2 = lon1 + unit
|
| 295 |
+
return lon1, lon2
|
| 296 |
+
|
| 297 |
+
|
| 298 |
+
def tile_edges(x:float, y:float, z:int) -> ts_list_float4:
|
| 299 |
+
"""
|
| 300 |
+
Return edge float latitude/longitude value coordinates from xyz tiles coordinates
|
| 301 |
+
|
| 302 |
+
Args:
|
| 303 |
+
x: float x tile value coordinate
|
| 304 |
+
y: float y tile value coordinate
|
| 305 |
+
z: float zoom tile value coordinate
|
| 306 |
+
|
| 307 |
+
Returns:
|
| 308 |
+
tuple: float latitude/longitude values coordinates
|
| 309 |
+
|
| 310 |
+
"""
|
| 311 |
+
lat1, lat2 = y_to_lat_edges(y, z)
|
| 312 |
+
lon1, lon2 = x_to_lon_edges(x, z)
|
| 313 |
+
return [lon1, lat1, lon2, lat2]
|
| 314 |
+
|
| 315 |
+
|
| 316 |
+
def merge_tiles(input_pattern:str, output_path:str, temp_dir:str, debug:bool=False) -> None:
|
| 317 |
+
"""
|
| 318 |
+
Merge given raster glob input pattern into one unique georeferenced raster.
|
| 319 |
+
|
| 320 |
+
Args:
|
| 321 |
+
input_pattern: input glob pattern needed for search the raster filenames
|
| 322 |
+
output_path: output path where to write the merged raster
|
| 323 |
+
temp_dir: temporary folder needed for create
|
| 324 |
+
debug: bool, default=False.
|
| 325 |
+
logging debug argument
|
| 326 |
+
|
| 327 |
+
"""
|
| 328 |
+
#from src.surferdtm_prediction_api.utilities.utilities import setup_logging
|
| 329 |
+
|
| 330 |
+
#app_logger = setup_logging(debug)
|
| 331 |
+
try:
|
| 332 |
+
from osgeo import gdal
|
| 333 |
+
except ModuleNotFoundError as module_error_merge_tiles:
|
| 334 |
+
msg = f"module_error_merge_tiles:{module_error_merge_tiles}."
|
| 335 |
+
app_logger.error(msg)
|
| 336 |
+
raise module_error_merge_tiles
|
| 337 |
+
|
| 338 |
+
try:
|
| 339 |
+
vrt_path = os.path.join(temp_dir, "tiles.vrt")
|
| 340 |
+
os_list_dir1 = os.listdir(temp_dir)
|
| 341 |
+
app_logger.info(f"os_list_dir1:{os_list_dir1}.")
|
| 342 |
+
|
| 343 |
+
gdal.BuildVRT(vrt_path, glob.glob(input_pattern))
|
| 344 |
+
gdal.Translate(output_path, vrt_path)
|
| 345 |
+
|
| 346 |
+
os_list_dir2 = os.listdir(temp_dir)
|
| 347 |
+
app_logger.info(f"os_list_dir2:{os_list_dir2}.")
|
| 348 |
+
except IOError as ioe_merge_tiles:
|
| 349 |
+
msg = f"ioe_merge_tiles:{ioe_merge_tiles}."
|
| 350 |
+
app_logger.error(msg)
|
| 351 |
+
raise ioe_merge_tiles
|
| 352 |
+
|
| 353 |
+
|
| 354 |
+
def get_lat_lon_coords(bounding_box: ts_llist2) -> ts_tuple_int4:
|
| 355 |
+
"""
|
| 356 |
+
Return couples of float latitude/longitude values from bounding box input list.
|
| 357 |
+
|
| 358 |
+
Args:
|
| 359 |
+
bounding_box: bounding box input list of latitude/longitude coordinates
|
| 360 |
+
|
| 361 |
+
Returns:
|
| 362 |
+
tuple: float longitude min, latitude min, longitude max, longitude max values coordinates
|
| 363 |
+
|
| 364 |
+
"""
|
| 365 |
+
top_right, bottom_left = bounding_box
|
| 366 |
+
lat_max, lon_max = top_right
|
| 367 |
+
lat_min, lon_min = bottom_left
|
| 368 |
+
if lon_min == lon_max or lat_min == lat_max:
|
| 369 |
+
raise ValueError(f"latitude and/or longitude coordinates should not be equal each others... {bounding_box}.")
|
| 370 |
+
return lon_min, lat_min, lon_max, lat_max
|
| 371 |
+
|
| 372 |
+
|
| 373 |
+
def get_prediction_georeferenced(prediction_obj:dict, transform:rasterio.transform, skip_conditions_list:list=None, debug:bool=False) -> dict:
|
| 374 |
+
"""
|
| 375 |
+
Return a georeferenced geojson-like object starting from a dict containing "predictions" -> "points" list.
|
| 376 |
+
Apply the affine transform matrix of georeferenced raster submitted to the machine learning model.
|
| 377 |
+
|
| 378 |
+
Args:
|
| 379 |
+
prediction_obj: input dict
|
| 380 |
+
transform: 'rasterio.transform' or dict list, affine tranform matrix
|
| 381 |
+
skip_conditions_list: dict list, skip condition list
|
| 382 |
+
debug: bool, default=False.
|
| 383 |
+
logging debug argument
|
| 384 |
+
|
| 385 |
+
Returns:
|
| 386 |
+
dict
|
| 387 |
+
|
| 388 |
+
"""
|
| 389 |
+
#from src.surferdtm_prediction_api.utilities.utilities import setup_logging
|
| 390 |
+
|
| 391 |
+
if skip_conditions_list is None:
|
| 392 |
+
skip_conditions_list = SKIP_CONDITIONS_LIST
|
| 393 |
+
|
| 394 |
+
#app_logger = setup_logging(debug)
|
| 395 |
+
app_logger.info(f"prediction_obj::{prediction_obj}, transform::{transform}.")
|
| 396 |
+
crs = {"type": "name", "properties": {"name": "urn:ogc:def:crs:EPSG::3857"}}
|
| 397 |
+
geojson_obj = {'features': [], 'type': 'FeatureCollection', "name": "geojson_name", "crs": crs}
|
| 398 |
+
for n, prediction in enumerate(prediction_obj["predictions"]):
|
| 399 |
+
points_dict_ = prediction["points"]
|
| 400 |
+
points_list = [[p["x"], p["y"]] for p in points_dict_]
|
| 401 |
+
app_logger.info(f"points_list::{points_list}.")
|
| 402 |
+
# if check_skip_conditions(prediction, skip_conditions_list, debug=debug):
|
| 403 |
+
# continue
|
| 404 |
+
feature = populate_features_geojson(n, points_list, confidence=prediction["confidence"], geomorphic_class=prediction["class"])
|
| 405 |
+
app_logger.info(f"geojson::feature:{feature}.")
|
| 406 |
+
feature["geometry"] = apply_transform(feature["geometry"], transform, debug=debug)
|
| 407 |
+
geojson_obj["features"].append(feature)
|
| 408 |
+
app_logger.info(f"geojson::post_update:{geojson_obj}.")
|
| 409 |
+
return geojson_obj
|
| 410 |
+
|
| 411 |
+
|
| 412 |
+
def populate_features_geojson(idx: int, coordinates_list: list, **kwargs) -> ts_ddict2:
|
| 413 |
+
"""
|
| 414 |
+
Return a list of coordinate points in a geojson-like feature-like object.
|
| 415 |
+
|
| 416 |
+
Args:
|
| 417 |
+
idx: int, feature index
|
| 418 |
+
coordinates_list: dict list, coordinate points
|
| 419 |
+
**kwargs: optional arguments to merge within the geojson properties feature
|
| 420 |
+
|
| 421 |
+
Returns:
|
| 422 |
+
dict
|
| 423 |
+
|
| 424 |
+
"""
|
| 425 |
+
return {
|
| 426 |
+
"type": "Feature",
|
| 427 |
+
"properties": {"id": idx, **kwargs},
|
| 428 |
+
"geometry": {
|
| 429 |
+
"type": "MultiPolygon",
|
| 430 |
+
"coordinates": [[coordinates_list]],
|
| 431 |
+
}
|
| 432 |
+
}
|
| 433 |
+
|
| 434 |
+
|
| 435 |
+
def check_skip_conditions(prediction:dict, skip_conditions_list:list, debug:bool=False) -> bool:
|
| 436 |
+
"""
|
| 437 |
+
Loop over elements within skip_condition_list and return a boolean if no condition to skip (or exceptions).
|
| 438 |
+
|
| 439 |
+
Args:
|
| 440 |
+
prediction: input dict to check
|
| 441 |
+
skip_conditions_list: dict list with conditions to evaluate
|
| 442 |
+
debug: bool, default=False
|
| 443 |
+
logging debug argument
|
| 444 |
+
|
| 445 |
+
Returns:
|
| 446 |
+
bool
|
| 447 |
+
|
| 448 |
+
"""
|
| 449 |
+
for obj in skip_conditions_list:
|
| 450 |
+
return skip_feature(prediction, obj["skip_key"], obj["skip_value"], obj["skip_condition"], debug=debug)
|
| 451 |
+
return False
|
| 452 |
+
|
| 453 |
+
|
| 454 |
+
def skip_feature(prediction:dict, skip_key:float, skip_value:str, skip_condition:str, debug:bool=False) -> bool:
|
| 455 |
+
"""
|
| 456 |
+
Return False if values from input dict shouldn't be skipped,
|
| 457 |
+
True in case of exceptions, empty skip_condition or when chosen condition meets skip_value and skip_condition.
|
| 458 |
+
|
| 459 |
+
E.g. confidence should be major than 0.8: if confidence is equal to 0.65 then return True (0.65 < 0.8) and skip!
|
| 460 |
+
|
| 461 |
+
Args:
|
| 462 |
+
prediction: input dict to check
|
| 463 |
+
skip_key: skip condition key string
|
| 464 |
+
skip_value: skip condition value string
|
| 465 |
+
skip_condition: string (major | minor | equal)
|
| 466 |
+
debug: bool, default=False
|
| 467 |
+
logging debug argument
|
| 468 |
+
|
| 469 |
+
Returns:
|
| 470 |
+
bool
|
| 471 |
+
|
| 472 |
+
"""
|
| 473 |
+
#from src.surferdtm_prediction_api.utilities.utilities import setup_logging
|
| 474 |
+
#app_logger = setup_logging(debug)
|
| 475 |
+
try:
|
| 476 |
+
v = prediction[skip_key]
|
| 477 |
+
match skip_condition:
|
| 478 |
+
case "major":
|
| 479 |
+
return v > skip_value
|
| 480 |
+
case "minor":
|
| 481 |
+
return v < skip_value
|
| 482 |
+
case "equal":
|
| 483 |
+
return v == skip_value
|
| 484 |
+
case "":
|
| 485 |
+
return False
|
| 486 |
+
except KeyError as ke_filter_feature:
|
| 487 |
+
app_logger.error(f"ke_filter_feature:{ke_filter_feature}.")
|
| 488 |
+
return False
|
| 489 |
+
except Exception as e_filter_feature:
|
| 490 |
+
app_logger.error(f"e_filter_feature:{e_filter_feature}.")
|
| 491 |
+
return False
|
| 492 |
+
|
| 493 |
+
|
| 494 |
+
def apply_transform(geometry:object, transform:list[object], debug:bool=False) -> dict:
|
| 495 |
+
"""
|
| 496 |
+
Returns a GeoJSON-like mapping from a transformed geometry using an affine transformation matrix.
|
| 497 |
+
|
| 498 |
+
The coefficient matrix is provided as a list or tuple with 6 items
|
| 499 |
+
for 2D transformations. The 6 parameter matrix is::
|
| 500 |
+
|
| 501 |
+
[a, b, d, e, xoff, yoff]
|
| 502 |
+
|
| 503 |
+
which represents the augmented matrix::
|
| 504 |
+
|
| 505 |
+
[x'] / a b xoff \ [x]
|
| 506 |
+
[y'] = | d e yoff | [y]
|
| 507 |
+
[1 ] \ 0 0 1 / [1]
|
| 508 |
+
|
| 509 |
+
or the equations for the transformed coordinates::
|
| 510 |
+
|
| 511 |
+
x' = a * x + b * y + xoff
|
| 512 |
+
y' = d * x + e * y + yoff
|
| 513 |
+
|
| 514 |
+
Args:
|
| 515 |
+
geometry: geometry value from a geojson dict
|
| 516 |
+
transform: list of float values (affine transformation matrix)
|
| 517 |
+
debug: bool, default=False
|
| 518 |
+
logging debug argument
|
| 519 |
+
|
| 520 |
+
Returns:
|
| 521 |
+
dict
|
| 522 |
+
|
| 523 |
+
"""
|
| 524 |
+
#from src.surferdtm_prediction_api.utilities.utilities import setup_logging
|
| 525 |
+
|
| 526 |
+
#app_logger = setup_logging(debug)
|
| 527 |
+
|
| 528 |
+
try:
|
| 529 |
+
from shapely.affinity import affine_transform
|
| 530 |
+
from shapely.geometry import mapping, shape
|
| 531 |
+
try:
|
| 532 |
+
geometry_transformed = affine_transform(shape(geometry), [transform.a, transform.b, transform.d, transform.e, transform.xoff, transform.yoff])
|
| 533 |
+
except AttributeError as ae:
|
| 534 |
+
app_logger.warning(f"ae:{ae}.")
|
| 535 |
+
geometry_transformed = affine_transform(shape(geometry), [transform[0], transform[1], transform[2], transform[3], transform[4], transform[5]])
|
| 536 |
+
geometry_serialized = mapping(geometry_transformed)
|
| 537 |
+
app_logger.info(f"geometry_serialized:{geometry_serialized}.")
|
| 538 |
+
return geometry_serialized
|
| 539 |
+
except ImportError as ie_apply_transform:
|
| 540 |
+
app_logger.error(f"ie_apply_transform:{ie_apply_transform}.")
|
| 541 |
+
raise ie_apply_transform
|
| 542 |
+
except Exception as e_apply_transform:
|
| 543 |
+
app_logger.error(f"e_apply_transform:{e_apply_transform}.")
|
| 544 |
+
raise e_apply_transform
|
| 545 |
+
|
| 546 |
+
|
| 547 |
+
def get_perc(nan_count:int, total_count:int) -> str:
|
| 548 |
+
"""
|
| 549 |
+
Return a formatted string with a percentage value representing the ratio between NaN and total number elements within a numpy array
|
| 550 |
+
|
| 551 |
+
Args:
|
| 552 |
+
nan_count: NaN value elements
|
| 553 |
+
total_count: total count of elements
|
| 554 |
+
|
| 555 |
+
Returns:
|
| 556 |
+
str
|
| 557 |
+
|
| 558 |
+
"""
|
| 559 |
+
return f"{100*nan_count/total_count:.2f}"
|
| 560 |
+
|
| 561 |
+
|
| 562 |
+
def json_unzip(j:dict, debug:bool=False) -> str:
|
| 563 |
+
"""
|
| 564 |
+
Return uncompressed content from input dict using 'zlib' library
|
| 565 |
+
|
| 566 |
+
Args:
|
| 567 |
+
j: input dict to uncompress. key must be 'base64(zip(o))'
|
| 568 |
+
debug: logging debug argument
|
| 569 |
+
|
| 570 |
+
Returns:
|
| 571 |
+
dict: uncompressed dict
|
| 572 |
+
|
| 573 |
+
"""
|
| 574 |
+
from json import JSONDecodeError
|
| 575 |
+
from zlib import error as zlib_error
|
| 576 |
+
|
| 577 |
+
#from src.surferdtm_prediction_api.utilities.utilities import setup_logging
|
| 578 |
+
|
| 579 |
+
#app_logger = setup_logging(debug)
|
| 580 |
+
|
| 581 |
+
try:
|
| 582 |
+
j = zlib.decompress(base64.b64decode(j[ZIPJSON_KEY]))
|
| 583 |
+
except KeyError as ke:
|
| 584 |
+
ke_error_msg = f"Could not decode/unzip the content because of wrong/missing dict key:{ke}."
|
| 585 |
+
raise KeyError(ke_error_msg)
|
| 586 |
+
except zlib_error as zlib_error2:
|
| 587 |
+
zlib_error2_msg = f"Could not decode/unzip the content because of:{zlib_error2}."
|
| 588 |
+
app_logger.error(zlib_error2_msg)
|
| 589 |
+
raise RuntimeError(zlib_error2_msg)
|
| 590 |
+
|
| 591 |
+
try:
|
| 592 |
+
j = json.loads(j)
|
| 593 |
+
except JSONDecodeError as json_e1:
|
| 594 |
+
msg = f"Could interpret the unzipped content because of JSONDecodeError with msg:{json_e1.msg}, pos:{json_e1.pos}, broken json:'{json_e1.doc}'"
|
| 595 |
+
app_logger.error(msg)
|
| 596 |
+
raise RuntimeError(msg)
|
| 597 |
+
|
| 598 |
+
return j
|
| 599 |
+
|
| 600 |
+
|
| 601 |
+
def json_zip(j:dict) -> dict[str]:
|
| 602 |
+
"""
|
| 603 |
+
Return compressed content from input dict using 'zlib' library
|
| 604 |
+
|
| 605 |
+
Args:
|
| 606 |
+
j: input dict to compress
|
| 607 |
+
|
| 608 |
+
Returns:
|
| 609 |
+
dict: compressed dict
|
| 610 |
+
|
| 611 |
+
"""
|
| 612 |
+
return {
|
| 613 |
+
ZIPJSON_KEY: base64.b64encode(
|
| 614 |
+
zlib.compress(
|
| 615 |
+
json.dumps(j).encode('utf-8')
|
| 616 |
+
)
|
| 617 |
+
).decode('ascii')
|
| 618 |
+
}
|
src/io/tiles_to_tiff.py
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Async download raster tiles"""
|
| 2 |
+
import os
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
|
| 5 |
+
import numpy as np
|
| 6 |
+
|
| 7 |
+
from src import app_logger, PROJECT_ROOT_FOLDER
|
| 8 |
+
from src.io.helpers import get_lat_lon_coords, merge_tiles, get_geojson_square_angles, crop_raster
|
| 9 |
+
from src.io.tms2geotiff import download_extent, save_geotiff_gdal
|
| 10 |
+
from src.utilities.constants import COMPLETE_URL_TILES, DEFAULT_TMS
|
| 11 |
+
from src.utilities.type_hints import ts_llist2
|
| 12 |
+
|
| 13 |
+
COOKIE_SESSION = {
|
| 14 |
+
"Accept": "*/*",
|
| 15 |
+
"Accept-Encoding": "gzip, deflate",
|
| 16 |
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0",
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def load_affine_transformation_from_matrix(matrix_source_coeffs):
|
| 21 |
+
from affine import Affine
|
| 22 |
+
|
| 23 |
+
if len(matrix_source_coeffs) != 6:
|
| 24 |
+
raise ValueError(f"Expected 6 coefficients, found {len(matrix_source_coeffs)};"
|
| 25 |
+
f"argument type: {type(matrix_source_coeffs)}.")
|
| 26 |
+
|
| 27 |
+
try:
|
| 28 |
+
a, d, b, e, c, f = (float(x) for x in matrix_source_coeffs)
|
| 29 |
+
center = tuple.__new__(Affine, [a, b, c, d, e, f, 0.0, 0.0, 1.0])
|
| 30 |
+
return center * Affine.translation(-0.5, -0.5)
|
| 31 |
+
except Exception as e:
|
| 32 |
+
app_logger.error(f"exception:{e}, check https://github.com/rasterio/affine project for updates")
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
# @timing_decorator
|
| 36 |
+
def convert(bounding_box: ts_llist2, zoom: int) -> tuple:
|
| 37 |
+
"""
|
| 38 |
+
Starting from a bounding box of two couples of latitude and longitude coordinate values, recognize a stratovolcano from an RGB image. The algorithm
|
| 39 |
+
create the image composing three channels as slope, DEM (Digital Elevation Model) and curvature. In more detail:
|
| 40 |
+
|
| 41 |
+
- download a series of terrain DEM (Digital Elevation Model) raster tiles enclosed within that bounding box
|
| 42 |
+
- merge all the downloaded rasters
|
| 43 |
+
- crop the merged raster
|
| 44 |
+
- process the cropped raster to extract slope and curvature (1st and 2nd degree derivative)
|
| 45 |
+
- produce three raster channels (DEM, slope and curvature rasters) to produce an RGB raster image
|
| 46 |
+
- submit the RGB image to a remote machine learning service to try to recognize a polygon representing a stratovolcano
|
| 47 |
+
- the output of the machine learning service is a json, so we need to georeferencing it
|
| 48 |
+
- finally we return a dict as response containing
|
| 49 |
+
- uploaded_file_name
|
| 50 |
+
- bucket_name
|
| 51 |
+
- prediction georeferenced geojson-like dict
|
| 52 |
+
|
| 53 |
+
Args:
|
| 54 |
+
bounding_box: float latitude/longitude bounding box
|
| 55 |
+
zoom: integer zoom value
|
| 56 |
+
|
| 57 |
+
Returns:
|
| 58 |
+
dict: uploaded_file_name (str), bucket_name (str), prediction_georef (dict), n_total_obj_prediction (str)
|
| 59 |
+
|
| 60 |
+
"""
|
| 61 |
+
import tempfile
|
| 62 |
+
|
| 63 |
+
# from src.surferdtm_prediction_api.utilities.constants import NODATA_VALUES
|
| 64 |
+
# from src.surferdtm_prediction_api.utilities.utilities import setup_logging
|
| 65 |
+
# from src.surferdtm_prediction_api.raster.elaborate_images import elaborate_images.get_rgb_prediction_image
|
| 66 |
+
# from src.surferdtm_prediction_api.raster.prediction import model_prediction
|
| 67 |
+
# from src.surferdtm_prediction_api.geo.helpers import get_lat_lon_coords, merge_tiles, crop_raster, get_prediction_georeferenced, \
|
| 68 |
+
# get_geojson_square_angles, get_perc
|
| 69 |
+
|
| 70 |
+
# app_logger = setup_logging(debug)
|
| 71 |
+
ext = "tif"
|
| 72 |
+
debug = False
|
| 73 |
+
tile_source = COMPLETE_URL_TILES
|
| 74 |
+
app_logger.info(f"start_args: tile_source:{tile_source},bounding_box:{bounding_box},zoom:{zoom}.")
|
| 75 |
+
|
| 76 |
+
try:
|
| 77 |
+
import rasterio
|
| 78 |
+
|
| 79 |
+
lon_min, lat_min, lon_max, lat_max = get_lat_lon_coords(bounding_box)
|
| 80 |
+
|
| 81 |
+
with tempfile.TemporaryDirectory() as input_tmp_dir:
|
| 82 |
+
# with tempfile.TemporaryDirectory() as output_tmp_dir:
|
| 83 |
+
output_tmp_dir = input_tmp_dir
|
| 84 |
+
app_logger.info(f'tile_source: {tile_source}!')
|
| 85 |
+
app_logger.info(f'created temporary input/output directory: {input_tmp_dir} => {output_tmp_dir}!')
|
| 86 |
+
pt0, pt1 = bounding_box
|
| 87 |
+
app_logger.info("downloading...")
|
| 88 |
+
img, matrix = download_extent(DEFAULT_TMS, pt0[0], pt0[1], pt1[0], pt1[1], zoom)
|
| 89 |
+
|
| 90 |
+
app_logger.info(f'img: type {type(img)}, len_matrix:{len(matrix)}, matrix {matrix}.')
|
| 91 |
+
app_logger.info(f'img: size (shape if PIL) {img.size}.')
|
| 92 |
+
try:
|
| 93 |
+
np_img = np.array(img)
|
| 94 |
+
app_logger.info(f'img: shape (numpy) {np_img.shape}.')
|
| 95 |
+
except Exception as e_shape:
|
| 96 |
+
app_logger.info(f'e_shape {e_shape}.')
|
| 97 |
+
raise e_shape
|
| 98 |
+
img.save(f"/tmp/downloaded_{pt0[0]}_{pt0[1]}_{pt1[0]}_{pt1[1]}.png")
|
| 99 |
+
app_logger.info("saved PIL image")
|
| 100 |
+
|
| 101 |
+
return img, matrix
|
| 102 |
+
# app_logger.info("prepare writing...")
|
| 103 |
+
# app_logger.info(f'img: type {type(img)}, len_matrix:{len(matrix)}, matrix {matrix}.')
|
| 104 |
+
#
|
| 105 |
+
# rio_output = str(Path(output_tmp_dir) / "downloaded_rio.tif")
|
| 106 |
+
# app_logger.info(f'writing to disk img, output file {rio_output}.')
|
| 107 |
+
# save_geotiff_gdal(img, rio_output, matrix)
|
| 108 |
+
# app_logger.info(f'img written to output file {rio_output}.')
|
| 109 |
+
#
|
| 110 |
+
# source_tiles = os.path.join(input_tmp_dir, f"*.{ext}")
|
| 111 |
+
# suffix_raster_filename = f"{lon_min},{lat_min},{lon_max},{lat_max}_{zoom}"
|
| 112 |
+
# merged_raster_filename = f"merged_{suffix_raster_filename}.{ext}"
|
| 113 |
+
# masked_raster_filename = f"masked_{suffix_raster_filename}.{ext}"
|
| 114 |
+
# output_merged_path = os.path.join(output_tmp_dir, merged_raster_filename)
|
| 115 |
+
#
|
| 116 |
+
# app_logger.info(f"try merging tiles to:{output_merged_path}.")
|
| 117 |
+
# merge_tiles(source_tiles, output_merged_path, input_tmp_dir)
|
| 118 |
+
# app_logger.info(f"Merge complete, try crop...")
|
| 119 |
+
# geojson = get_geojson_square_angles(bounding_box, name=suffix_raster_filename, debug=debug)
|
| 120 |
+
# app_logger.info(f"geojson to convert:{geojson}.")
|
| 121 |
+
#
|
| 122 |
+
# crop_raster_output = crop_raster(output_merged_path, geojson, debug=False)
|
| 123 |
+
# masked_raster = crop_raster_output["masked_raster"]
|
| 124 |
+
# masked_meta = crop_raster_output["masked_meta"]
|
| 125 |
+
# masked_transform = crop_raster_output["masked_transform"]
|
| 126 |
+
#
|
| 127 |
+
# return masked_raster, masked_transform
|
| 128 |
+
|
| 129 |
+
# app_logger.info(f"resampling -32768 values as NaN for file:{masked_raster_filename}.")
|
| 130 |
+
# masked_raster = masked_raster[0].astype(float)
|
| 131 |
+
# masked_raster[masked_raster == NODATA_VALUES] = 0
|
| 132 |
+
# # info
|
| 133 |
+
# nan_count = np.count_nonzero(~np.isnan(masked_raster))
|
| 134 |
+
# total_count = masked_raster.shape[-1] * masked_raster.shape[-2]
|
| 135 |
+
# perc = get_perc(nan_count, total_count)
|
| 136 |
+
# msg = f"img:{masked_raster_filename}, shape:{masked_raster.shape}: found {nan_count} not-NaN values / {total_count} total, %:{perc}."
|
| 137 |
+
# app_logger.info(msg)
|
| 138 |
+
#
|
| 139 |
+
# app_logger.info(f"crop complete, shape:{masked_raster.shape}, dtype:{masked_raster.dtype}. Create RGB image...")
|
| 140 |
+
# # rgb_filename, rgb_path = elaborate_images.get_rgb_prediction_image(masked_raster, slope_cellsize, suffix_raster_filename, output_tmp_dir, debug=debug)
|
| 141 |
+
# # prediction = model_prediction(rgb_path, project_name=model_project_name, version=model_version, api_key=model_api_key, debug=False)
|
| 142 |
+
#
|
| 143 |
+
# mask_vectorizing = np.ones(masked_raster.shape).astype(rasterio.uint8)
|
| 144 |
+
# app_logger.info(f"prediction success, try to geo-referencing it with transform:{masked_transform}.")
|
| 145 |
+
#
|
| 146 |
+
# app_logger.info(
|
| 147 |
+
# f"image/geojson origin matrix:, masked_transform:{masked_transform}: create shapes_generator...")
|
| 148 |
+
# app_logger.info(f"raster mask to vectorize, type:{type(mask_vectorizing)}.")
|
| 149 |
+
# app_logger.info(f"raster mask to vectorize: shape:{mask_vectorizing.shape}, {mask_vectorizing.dtype}.")
|
| 150 |
+
#
|
| 151 |
+
# shapes_generator = ({
|
| 152 |
+
# 'properties': {'raster_val': v}, 'geometry': s}
|
| 153 |
+
# for i, (s, v)
|
| 154 |
+
# in enumerate(shapes(mask_vectorizing, mask=mask_vectorizing, transform=masked_transform))
|
| 155 |
+
# )
|
| 156 |
+
# shapes_list = list(shapes_generator)
|
| 157 |
+
# app_logger.info(f"created {len(shapes_list)} polygons.")
|
| 158 |
+
# gpd_polygonized_raster = GeoDataFrame.from_features(shapes_list, crs="EPSG:3857")
|
| 159 |
+
# app_logger.info(f"created a GeoDataFrame: type {type(gpd_polygonized_raster)}.")
|
| 160 |
+
# geojson = gpd_polygonized_raster.to_json(to_wgs84=True)
|
| 161 |
+
# app_logger.info(f"created geojson: type {type(geojson)}, len:{len(geojson)}.")
|
| 162 |
+
# serialized_geojson = serialize.serialize(geojson)
|
| 163 |
+
# app_logger.info(f"created serialized_geojson: type {type(serialized_geojson)}, len:{len(serialized_geojson)}.")
|
| 164 |
+
# loaded_geojson = json.loads(geojson)
|
| 165 |
+
# app_logger.info(f"loaded_geojson: type {type(loaded_geojson)}, loaded_geojson:{loaded_geojson}.")
|
| 166 |
+
# n_feats = len(loaded_geojson["features"])
|
| 167 |
+
# app_logger.info(f"created geojson: n_feats {n_feats}.")
|
| 168 |
+
#
|
| 169 |
+
# output_geojson = str(Path(ROOT) / "geojson_output.json")
|
| 170 |
+
# with open(output_geojson, "w") as jj_out:
|
| 171 |
+
# app_logger.info(f"writing geojson file to {output_geojson}.")
|
| 172 |
+
# json.dump(loaded_geojson, jj_out)
|
| 173 |
+
# app_logger.info(f"geojson file written to {output_geojson}.")
|
| 174 |
+
#
|
| 175 |
+
# # prediction_georef = helpers.get_prediction_georeferenced(prediction, masked_transform, skip_conditions_list, debug=debug)
|
| 176 |
+
# app_logger.info(f"success on geo-referencing prediction.")
|
| 177 |
+
# # app_logger.info(f"success on creating file {rgb_filename}, now try upload it to bucket_name {bucket_name}...")
|
| 178 |
+
# return {
|
| 179 |
+
# # "uploaded_file_name": rgb_filename,
|
| 180 |
+
# "geojson": loaded_geojson,
|
| 181 |
+
# # "prediction_georef": prediction_georef,
|
| 182 |
+
# "n_total_obj_prediction": n_feats
|
| 183 |
+
# }
|
| 184 |
+
except ImportError as e_import_convert:
|
| 185 |
+
app_logger.error(f"e0:{e_import_convert}.")
|
| 186 |
+
raise e_import_convert
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
if __name__ == '__main__':
|
| 190 |
+
from PIL import Image
|
| 191 |
+
|
| 192 |
+
npy_file = "prediction_masks_46.27697017893455_9.616470336914064_46.11441972281433_9.264907836914064.npy"
|
| 193 |
+
prediction_masks = np.load(Path(PROJECT_ROOT_FOLDER) / "tmp" / "try_by_steps" / "t0" / npy_file)
|
| 194 |
+
|
| 195 |
+
print("#")
|
src/prediction_api/predictors.py
CHANGED
|
@@ -1,13 +1,18 @@
|
|
| 1 |
# Press the green button in the gutter to run the script.
|
| 2 |
-
import
|
|
|
|
| 3 |
from typing import List
|
| 4 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
from src import app_logger, MODEL_FOLDER
|
| 6 |
-
from src.io.
|
|
|
|
| 7 |
from src.prediction_api.sam_onnx import SegmentAnythingONNX
|
| 8 |
-
from src.utilities.constants import MODEL_ENCODER_NAME, ZOOM,
|
| 9 |
from src.utilities.serialize import serialize
|
| 10 |
-
from src.utilities.type_hints import input_float_tuples
|
| 11 |
|
| 12 |
|
| 13 |
models_dict = {"fastsam": {"instance": None}}
|
|
@@ -46,7 +51,7 @@ def load_affine_transformation_from_matrix(matrix_source_coeffs: List):
|
|
| 46 |
app_logger.error(f"exception:{e}, check https://github.com/rasterio/affine project for updates")
|
| 47 |
|
| 48 |
|
| 49 |
-
def samexporter_predict(bbox
|
| 50 |
try:
|
| 51 |
from rasterio.features import shapes
|
| 52 |
from geopandas import GeoDataFrame
|
|
@@ -61,54 +66,114 @@ def samexporter_predict(bbox: input_float_tuples, prompt: list[dict], zoom: floa
|
|
| 61 |
app_logger.info(f"using a {model_name} instance model...")
|
| 62 |
models_instance = models_dict[model_name]["instance"]
|
| 63 |
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
|
|
|
| 67 |
|
| 68 |
-
pt0 = bbox
|
| 69 |
-
|
| 70 |
-
img,
|
|
|
|
| 71 |
|
| 72 |
-
app_logger.info(f"img type {type(img)}, matrix type {type(matrix)}.")
|
| 73 |
-
app_logger.debug(f"matrix values: {serialize(matrix)}.")
|
| 74 |
np_img = np.array(img)
|
| 75 |
-
app_logger.
|
| 76 |
-
|
| 77 |
-
app_logger.info(f"
|
| 78 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
embedding = models_instance.encode(np_img)
|
| 80 |
app_logger.info(f"embedding created, running predict_masks...")
|
| 81 |
prediction_masks = models_instance.predict_masks(embedding, prompt)
|
| 82 |
-
app_logger.
|
| 83 |
app_logger.info(f"predict_masks terminated, prediction masks shape:{prediction_masks.shape}, {prediction_masks.dtype}.")
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
|
| 89 |
mask_unique_values, mask_unique_values_count = serialize(np.unique(mask, return_counts=True))
|
| 90 |
-
app_logger.
|
| 91 |
-
app_logger.
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
app_logger.info(f"image/geojson
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
except ImportError as e:
|
| 114 |
app_logger.error(f"Error trying import module:{e}.")
|
|
|
|
| 1 |
# Press the green button in the gutter to run the script.
|
| 2 |
+
import json
|
| 3 |
+
from pathlib import Path
|
| 4 |
from typing import List
|
| 5 |
|
| 6 |
+
import numpy as np
|
| 7 |
+
import rasterio
|
| 8 |
+
from PIL import Image
|
| 9 |
+
|
| 10 |
from src import app_logger, MODEL_FOLDER
|
| 11 |
+
from src.io.tiles_to_tiff import convert
|
| 12 |
+
from src.io.tms2geotiff import save_geotiff_gdal
|
| 13 |
from src.prediction_api.sam_onnx import SegmentAnythingONNX
|
| 14 |
+
from src.utilities.constants import MODEL_ENCODER_NAME, ZOOM, MODEL_DECODER_NAME, ROOT
|
| 15 |
from src.utilities.serialize import serialize
|
|
|
|
| 16 |
|
| 17 |
|
| 18 |
models_dict = {"fastsam": {"instance": None}}
|
|
|
|
| 51 |
app_logger.error(f"exception:{e}, check https://github.com/rasterio/affine project for updates")
|
| 52 |
|
| 53 |
|
| 54 |
+
def samexporter_predict(bbox, prompt: list[dict], zoom: float = ZOOM, model_name: str = "fastsam") -> dict:
|
| 55 |
try:
|
| 56 |
from rasterio.features import shapes
|
| 57 |
from geopandas import GeoDataFrame
|
|
|
|
| 66 |
app_logger.info(f"using a {model_name} instance model...")
|
| 67 |
models_instance = models_dict[model_name]["instance"]
|
| 68 |
|
| 69 |
+
img, matrix = convert(
|
| 70 |
+
bounding_box=bbox,
|
| 71 |
+
zoom=int(zoom)
|
| 72 |
+
)
|
| 73 |
|
| 74 |
+
pt0, pt1 = bbox
|
| 75 |
+
rio_output = f"/tmp/downloaded_rio_{pt0[0]}_{pt0[1]}_{pt1[0]}_{pt1[1]}.tif"
|
| 76 |
+
save_geotiff_gdal(img, rio_output, matrix)
|
| 77 |
+
app_logger.info(f"saved downloaded geotiff image to {rio_output}...")
|
| 78 |
|
|
|
|
|
|
|
| 79 |
np_img = np.array(img)
|
| 80 |
+
app_logger.info(f"## img type {type(np_img)}, prompt:{prompt}.")
|
| 81 |
+
|
| 82 |
+
app_logger.info(f"onnxruntime input shape/size (shape if PIL) {np_img.size},"
|
| 83 |
+
f"start to initialize SamGeo instance:")
|
| 84 |
+
try:
|
| 85 |
+
app_logger.info(f"onnxruntime input shape (NUMPY) {np_img.shape}.")
|
| 86 |
+
except Exception as e_shape:
|
| 87 |
+
app_logger.error(f"e_shape:{e_shape}.")
|
| 88 |
+
app_logger.info(f"use {model_name} model, ENCODER model {MODEL_ENCODER_NAME} and"
|
| 89 |
+
f" {MODEL_DECODER_NAME} from {MODEL_FOLDER}): model instantiated, creating embedding...")
|
| 90 |
embedding = models_instance.encode(np_img)
|
| 91 |
app_logger.info(f"embedding created, running predict_masks...")
|
| 92 |
prediction_masks = models_instance.predict_masks(embedding, prompt)
|
| 93 |
+
app_logger.info(f"predict_masks terminated...")
|
| 94 |
app_logger.info(f"predict_masks terminated, prediction masks shape:{prediction_masks.shape}, {prediction_masks.dtype}.")
|
| 95 |
+
pt0, pt1 = bbox
|
| 96 |
+
prediction_masks_output = f"/tmp/prediction_masks_{pt0[0]}_{pt0[1]}_{pt1[0]}_{pt1[1]}.npy"
|
| 97 |
+
np.save(
|
| 98 |
+
prediction_masks_output,
|
| 99 |
+
prediction_masks, allow_pickle=True, fix_imports=True
|
| 100 |
+
)
|
| 101 |
+
app_logger.info(f"saved prediction_masks:{prediction_masks_output}.")
|
| 102 |
+
|
| 103 |
+
# mask = np.zeros((prediction_masks.shape[2], prediction_masks.shape[3]), dtype=np.uint8)
|
| 104 |
+
# app_logger.info(f"output mask shape:{mask.shape}, {mask.dtype}.")
|
| 105 |
+
# ## todo: convert to geojson directly within the loop to avoid merging two objects
|
| 106 |
+
# for n, m in enumerate(prediction_masks[0, :, :, :]):
|
| 107 |
+
# app_logger.info(f"## {n} mask => m shape:{mask.shape}, {mask.dtype}.")
|
| 108 |
+
# mask[m > 0.0] = 255
|
| 109 |
+
prediction_masks0 = prediction_masks[0]
|
| 110 |
+
app_logger.info(f"prediction_masks0 shape:{prediction_masks0.shape}.")
|
| 111 |
+
|
| 112 |
+
try:
|
| 113 |
+
pmf = np.sum(prediction_masks0, axis=0).astype(np.uint8)
|
| 114 |
+
except Exception as e_sum_pmf:
|
| 115 |
+
app_logger.error(f"e_sum_pmf:{e_sum_pmf}.")
|
| 116 |
+
pmf = prediction_masks0[0]
|
| 117 |
+
app_logger.info(f"creating pil image from prediction mask with shape {pmf.shape}.")
|
| 118 |
+
pil_pmf = Image.fromarray(pmf)
|
| 119 |
+
pil_pmf_output = f"/tmp/pil_pmf_{pmf.shape[0]}_{pmf.shape[1]}.png"
|
| 120 |
+
pil_pmf.save(pil_pmf_output)
|
| 121 |
+
app_logger.info(f"saved pil_pmf:{pil_pmf_output}.")
|
| 122 |
+
|
| 123 |
+
mask = np.zeros(pmf.shape, dtype=np.uint8)
|
| 124 |
+
mask[pmf > 0] = 255
|
| 125 |
+
|
| 126 |
+
# cv2.imwrite(f"/tmp/cv2_mask_predicted_{mask.shape[0]}_{mask.shape[1]}_{mask.shape[2]}.png", mask)
|
| 127 |
+
pil_mask = Image.fromarray(mask)
|
| 128 |
+
pil_mask_predicted_output = f"/tmp/pil_mask_predicted_{mask.shape[0]}_{mask.shape[1]}.png"
|
| 129 |
+
pil_mask.save(pil_mask_predicted_output)
|
| 130 |
+
app_logger.info(f"saved pil_mask_predicted:{pil_mask_predicted_output}.")
|
| 131 |
|
| 132 |
mask_unique_values, mask_unique_values_count = serialize(np.unique(mask, return_counts=True))
|
| 133 |
+
app_logger.info(f"mask_unique_values:{mask_unique_values}.")
|
| 134 |
+
app_logger.info(f"mask_unique_values_count:{mask_unique_values_count}.")
|
| 135 |
+
|
| 136 |
+
app_logger.info(f"read geotiff:{rio_output}: create shapes_generator...")
|
| 137 |
+
# app_logger.info(f"image/geojson transform:{transform}: create shapes_generator...")
|
| 138 |
+
with rasterio.open(rio_output, "r", driver="GTiff") as rio_src:
|
| 139 |
+
band = rio_src.read()
|
| 140 |
+
try:
|
| 141 |
+
transform = load_affine_transformation_from_matrix(matrix)
|
| 142 |
+
app_logger.info(f"geotiff band:{band.shape}, type: {type(band)}, dtype: {band.dtype}.")
|
| 143 |
+
app_logger.info(f"geotiff band:{mask.shape}.")
|
| 144 |
+
app_logger.info(f"transform from matrix:{transform}.")
|
| 145 |
+
app_logger.info(f"rio_src crs:{rio_src.crs}.")
|
| 146 |
+
app_logger.info(f"rio_src transform:{rio_src.transform}.")
|
| 147 |
+
except Exception as e_shape_band:
|
| 148 |
+
app_logger.error(f"e_shape_band:{e_shape_band}.")
|
| 149 |
+
raise e_shape_band
|
| 150 |
+
# mask_band = band != 0
|
| 151 |
+
shapes_generator = ({
|
| 152 |
+
'properties': {'raster_val': v}, 'geometry': s}
|
| 153 |
+
for i, (s, v)
|
| 154 |
+
# in enumerate(shapes(mask, mask=(band != 0), transform=rio_src.transform))
|
| 155 |
+
# use mask=None to avoid using source
|
| 156 |
+
in enumerate(shapes(mask, mask=None, transform=rio_src.transform))
|
| 157 |
+
)
|
| 158 |
+
app_logger.info(f"created shapes_generator.")
|
| 159 |
+
shapes_list = list(shapes_generator)
|
| 160 |
+
app_logger.info(f"created {len(shapes_list)} polygons.")
|
| 161 |
+
gpd_polygonized_raster = GeoDataFrame.from_features(shapes_list, crs="EPSG:3857")
|
| 162 |
+
app_logger.info(f"created a GeoDataFrame...")
|
| 163 |
+
geojson = gpd_polygonized_raster.to_json(to_wgs84=True)
|
| 164 |
+
app_logger.info(f"created geojson...")
|
| 165 |
+
|
| 166 |
+
output_geojson = str(Path(ROOT) / "geojson_output.json")
|
| 167 |
+
with open(output_geojson, "w") as jj_out:
|
| 168 |
+
app_logger.info(f"writing geojson file to {output_geojson}.")
|
| 169 |
+
json.dump(json.loads(geojson), jj_out)
|
| 170 |
+
app_logger.info(f"geojson file written to {output_geojson}.")
|
| 171 |
+
|
| 172 |
+
return {
|
| 173 |
+
"geojson": geojson,
|
| 174 |
+
"n_shapes_geojson": len(shapes_list),
|
| 175 |
+
"n_predictions": len(prediction_masks),
|
| 176 |
+
# "n_pixels_predictions": zip_arrays(mask_unique_values, mask_unique_values_count),
|
| 177 |
+
}
|
| 178 |
except ImportError as e:
|
| 179 |
app_logger.error(f"Error trying import module:{e}.")
|
src/prediction_api/sam_onnx.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
|
|
| 1 |
from copy import deepcopy
|
| 2 |
|
| 3 |
import cv2
|
|
@@ -5,6 +6,7 @@ import numpy as np
|
|
| 5 |
import onnxruntime
|
| 6 |
|
| 7 |
from src import app_logger
|
|
|
|
| 8 |
|
| 9 |
|
| 10 |
class SegmentAnythingONNX:
|
|
@@ -145,12 +147,35 @@ class SegmentAnythingONNX:
|
|
| 145 |
batch_masks = []
|
| 146 |
for mask_id in range(masks.shape[1]):
|
| 147 |
mask = masks[batch, mask_id]
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 154 |
batch_masks.append(mask)
|
| 155 |
output_masks.append(batch_masks)
|
| 156 |
return np.array(output_masks)
|
|
@@ -172,12 +197,36 @@ class SegmentAnythingONNX:
|
|
| 172 |
[0, 0, 1],
|
| 173 |
]
|
| 174 |
)
|
| 175 |
-
|
| 176 |
-
cv_image
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 181 |
|
| 182 |
encoder_inputs = {
|
| 183 |
self.encoder_input_name: cv_image.astype(np.float32),
|
|
|
|
| 1 |
+
import json
|
| 2 |
from copy import deepcopy
|
| 3 |
|
| 4 |
import cv2
|
|
|
|
| 6 |
import onnxruntime
|
| 7 |
|
| 8 |
from src import app_logger
|
| 9 |
+
from src.utilities.serialize import serialize
|
| 10 |
|
| 11 |
|
| 12 |
class SegmentAnythingONNX:
|
|
|
|
| 147 |
batch_masks = []
|
| 148 |
for mask_id in range(masks.shape[1]):
|
| 149 |
mask = masks[batch, mask_id]
|
| 150 |
+
try:
|
| 151 |
+
try:
|
| 152 |
+
app_logger.info(f"mask_shape transform_masks:{mask.shape}, dtype:{mask.dtype}.")
|
| 153 |
+
except Exception as e_mask_shape_transform_masks:
|
| 154 |
+
app_logger.error(f"e_mask_shape_transform_masks:{e_mask_shape_transform_masks}.")
|
| 155 |
+
# raise e_mask_shape_transform_masks
|
| 156 |
+
output_filename = f"2_cv2img_{'_'.join([str(s) for s in mask.shape])}.npy"
|
| 157 |
+
np.save(output_filename, np.array(mask), allow_pickle=True, fix_imports=True)
|
| 158 |
+
app_logger.info(f"written: /tmp/{output_filename} ...")
|
| 159 |
+
with open("/tmp/2_args.json", "w") as jj_out_dst:
|
| 160 |
+
json.dump({
|
| 161 |
+
"transform_matrix": serialize(transform_matrix),
|
| 162 |
+
"M": serialize(transform_matrix[:2]),
|
| 163 |
+
"original_size": serialize(original_size),
|
| 164 |
+
"dsize": serialize((original_size[1], original_size[0])),
|
| 165 |
+
"flags": cv2.INTER_LINEAR
|
| 166 |
+
}, jj_out_dst)
|
| 167 |
+
app_logger.info(f"written: /tmp/jj_out.json")
|
| 168 |
+
mask = cv2.warpAffine(
|
| 169 |
+
mask,
|
| 170 |
+
transform_matrix[:2],
|
| 171 |
+
(original_size[1], original_size[0]),
|
| 172 |
+
flags=cv2.INTER_LINEAR,
|
| 173 |
+
)
|
| 174 |
+
except Exception as e_warp_affine1:
|
| 175 |
+
app_logger.error(f"e_warp_affine1 mask shape:{mask.shape}, dtype:{mask.dtype}.")
|
| 176 |
+
app_logger.error(f"e_warp_affine1 transform_matrix:{transform_matrix}, [:2] {transform_matrix[:2]}.")
|
| 177 |
+
app_logger.error(f"e_warp_affine1 original_size:{original_size}.")
|
| 178 |
+
raise e_warp_affine1
|
| 179 |
batch_masks.append(mask)
|
| 180 |
output_masks.append(batch_masks)
|
| 181 |
return np.array(output_masks)
|
|
|
|
| 197 |
[0, 0, 1],
|
| 198 |
]
|
| 199 |
)
|
| 200 |
+
try:
|
| 201 |
+
np_cv_image = np.array(cv_image)
|
| 202 |
+
try:
|
| 203 |
+
app_logger.info(f"cv_image shape_encode:{np_cv_image.shape}, dtype:{np_cv_image.dtype}.")
|
| 204 |
+
except Exception as e_cv_image_shape_encode:
|
| 205 |
+
app_logger.error(f"e_cv_image_shape_encode:{e_cv_image_shape_encode}.")
|
| 206 |
+
# raise e_cv_image_shape_encode
|
| 207 |
+
output_filename = f"/tmp/1_cv2img_{'_'.join([str(s) for s in np_cv_image.shape])}.npy"
|
| 208 |
+
np.save(output_filename, np_cv_image, allow_pickle=True, fix_imports=True)
|
| 209 |
+
app_logger.info(f"written: /tmp/{output_filename} ...")
|
| 210 |
+
with open("/tmp/1_args.json", "w") as jj_out_dst:
|
| 211 |
+
json.dump({
|
| 212 |
+
"transform_matrix": serialize(transform_matrix),
|
| 213 |
+
"M": serialize(transform_matrix[:2]),
|
| 214 |
+
"flags": cv2.INTER_LINEAR
|
| 215 |
+
}, jj_out_dst)
|
| 216 |
+
app_logger.info(f"written: /tmp/jj_out.json")
|
| 217 |
+
cv_image = cv2.warpAffine(
|
| 218 |
+
cv_image,
|
| 219 |
+
transform_matrix[:2],
|
| 220 |
+
(self.input_size[1], self.input_size[0]),
|
| 221 |
+
flags=cv2.INTER_LINEAR,
|
| 222 |
+
)
|
| 223 |
+
except Exception as e_warp_affine2:
|
| 224 |
+
app_logger.error(f"e_warp_affine2:{e_warp_affine2}.")
|
| 225 |
+
np_cv_image = np.array(cv_image)
|
| 226 |
+
app_logger.error(f"e_warp_affine2 cv_image shape:{np_cv_image.shape}, dtype:{np_cv_image.dtype}.")
|
| 227 |
+
app_logger.error(f"e_warp_affine2 transform_matrix:{transform_matrix}, [:2] {transform_matrix[:2]}")
|
| 228 |
+
app_logger.error(f"e_warp_affine2 self.input_size:{self.input_size}.")
|
| 229 |
+
raise e_warp_affine2
|
| 230 |
|
| 231 |
encoder_inputs = {
|
| 232 |
self.encoder_input_name: cv_image.astype(np.float32),
|
src/utilities/constants.py
CHANGED
|
@@ -1,9 +1,14 @@
|
|
| 1 |
"""Project constants"""
|
| 2 |
CHANNEL_EXAGGERATIONS_LIST = [2.5, 1.1, 2.0]
|
| 3 |
-
INPUT_CRS_STRING = "EPSG:
|
| 4 |
-
OUTPUT_CRS_STRING = "EPSG:
|
|
|
|
|
|
|
|
|
|
| 5 |
ROOT = "/tmp"
|
| 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},
|
|
@@ -33,3 +38,4 @@ WKT_3857 = 'PROJCS["WGS 84 / Pseudo-Mercator",GEOGCS["WGS 84",DATUM["WGS_1984",S
|
|
| 33 |
WKT_3857 += 'AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]],PROJECTION["Mercator_1SP"],PARAMETER["central_meridian",0],'
|
| 34 |
WKT_3857 += 'PARAMETER["scale_factor",1],PARAMETER["false_easting",0],PARAMETER["false_northing",0],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AXIS["X",EAST],AXIS["Y",NORTH],EXTENSION["PROJ4",'
|
| 35 |
WKT_3857 += '"+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs"],AUTHORITY["EPSG","3857"]]'
|
|
|
|
|
|
| 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 |
+
# DOMAIN_URL_TILES = "elevation-tiles-prod-eu.s3.eu-central-1.amazonaws.com"
|
| 6 |
+
# RELATIVE_URL_TILES = "geotiff/{z}/{x}/{y}.tif"
|
| 7 |
+
# COMPLETE_URL_TILES = f"https://{DOMAIN_URL_TILES}/{RELATIVE_URL_TILES}"
|
| 8 |
ROOT = "/tmp"
|
| 9 |
NODATA_VALUES = -32768
|
| 10 |
+
MODEL_PROJECT_NAME = "surferdtm"
|
| 11 |
+
MODEL_VERSION = 4
|
| 12 |
SKIP_CONDITIONS_LIST = [{"skip_key": "confidence", "skip_value": 0.5, "skip_condition": "major"}]
|
| 13 |
FEATURE_SQUARE_TEMPLATE = [
|
| 14 |
{'type': 'Feature', 'properties': {'id': 1},
|
|
|
|
| 38 |
WKT_3857 += 'AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]],PROJECTION["Mercator_1SP"],PARAMETER["central_meridian",0],'
|
| 39 |
WKT_3857 += 'PARAMETER["scale_factor",1],PARAMETER["false_easting",0],PARAMETER["false_northing",0],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AXIS["X",EAST],AXIS["Y",NORTH],EXTENSION["PROJ4",'
|
| 40 |
WKT_3857 += '"+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs"],AUTHORITY["EPSG","3857"]]'
|
| 41 |
+
COMPLETE_URL_TILES = DEFAULT_TMS
|
src/utilities/type_hints.py
CHANGED
|
@@ -1,8 +1,24 @@
|
|
| 1 |
"""custom type hints"""
|
| 2 |
from typing import List, Tuple
|
|
|
|
| 3 |
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
ts_dict_str2 = dict[str, str]
|
| 8 |
ts_dict_str3 = dict[str, str, any]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
"""custom type hints"""
|
| 2 |
from typing import List, Tuple
|
| 3 |
+
import numpy as np
|
| 4 |
|
| 5 |
+
ts_list_str1 = list[str]
|
| 6 |
+
ts_http2 = tuple[ts_list_str1, ts_list_str1]
|
| 7 |
+
ts_list_float2 = list[float, float]
|
| 8 |
+
ts_llist_float2 = list[ts_list_float2, ts_list_float2]
|
| 9 |
+
ts_geojson = dict[str, str, dict[str, dict[str]], list[str, dict[int], dict[str, list]]]
|
| 10 |
+
ts_float64_1 = tuple[np.float64, np.float64, np.float64, np.float64, np.float64, np.float64]
|
| 11 |
+
ts_float64_2 = tuple[np.float64, np.float64, np.float64, np.float64, np.float64, np.float64, np.float64]
|
| 12 |
ts_dict_str2 = dict[str, str]
|
| 13 |
ts_dict_str3 = dict[str, str, any]
|
| 14 |
+
ts_dict_str2b = dict[str, any]
|
| 15 |
+
ts_ddict1 = dict[str, dict[str, any], dict, dict, any]
|
| 16 |
+
ts_ddict2 = dict[str, dict, dict[str, list]]
|
| 17 |
+
ts_tuple_str2 = tuple[str, str]
|
| 18 |
+
ts_tuple_arr2 = tuple[np.ndarray, np.ndarray]
|
| 19 |
+
ts_tuple_flat2 = tuple[float, float]
|
| 20 |
+
ts_tuple_flat4 = tuple[float, float, float, float]
|
| 21 |
+
ts_list_float4 = list[float, float, float, float]
|
| 22 |
+
ts_tuple_int4 = tuple[int, int, int, int]
|
| 23 |
+
ts_llist2 = list[[int, int], [int, int]]
|
| 24 |
+
ts_ddict3 = dict[list[dict[float | int | str]], dict[float | int]]
|
src/utilities/utilities.py
CHANGED
|
@@ -1,5 +1,8 @@
|
|
| 1 |
"""Various utilities (logger, time benchmark, args dump, numerical and stats info)"""
|
|
|
|
|
|
|
| 2 |
from src import app_logger
|
|
|
|
| 3 |
|
| 4 |
|
| 5 |
def is_base64(sb):
|
|
@@ -57,11 +60,10 @@ def get_constants(event: dict, debug=False) -> dict:
|
|
| 57 |
"""
|
| 58 |
import json
|
| 59 |
|
| 60 |
-
local_logger = setup_logging(debug)
|
| 61 |
try:
|
| 62 |
body = event["body"]
|
| 63 |
except Exception as e_constants1:
|
| 64 |
-
|
| 65 |
body = event
|
| 66 |
|
| 67 |
if isinstance(body, str):
|
|
@@ -69,11 +71,10 @@ def get_constants(event: dict, debug=False) -> dict:
|
|
| 69 |
|
| 70 |
try:
|
| 71 |
debug = body["debug"]
|
| 72 |
-
|
| 73 |
-
local_logger = setup_logging(debug)
|
| 74 |
except KeyError:
|
| 75 |
-
|
| 76 |
-
|
| 77 |
|
| 78 |
try:
|
| 79 |
return {
|
|
@@ -82,8 +83,102 @@ def get_constants(event: dict, debug=False) -> dict:
|
|
| 82 |
"debug": debug
|
| 83 |
}
|
| 84 |
except KeyError as e_key_constants2:
|
| 85 |
-
|
| 86 |
raise KeyError(f"e_key_constants2:{e_key_constants2}.")
|
| 87 |
except Exception as e_constants2:
|
| 88 |
-
|
| 89 |
raise e_constants2
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
"""Various utilities (logger, time benchmark, args dump, numerical and stats info)"""
|
| 2 |
+
import numpy as np
|
| 3 |
+
|
| 4 |
from src import app_logger
|
| 5 |
+
from src.utilities.type_hints import ts_float64_1, ts_float64_2
|
| 6 |
|
| 7 |
|
| 8 |
def is_base64(sb):
|
|
|
|
| 60 |
"""
|
| 61 |
import json
|
| 62 |
|
|
|
|
| 63 |
try:
|
| 64 |
body = event["body"]
|
| 65 |
except Exception as e_constants1:
|
| 66 |
+
app_logger.error(f"e_constants1:{e_constants1}.")
|
| 67 |
body = event
|
| 68 |
|
| 69 |
if isinstance(body, str):
|
|
|
|
| 71 |
|
| 72 |
try:
|
| 73 |
debug = body["debug"]
|
| 74 |
+
app_logger.info(f"re-try get debug value:{debug}, log_level:{app_logger.level}.")
|
|
|
|
| 75 |
except KeyError:
|
| 76 |
+
app_logger.error("get_constants:: no debug key, pass...")
|
| 77 |
+
app_logger.info(f"constants debug:{debug}, log_level:{app_logger.level}, body:{body}.")
|
| 78 |
|
| 79 |
try:
|
| 80 |
return {
|
|
|
|
| 83 |
"debug": debug
|
| 84 |
}
|
| 85 |
except KeyError as e_key_constants2:
|
| 86 |
+
app_logger.error(f"e_key_constants2:{e_key_constants2}.")
|
| 87 |
raise KeyError(f"e_key_constants2:{e_key_constants2}.")
|
| 88 |
except Exception as e_constants2:
|
| 89 |
+
app_logger.error(f"e_constants2:{e_constants2}.")
|
| 90 |
raise e_constants2
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def get_rasters_info(rasters_list:list, names_list:list, title:str="", debug:bool=False) -> str:
|
| 94 |
+
"""
|
| 95 |
+
Analyze numpy arrays' list to extract a string containing some useful information. For every raster:
|
| 96 |
+
|
| 97 |
+
- type of raster
|
| 98 |
+
- raster.dtype if that's instance of np.ndarray
|
| 99 |
+
- raster shape
|
| 100 |
+
- min of raster value, over all axis (flattening the array)
|
| 101 |
+
- max of raster value, over all axis (flattening the array)
|
| 102 |
+
- mean of raster value, over all axis (flattening the array)
|
| 103 |
+
- median of raster value, over all axis (flattening the array)
|
| 104 |
+
- standard deviation of raster value, over all axis (flattening the array)
|
| 105 |
+
- variance of raster value, over all axis (flattening the array)
|
| 106 |
+
|
| 107 |
+
Raises:
|
| 108 |
+
ValueError if raster_list and names_list have a different number of elements
|
| 109 |
+
|
| 110 |
+
Args:
|
| 111 |
+
rasters_list: list of numpy array raster to analyze
|
| 112 |
+
names_list: string list of numpy array
|
| 113 |
+
title: title of current analytic session
|
| 114 |
+
debug: logging debug argument
|
| 115 |
+
|
| 116 |
+
Returns:
|
| 117 |
+
str: the collected information
|
| 118 |
+
|
| 119 |
+
"""
|
| 120 |
+
|
| 121 |
+
msg = f"get_rasters_info::title:{title},\n"
|
| 122 |
+
if not len(rasters_list) == len(names_list):
|
| 123 |
+
msg = "raster_list and names_list should have the same number of elements:\n"
|
| 124 |
+
msg += f"len(rasters_list):{len(rasters_list)}, len(names_list):{len(names_list)}."
|
| 125 |
+
raise ValueError(msg)
|
| 126 |
+
try:
|
| 127 |
+
for raster, name in zip(rasters_list, names_list):
|
| 128 |
+
try:
|
| 129 |
+
if isinstance(raster, np.ndarray):
|
| 130 |
+
shape_or_len = raster.shape
|
| 131 |
+
elif isinstance(raster, list):
|
| 132 |
+
shape_or_len = len(raster)
|
| 133 |
+
else:
|
| 134 |
+
raise ValueError(f"wrong argument type:{raster}, variable:{raster}.")
|
| 135 |
+
zmin, zmax, zmean, zmedian, zstd, zvar = get_stats_raster(raster, debug=debug)
|
| 136 |
+
msg += "name:{}:type:{},dtype:{},shape:{},min:{},max:{},mean:{},median:{},std:{},var:{}\n".format(
|
| 137 |
+
name, type(raster), raster.dtype if isinstance(raster, np.ndarray) else None, shape_or_len, zmin,
|
| 138 |
+
zmax, zmean, zmedian, zstd, zvar
|
| 139 |
+
)
|
| 140 |
+
except Exception as get_rasters_types_e:
|
| 141 |
+
msg = f"get_rasters_types_e::{get_rasters_types_e}, type_raster:{type(raster)}."
|
| 142 |
+
app_logger.error(msg)
|
| 143 |
+
raise ValueError(msg)
|
| 144 |
+
except IndexError as get_rasters_types_ie:
|
| 145 |
+
app_logger.error(f"get_rasters_types::len:rasters_list:{len(rasters_list)}, len_names_list:{len(names_list)}.")
|
| 146 |
+
raise get_rasters_types_ie
|
| 147 |
+
return msg + "\n=============================\n"
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
def get_stats_raster(raster: np.ndarray, get_rms:bool=False, debug:bool=False) -> ts_float64_1 or ts_float64_2:
|
| 151 |
+
"""
|
| 152 |
+
Analyze a numpy arrays to extract a tuple of useful information:
|
| 153 |
+
|
| 154 |
+
- type of raster
|
| 155 |
+
- raster.dtype if that's instance of np.ndarray
|
| 156 |
+
- raster shape
|
| 157 |
+
- min of raster value, over all axis (flattening the array)
|
| 158 |
+
- max of raster value, over all axis (flattening the array)
|
| 159 |
+
- mean of raster value, over all axis (flattening the array)
|
| 160 |
+
- median of raster value, over all axis (flattening the array)
|
| 161 |
+
- standard deviation of raster value, over all axis (flattening the array)
|
| 162 |
+
- variance of raster value, over all axis (flattening the array)
|
| 163 |
+
|
| 164 |
+
Args:
|
| 165 |
+
raster: numpy array to analyze
|
| 166 |
+
get_rms: bool to get Root Mean Square Error
|
| 167 |
+
debug: logging debug argument
|
| 168 |
+
|
| 169 |
+
Returns:
|
| 170 |
+
tuple: float values (min, max, mean, median, standard deviation, variance of raster)
|
| 171 |
+
|
| 172 |
+
"""
|
| 173 |
+
std = np.nanstd(raster)
|
| 174 |
+
if get_rms:
|
| 175 |
+
try:
|
| 176 |
+
rms = np.sqrt(np.nanmean(np.square(raster)))
|
| 177 |
+
except Exception as rms_e:
|
| 178 |
+
rms = None
|
| 179 |
+
app_logger.error(f"get_stats_raster::rms_Exception:{rms_e}.")
|
| 180 |
+
app_logger.info(f"nanmin:{type(np.nanmin(raster))}.")
|
| 181 |
+
return (np.nanmin(raster), np.nanmax(raster), np.nanmean(raster), np.nanmedian(raster), std,
|
| 182 |
+
np.nanvar(raster), rms)
|
| 183 |
+
return (np.nanmin(raster), np.nanmax(raster), np.nanmean(raster), np.nanmedian(raster), np.nanstd(raster),
|
| 184 |
+
np.nanvar(raster))
|