aletrn commited on
Commit
4d26ef2
·
1 Parent(s): 2779d38

[refactor] refactor and remove unused code, remove unused deps

Browse files
requirements.txt CHANGED
@@ -2,10 +2,10 @@ aws-lambda-powertools
2
  awslambdaric
3
  bson
4
  geopandas
5
- httpx
6
  jmespath
7
  numpy
8
  onnxruntime
9
  opencv-python
10
  pillow
11
- rasterio
 
 
2
  awslambdaric
3
  bson
4
  geopandas
 
5
  jmespath
6
  numpy
7
  onnxruntime
8
  opencv-python
9
  pillow
10
+ rasterio
11
+ requests
requirements_dev.txt CHANGED
@@ -1,5 +1,3 @@
1
- aiofiles
2
- aiohttp
3
  aws-lambda-powertools
4
  awslambdaric
5
  bson
@@ -9,7 +7,5 @@ numpy
9
  onnxruntime
10
  opencv-python
11
  pillow
12
- pyproj
13
  rasterio
14
  requests
15
- shapely
 
 
 
1
  aws-lambda-powertools
2
  awslambdaric
3
  bson
 
7
  onnxruntime
8
  opencv-python
9
  pillow
 
10
  rasterio
11
  requests
 
src/app.py CHANGED
@@ -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.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
@@ -38,59 +38,36 @@ def get_response(status: int, start_time: float, request_id: str, response_body:
38
  "body": json.dumps(response_body),
39
  "isBase64Encoded": False
40
  }
41
- app_logger.info(f"response type:{type(response)} => {response}.")
42
  return json.dumps(response)
43
 
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]
56
  zoom = int(request_input["zoom"])
57
  for prompt in request_input["prompt"]:
58
- app_logger.info(f"current prompt: {type(prompt)}, value:{prompt}.")
59
  data = prompt["data"]
60
- # if prompt["type"] == "rectangle":
61
- # app_logger.info(f"current data points: {type(data)}, value:{data}.")
62
- # data_ne = data["ne"]
63
- # app_logger.info(f"current data_ne point: {type(data_ne)}, value:{data_ne}.")
64
- # data_sw = data["sw"]
65
- # app_logger.info(f"current data_sw point: {type(data_sw)}, value:{data_sw}.")
66
- #
67
- # diff_pixel_coords_origin_data_ne = get_latlng_to_pixel_coordinates(ne, sw, data_ne, zoom, "ne")
68
- # app_logger.info(f'current diff prompt ne: {type(data)}, {data} => {diff_pixel_coords_origin_data_ne}.')
69
- # diff_pixel_coords_origin_data_sw = get_latlng_to_pixel_coordinates(ne, sw, data_sw, zoom, "sw")
70
- # app_logger.info(f'current diff prompt sw: {type(data)}, {data} => {diff_pixel_coords_origin_data_sw}.')
71
- # prompt["data"] = [
72
- # diff_pixel_coords_origin_data_ne["x"], diff_pixel_coords_origin_data_ne["y"],
73
- # diff_pixel_coords_origin_data_sw["x"], diff_pixel_coords_origin_data_sw["y"]
74
- # ]
75
- # # rect_diffs_input = str(Path(ROOT) / "rect_diffs_input.json")
76
- # # with open(rect_diffs_input, "w") as jj_out3:
77
- # # json.dump({
78
- # # "prompt_data": serialize(prompt["data"]),
79
- # # "diff_pixel_coords_origin_data_ne": serialize(diff_pixel_coords_origin_data_ne),
80
- # # "diff_pixel_coords_origin_data_sw": serialize(diff_pixel_coords_origin_data_sw),
81
- # # }, jj_out3)
82
- # # app_logger.info(f"written json:{rect_diffs_input}.")
83
  if prompt["type"] == "point":
84
  current_point = get_latlng_to_pixel_coordinates(ne, sw, data, zoom, "point")
85
- app_logger.info(f"current prompt: {type(current_point)}, value:{current_point}.")
86
  new_prompt_data = [current_point['x'], current_point['y']]
87
- app_logger.info(f"new_prompt_data: {type(new_prompt_data)}, value:{new_prompt_data}.")
88
  prompt["data"] = new_prompt_data
89
  else:
90
  raise ValueError("valid prompt type is only 'point'")
91
 
92
- app_logger.info(f"bbox => {bbox}.")
93
- app_logger.info(f'## request_input-prompt updated => {request_input["prompt"]}.')
94
 
95
  app_logger.info(f"unpacking elaborated {request_input}...")
96
  return {
@@ -117,18 +94,18 @@ def lambda_handler(event: dict, context: LambdaContext):
117
  app_logger.error(f"e_constants1:{e_constants1}.")
118
  body = event
119
 
120
- app_logger.info(f"body, #1: {type(body)}, {body}...")
121
 
122
  if isinstance(body, str):
123
  body_decoded_str = base64_decode(body)
124
- app_logger.info(f"body_decoded_str: {type(body_decoded_str)}, {body_decoded_str}...")
125
  body = json.loads(body_decoded_str)
126
 
127
  app_logger.info(f"body, #2: {type(body)}, {body}...")
128
 
129
  try:
130
  prompt_latlng = body["prompt"]
131
- app_logger.info(f"prompt_latlng:{prompt_latlng}.")
132
  body_request = get_parsed_bbox_points(body)
133
  app_logger.info(f"body_request=> {type(body_request)}, {body_request}.")
134
  body_response = samexporter_predict(body_request["bbox"], body_request["prompt"], body_request["zoom"])
 
27
  str: json response
28
 
29
  """
30
+ app_logger.debug(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
 
38
  "body": json.dumps(response_body),
39
  "isBase64Encoded": False
40
  }
41
+ app_logger.debug(f"response type:{type(response)} => {response}.")
42
  return json.dumps(response)
43
 
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.debug(f"request bbox: {type(bbox)}, value:{bbox}.")
49
  ne = bbox["ne"]
50
  sw = bbox["sw"]
51
+ app_logger.debug(f"request ne: {type(ne)}, value:{ne}.")
52
+ app_logger.debug(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]
56
  zoom = int(request_input["zoom"])
57
  for prompt in request_input["prompt"]:
58
+ app_logger.debug(f"current prompt: {type(prompt)}, value:{prompt}.")
59
  data = prompt["data"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  if prompt["type"] == "point":
61
  current_point = get_latlng_to_pixel_coordinates(ne, sw, data, zoom, "point")
62
+ app_logger.debug(f"current prompt: {type(current_point)}, value:{current_point}.")
63
  new_prompt_data = [current_point['x'], current_point['y']]
64
+ app_logger.debug(f"new_prompt_data: {type(new_prompt_data)}, value:{new_prompt_data}.")
65
  prompt["data"] = new_prompt_data
66
  else:
67
  raise ValueError("valid prompt type is only 'point'")
68
 
69
+ app_logger.debug(f"bbox => {bbox}.")
70
+ app_logger.debug(f'## request_input-prompt updated => {request_input["prompt"]}.')
71
 
72
  app_logger.info(f"unpacking elaborated {request_input}...")
73
  return {
 
94
  app_logger.error(f"e_constants1:{e_constants1}.")
95
  body = event
96
 
97
+ app_logger.debug(f"body, #1: {type(body)}, {body}...")
98
 
99
  if isinstance(body, str):
100
  body_decoded_str = base64_decode(body)
101
+ app_logger.debug(f"body_decoded_str: {type(body_decoded_str)}, {body_decoded_str}...")
102
  body = json.loads(body_decoded_str)
103
 
104
  app_logger.info(f"body, #2: {type(body)}, {body}...")
105
 
106
  try:
107
  prompt_latlng = body["prompt"]
108
+ app_logger.debug(f"prompt_latlng:{prompt_latlng}.")
109
  body_request = get_parsed_bbox_points(body)
110
  app_logger.info(f"body_request=> {type(body_request)}, {body_request}.")
111
  body_response = samexporter_predict(body_request["bbox"], body_request["prompt"], body_request["zoom"])
src/io/coordinates_pixel_conversion.py CHANGED
@@ -1,5 +1,5 @@
1
  import math
2
- from typing import TypedDict, List
3
 
4
  from src import app_logger
5
  from src.utilities.constants import TILE_SIZE
 
1
  import math
2
+ from typing import TypedDict
3
 
4
  from src import app_logger
5
  from src.utilities.constants import TILE_SIZE
src/io/geo_helpers.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Async download raster tiles"""
2
+ from pathlib import Path
3
+ from typing import List
4
+
5
+ import numpy as np
6
+
7
+ from src import app_logger, PROJECT_ROOT_FOLDER
8
+
9
+
10
+ def load_affine_transformation_from_matrix(matrix_source_coeffs: List):
11
+ from affine import Affine
12
+
13
+ if len(matrix_source_coeffs) != 6:
14
+ raise ValueError(f"Expected 6 coefficients, found {len(matrix_source_coeffs)}; "
15
+ f"argument type: {type(matrix_source_coeffs)}.")
16
+
17
+ try:
18
+ a, d, b, e, c, f = (float(x) for x in matrix_source_coeffs)
19
+ center = tuple.__new__(Affine, [a, b, c, d, e, f, 0.0, 0.0, 1.0])
20
+ return center * Affine.translation(-0.5, -0.5)
21
+ except Exception as e:
22
+ app_logger.error(f"exception:{e}, check https://github.com/rasterio/affine project for updates")
23
+
24
+
25
+ def get_vectorized_raster_as_geojson(rio_output, mask):
26
+ try:
27
+ from rasterio import open as rio_open
28
+ from rasterio.features import shapes
29
+ from geopandas import GeoDataFrame
30
+
31
+ app_logger.info(f"read downloaded geotiff:{rio_output} to create the shapes_generator...")
32
+
33
+ with rio_open(rio_output, "r", driver="GTiff") as rio_src:
34
+ raster = rio_src.read()
35
+ transform = rio_src.transform
36
+ crs = rio_src.crs
37
+
38
+ app_logger.debug(f"geotiff band:{raster.shape}, type: {type(raster)}, dtype: {raster.dtype}.")
39
+ app_logger.debug(f"rio_src crs:{crs}.")
40
+ app_logger.debug(f"rio_src transform:{transform}.")
41
+
42
+ # mask = band != 0
43
+ shapes_generator = ({
44
+ 'properties': {'raster_val': v}, 'geometry': s}
45
+ for i, (s, v)
46
+ # in enumerate(shapes(mask, mask=(band != 0), transform=rio_src.transform))
47
+ # use mask=None to avoid using source
48
+ in enumerate(shapes(mask, mask=None, transform=transform))
49
+ )
50
+ app_logger.info(f"created shapes_generator, transform it to a polygon list...")
51
+ shapes_list = list(shapes_generator)
52
+ app_logger.info(f"created {len(shapes_list)} polygons.")
53
+ gpd_polygonized_raster = GeoDataFrame.from_features(shapes_list, crs="EPSG:3857")
54
+ app_logger.info(f"created a GeoDataFrame, export to geojson...")
55
+ geojson = gpd_polygonized_raster.to_json(to_wgs84=True)
56
+ app_logger.info(f"created geojson, preparing API response...")
57
+ return {
58
+ "geojson": geojson,
59
+ "n_shapes_geojson": len(shapes_list)
60
+ }
61
+ except Exception as e_shape_band:
62
+ app_logger.error(f"e_shape_band:{e_shape_band}.")
63
+ raise e_shape_band
64
+
65
+
66
+ if __name__ == '__main__':
67
+ npy_file = "prediction_masks_46.27697017893455_9.616470336914064_46.11441972281433_9.264907836914064.npy"
68
+ prediction_masks = np.load(Path(PROJECT_ROOT_FOLDER) / "tmp" / "try_by_steps" / "t0" / npy_file)
69
+
70
+ print("#")
src/io/helpers.py DELETED
@@ -1,618 +0,0 @@
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 DELETED
@@ -1,91 +0,0 @@
1
- """Async download raster tiles"""
2
- from pathlib import Path
3
-
4
- import numpy as np
5
-
6
- from src import app_logger, PROJECT_ROOT_FOLDER
7
- from src.io.tms2geotiff import download_extent
8
- from src.utilities.constants import COMPLETE_URL_TILES, DEFAULT_TMS
9
- from src.utilities.type_hints import ts_llist2
10
-
11
-
12
- COOKIE_SESSION = {
13
- "Accept": "*/*",
14
- "Accept-Encoding": "gzip, deflate",
15
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0",
16
- }
17
-
18
-
19
- def load_affine_transformation_from_matrix(matrix_source_coeffs):
20
- from affine import Affine
21
-
22
- if len(matrix_source_coeffs) != 6:
23
- raise ValueError(f"Expected 6 coefficients, found {len(matrix_source_coeffs)};"
24
- f"argument type: {type(matrix_source_coeffs)}.")
25
-
26
- try:
27
- a, d, b, e, c, f = (float(x) for x in matrix_source_coeffs)
28
- center = tuple.__new__(Affine, [a, b, c, d, e, f, 0.0, 0.0, 1.0])
29
- return center * Affine.translation(-0.5, -0.5)
30
- except Exception as e:
31
- app_logger.error(f"exception:{e}, check https://github.com/rasterio/affine project for updates")
32
-
33
-
34
- # @timing_decorator
35
- def convert(bounding_box: ts_llist2, zoom: int) -> tuple:
36
- """
37
- Starting from a bounding box of two couples of latitude and longitude coordinate values, recognize a stratovolcano from an RGB image. The algorithm
38
- create the image composing three channels as slope, DEM (Digital Elevation Model) and curvature. In more detail:
39
-
40
- - download a series of terrain DEM (Digital Elevation Model) raster tiles enclosed within that bounding box
41
- - merge all the downloaded rasters
42
- - crop the merged raster
43
- - process the cropped raster to extract slope and curvature (1st and 2nd degree derivative)
44
- - produce three raster channels (DEM, slope and curvature rasters) to produce an RGB raster image
45
- - submit the RGB image to a remote machine learning service to try to recognize a polygon representing a stratovolcano
46
- - the output of the machine learning service is a json, so we need to georeferencing it
47
- - finally we return a dict as response containing
48
- - uploaded_file_name
49
- - bucket_name
50
- - prediction georeferenced geojson-like dict
51
-
52
- Args:
53
- bounding_box: float latitude/longitude bounding box
54
- zoom: integer zoom value
55
-
56
- Returns:
57
- dict: uploaded_file_name (str), bucket_name (str), prediction_georef (dict), n_total_obj_prediction (str)
58
-
59
- """
60
-
61
- tile_source = COMPLETE_URL_TILES
62
- app_logger.info(f"start_args: tile_source:{tile_source},bounding_box:{bounding_box},zoom:{zoom}.")
63
-
64
- try:
65
- import rasterio
66
-
67
- app_logger.info(f'tile_source: {tile_source}!')
68
- pt0, pt1 = bounding_box
69
- app_logger.info("downloading...")
70
- img, matrix = download_extent(DEFAULT_TMS, pt0[0], pt0[1], pt1[0], pt1[1], zoom)
71
-
72
- app_logger.info(f'img: type {type(img)}, len_matrix:{len(matrix)}, matrix {matrix}.')
73
- app_logger.info(f'img: size (shape if PIL) {img.size}.')
74
- try:
75
- np_img = np.array(img)
76
- app_logger.info(f'img: shape (numpy) {np_img.shape}.')
77
- except Exception as e_shape:
78
- app_logger.info(f'e_shape {e_shape}.')
79
- raise e_shape
80
-
81
- return img, matrix
82
- except ImportError as e_import_convert:
83
- app_logger.error(f"e0:{e_import_convert}.")
84
- raise e_import_convert
85
-
86
-
87
- if __name__ == '__main__':
88
- npy_file = "prediction_masks_46.27697017893455_9.616470336914064_46.11441972281433_9.264907836914064.npy"
89
- prediction_masks = np.load(Path(PROJECT_ROOT_FOLDER) / "tmp" / "try_by_steps" / "t0" / npy_file)
90
-
91
- print("#")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/prediction_api/predictors.py CHANGED
@@ -1,59 +1,21 @@
1
  # Press the green button in the gutter to run the script.
2
  import tempfile
3
  from pathlib import Path
4
- from typing import List
5
 
6
  import numpy as np
7
- import rasterio
8
 
9
  from src import app_logger, MODEL_FOLDER
10
- from src.io.tiles_to_tiff import convert
11
- from src.io.tms2geotiff import save_geotiff_gdal
12
  from src.prediction_api.sam_onnx import SegmentAnythingONNX
13
- from src.utilities.constants import MODEL_ENCODER_NAME, MODEL_DECODER_NAME
14
 
15
 
16
  models_dict = {"fastsam": {"instance": None}}
17
 
18
 
19
- def zip_arrays(arr1, arr2):
20
- try:
21
- arr1_list = arr1.tolist()
22
- arr2_list = arr2.tolist()
23
- # return {serialize(k): serialize(v) for k, v in zip(arr1_list, arr2_list)}
24
- d = {}
25
- for n1, n2 in zip(arr1_list, arr2_list):
26
- app_logger.info(f"n1:{n1}, type {type(n1)}, n2:{n2}, type {type(n2)}.")
27
- n1f = str(n1)
28
- n2f = str(n2)
29
- app_logger.info(f"n1:{n1}=>{n1f}, n2:{n2}=>{n2f}.")
30
- d[n1f] = n2f
31
- app_logger.info(f"zipped dict:{d}.")
32
- return d
33
- except Exception as e_zip_arrays:
34
- app_logger.info(f"exception zip_arrays:{e_zip_arrays}.")
35
- return {}
36
-
37
-
38
- def load_affine_transformation_from_matrix(matrix_source_coeffs: List):
39
- from affine import Affine
40
-
41
- if len(matrix_source_coeffs) != 6:
42
- raise ValueError(f"Expected 6 coefficients, found {len(matrix_source_coeffs)}; argument type: {type(matrix_source_coeffs)}.")
43
-
44
- try:
45
- a, d, b, e, c, f = (float(x) for x in matrix_source_coeffs)
46
- center = tuple.__new__(Affine, [a, b, c, d, e, f, 0.0, 0.0, 1.0])
47
- return center * Affine.translation(-0.5, -0.5)
48
- except Exception as e:
49
- app_logger.error(f"exception:{e}, check https://github.com/rasterio/affine project for updates")
50
-
51
-
52
  def samexporter_predict(bbox, prompt: list[dict], zoom: float, model_name: str = "fastsam") -> dict:
53
  try:
54
- from rasterio.features import shapes
55
- from geopandas import GeoDataFrame
56
-
57
  if models_dict[model_name]["instance"] is None:
58
  app_logger.info(f"missing instance model {model_name}, instantiating it now!")
59
  model_instance = SegmentAnythingONNX(
@@ -65,71 +27,47 @@ def samexporter_predict(bbox, prompt: list[dict], zoom: float, model_name: str =
65
  models_instance = models_dict[model_name]["instance"]
66
 
67
  with tempfile.TemporaryDirectory() as input_tmp_dir:
68
- img, matrix = convert(
69
- bounding_box=bbox,
70
- zoom=int(zoom)
71
- )
72
- app_logger.debug(f"## img type {type(img)} with shape/size:{img.size}, matrix:{matrix}.")
73
 
74
  pt0, pt1 = bbox
75
  rio_output = str(Path(input_tmp_dir) / f"downloaded_rio_{pt0[0]}_{pt0[1]}_{pt1[0]}_{pt1[1]}.tif")
76
- app_logger.debug(f"saving downloaded geotiff image to {rio_output}...")
77
  save_geotiff_gdal(img, rio_output, matrix)
78
- app_logger.info(f"saved downloaded geotiff image to {rio_output}...")
79
-
80
- np_img = np.array(img)
81
- app_logger.info(f"## img type {type(np_img)}, prompt:{prompt}.")
82
-
83
- app_logger.info(f"onnxruntime input shape/size (shape if PIL) {np_img.size}.")
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"instantiated model {model_name}, ENCODER {MODEL_ENCODER_NAME}, "
89
- f"DECODER {MODEL_DECODER_NAME} from {MODEL_FOLDER}: Creating embedding...")
90
- embedding = models_instance.encode(np_img)
91
- app_logger.info(f"embedding created, running predict_masks with prompt {prompt}...")
92
- prediction_masks = models_instance.predict_masks(embedding, prompt)
93
- app_logger.info(f"Created {len(prediction_masks)} prediction_masks,"
94
- f"shape:{prediction_masks.shape}, dtype:{prediction_masks.dtype}.")
95
-
96
- mask = np.zeros((prediction_masks.shape[2], prediction_masks.shape[3]), dtype=np.uint8)
97
- for n, m in enumerate(prediction_masks[0, :, :, :]):
98
- app_logger.debug(f"## {n} mask => m shape:{mask.shape}, {mask.dtype}.")
99
- mask[m > 0.0] = 255
100
-
101
- app_logger.info(f"read downloaded geotiff:{rio_output} to create the shapes_generator...")
102
-
103
- with rasterio.open(rio_output, "r", driver="GTiff") as rio_src:
104
- band = rio_src.read()
105
- try:
106
- app_logger.debug(f"geotiff band:{band.shape}, type: {type(band)}, dtype: {band.dtype}.")
107
- app_logger.debug(f"rio_src crs:{rio_src.crs}.")
108
- app_logger.debug(f"rio_src transform:{rio_src.transform}.")
109
- except Exception as e_shape_band:
110
- app_logger.error(f"e_shape_band:{e_shape_band}.")
111
- raise e_shape_band
112
- # mask_band = band != 0
113
- shapes_generator = ({
114
- 'properties': {'raster_val': v}, 'geometry': s}
115
- for i, (s, v)
116
- # in enumerate(shapes(mask, mask=(band != 0), transform=rio_src.transform))
117
- # use mask=None to avoid using source
118
- in enumerate(shapes(mask, mask=None, transform=rio_src.transform))
119
- )
120
- app_logger.info(f"created shapes_generator, transform it to a polygon list...")
121
- shapes_list = list(shapes_generator)
122
- app_logger.info(f"created {len(shapes_list)} polygons.")
123
- gpd_polygonized_raster = GeoDataFrame.from_features(shapes_list, crs="EPSG:3857")
124
- app_logger.info(f"created a GeoDataFrame, export to geojson...")
125
- geojson = gpd_polygonized_raster.to_json(to_wgs84=True)
126
- app_logger.info(f"created geojson...")
127
-
128
- return {
129
- "geojson": geojson,
130
- "n_shapes_geojson": len(shapes_list),
131
- "n_predictions": len(prediction_masks),
132
- # "n_pixels_predictions": zip_arrays(mask_unique_values, mask_unique_values_count),
133
- }
134
- except ImportError as e:
135
- app_logger.error(f"Error trying import module:{e}.")
 
1
  # Press the green button in the gutter to run the script.
2
  import tempfile
3
  from pathlib import Path
 
4
 
5
  import numpy as np
 
6
 
7
  from src import app_logger, MODEL_FOLDER
8
+ from src.io.geo_helpers import get_vectorized_raster_as_geojson
9
+ from src.io.tms2geotiff import save_geotiff_gdal, download_extent
10
  from src.prediction_api.sam_onnx import SegmentAnythingONNX
11
+ from src.utilities.constants import MODEL_ENCODER_NAME, MODEL_DECODER_NAME, DEFAULT_TMS
12
 
13
 
14
  models_dict = {"fastsam": {"instance": None}}
15
 
16
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  def samexporter_predict(bbox, prompt: list[dict], zoom: float, model_name: str = "fastsam") -> dict:
18
  try:
 
 
 
19
  if models_dict[model_name]["instance"] is None:
20
  app_logger.info(f"missing instance model {model_name}, instantiating it now!")
21
  model_instance = SegmentAnythingONNX(
 
27
  models_instance = models_dict[model_name]["instance"]
28
 
29
  with tempfile.TemporaryDirectory() as input_tmp_dir:
30
+ app_logger.info(f'tile_source: {DEFAULT_TMS}!')
31
+ pt0, pt1 = bbox
32
+ app_logger.info("downloading...")
33
+ img, matrix = download_extent(DEFAULT_TMS, pt0[0], pt0[1], pt1[0], pt1[1], zoom)
34
+ app_logger.debug(f"img type {type(img)} with shape/size:{img.size}, matrix:{matrix}.")
35
 
36
  pt0, pt1 = bbox
37
  rio_output = str(Path(input_tmp_dir) / f"downloaded_rio_{pt0[0]}_{pt0[1]}_{pt1[0]}_{pt1[1]}.tif")
38
+ app_logger.debug(f"saving downloaded image as geotiff using matrix {matrix} to {rio_output}...")
39
  save_geotiff_gdal(img, rio_output, matrix)
40
+ app_logger.info(f"saved downloaded geotiff image to {rio_output}, preparing inference...")
41
+
42
+ mask, prediction_masks = get_raster_inference(img, prompt, models_instance, model_name)
43
+ n_predictions = len(prediction_masks)
44
+ app_logger.info(f"created {n_predictions} masks, preparing conversion to geojson...")
45
+ return {
46
+ "n_predictions": n_predictions,
47
+ **get_vectorized_raster_as_geojson(rio_output, mask)
48
+ }
49
+ except ImportError as e_import_module:
50
+ app_logger.error(f"Error trying import module:{e_import_module}.")
51
+
52
+
53
+ def get_raster_inference(img, prompt, models_instance, model_name):
54
+ np_img = np.array(img)
55
+ app_logger.info(f"img type {type(np_img)}, prompt:{prompt}.")
56
+ app_logger.debug(f"onnxruntime input shape/size (shape if PIL) {np_img.size}.")
57
+ try:
58
+ app_logger.debug(f"onnxruntime input shape (NUMPY) {np_img.shape}.")
59
+ except Exception as e_shape:
60
+ app_logger.error(f"e_shape:{e_shape}.")
61
+ app_logger.info(f"instantiated model {model_name}, ENCODER {MODEL_ENCODER_NAME}, "
62
+ f"DECODER {MODEL_DECODER_NAME} from {MODEL_FOLDER}: Creating embedding...")
63
+ embedding = models_instance.encode(np_img)
64
+ app_logger.debug(f"embedding created, running predict_masks with prompt {prompt}...")
65
+ inference_out = models_instance.predict_masks(embedding, prompt)
66
+ app_logger.info(f"Created {len(inference_out)} prediction_masks,"
67
+ f"shape:{inference_out.shape}, dtype:{inference_out.dtype}.")
68
+ mask = np.zeros((inference_out.shape[2], inference_out.shape[3]), dtype=np.uint8)
69
+ for n, m in enumerate(inference_out[0, :, :, :]):
70
+ app_logger.debug(f"{n}th of prediction_masks shape {inference_out.shape}"
71
+ f" => mask shape:{mask.shape}, {mask.dtype}.")
72
+ mask[m > 0.0] = 255
73
+ return mask, inference_out
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/utilities/constants.py CHANGED
@@ -1,27 +1,7 @@
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},
15
- 'geometry': {
16
- 'type': 'MultiPolygon',
17
- 'coordinates': [[]]
18
- }}
19
- ]
20
- GEOJSON_SQUARE_TEMPLATE = {
21
- 'type': 'FeatureCollection', 'name': 'etna_wgs84p',
22
- 'crs': {'type': 'name', 'properties': {'name': 'urn:ogc:def:crs:OGC:1.3:CRS84'}},
23
- 'features': FEATURE_SQUARE_TEMPLATE
24
- }
25
  CUSTOM_RESPONSE_MESSAGES = {
26
  200: "ok",
27
  422: "Missing required parameter",
@@ -29,8 +9,6 @@ CUSTOM_RESPONSE_MESSAGES = {
29
  }
30
  MODEL_ENCODER_NAME = "mobile_sam.encoder.onnx"
31
  MODEL_DECODER_NAME = "sam_vit_h_4b8939.decoder.onnx"
32
- ZOOM = 13
33
- SOURCE_TYPE = "Satellite"
34
  TILE_SIZE = 256
35
  EARTH_EQUATORIAL_RADIUS = 6378137.0
36
  DEFAULT_TMS = 'https://tile.openstreetmap.org/{z}/{x}/{y}.png'
@@ -38,4 +16,3 @@ WKT_3857 = 'PROJCS["WGS 84 / Pseudo-Mercator",GEOGCS["WGS 84",DATUM["WGS_1984",S
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
 
1
  """Project constants"""
 
2
  INPUT_CRS_STRING = "EPSG:4326"
3
  OUTPUT_CRS_STRING = "EPSG:3857"
 
 
 
4
  ROOT = "/tmp"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  CUSTOM_RESPONSE_MESSAGES = {
6
  200: "ok",
7
  422: "Missing required parameter",
 
9
  }
10
  MODEL_ENCODER_NAME = "mobile_sam.encoder.onnx"
11
  MODEL_DECODER_NAME = "sam_vit_h_4b8939.decoder.onnx"
 
 
12
  TILE_SIZE = 256
13
  EARTH_EQUATORIAL_RADIUS = 6378137.0
14
  DEFAULT_TMS = 'https://tile.openstreetmap.org/{z}/{x}/{y}.png'
 
16
  WKT_3857 += 'AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]],PROJECTION["Mercator_1SP"],PARAMETER["central_meridian",0],'
17
  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",'
18
  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"]]'
 
src/utilities/type_hints.py CHANGED
@@ -1,24 +1,3 @@
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]]
 
1
  """custom type hints"""
 
 
 
 
 
 
 
 
 
 
2
  ts_dict_str2 = dict[str, str]
3
  ts_dict_str3 = dict[str, str, any]
 
 
 
 
 
 
 
 
 
 
 
src/utilities/utilities.py CHANGED
@@ -1,8 +1,4 @@
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):
@@ -21,22 +17,6 @@ def is_base64(sb):
21
  return False
22
 
23
 
24
- def get_system_info():
25
- import multiprocessing
26
- import torch.multiprocessing as mp
27
- import os
28
- import subprocess
29
-
30
- app_logger.info(f"mp::cpu_count:{mp.cpu_count()}.")
31
- app_logger.info(f"multiprocessing::cpu_count:{multiprocessing.cpu_count()}.")
32
- app_logger.info(f"os::cpu_count:{os.cpu_count()}")
33
- app_logger.info(f"os::sched_getaffinity:{len(os.sched_getaffinity(0))}")
34
- lscpu_output = subprocess.run("/usr/bin/lscpu", capture_output=True)
35
- app_logger.info(f"lscpu:{lscpu_output.stdout.decode('utf-8')}.")
36
- free_mem_output = subprocess.run(["/usr/bin/free", "-m"], capture_output=True)
37
- app_logger.info(f"free_mem_output:{free_mem_output.stdout.decode('utf-8')}.")
38
-
39
-
40
  def base64_decode(s):
41
  import base64
42
 
@@ -44,141 +24,3 @@ def base64_decode(s):
44
  return base64.b64decode(s, validate=True).decode("utf-8")
45
 
46
  return s
47
-
48
-
49
- def get_constants(event: dict, debug=False) -> dict:
50
- """
51
- Return constants we need to use from event, context and environment variables (both production and test).
52
-
53
- Args:
54
- event: request event
55
- debug: logging debug argument
56
-
57
- Returns:
58
- dict: project constants object
59
-
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):
70
- body = json.loads(event["body"])
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 {
81
- "bbox": body["bbox"],
82
- "point": body["point"],
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))
 
1
  """Various utilities (logger, time benchmark, args dump, numerical and stats info)"""
 
 
 
 
2
 
3
 
4
  def is_base64(sb):
 
17
  return False
18
 
19
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  def base64_decode(s):
21
  import base64
22
 
 
24
  return base64.b64decode(s, validate=True).decode("utf-8")
25
 
26
  return s