[feat] handle prompt point as lat-lng coordinates
Browse files- events/payload_point.json +1 -1
- src/app.py +26 -15
- src/io/coordinates_pixel_conversion.py +53 -0
- src/io/tms2geotiff.py +43 -43
- src/prediction_api/predictors.py +10 -12
- src/utilities/constants.py +5 -2
events/payload_point.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
| 3 |
"sw": {"lat": 30.1, "lng": 148.492},
|
| 4 |
"prompt": [{
|
| 5 |
"type": "point",
|
| 6 |
-
"data":
|
| 7 |
"label": 0
|
| 8 |
}],
|
| 9 |
"zoom": 6,
|
|
|
|
| 3 |
"sw": {"lat": 30.1, "lng": 148.492},
|
| 4 |
"prompt": [{
|
| 5 |
"type": "point",
|
| 6 |
+
"data": {"lat": 36.91, "lng": 136.854},
|
| 7 |
"label": 0
|
| 8 |
}],
|
| 9 |
"zoom": 6,
|
src/app.py
CHANGED
|
@@ -7,6 +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.prediction_api.predictors import samexporter_predict
|
| 11 |
from src.utilities.constants import CUSTOM_RESPONSE_MESSAGES
|
| 12 |
from src.utilities.utilities import base64_decode
|
|
@@ -26,7 +27,7 @@ def get_response(status: int, start_time: float, request_id: str, response_body:
|
|
| 26 |
str: json response
|
| 27 |
|
| 28 |
"""
|
| 29 |
-
app_logger.
|
| 30 |
response_body["duration_run"] = time.time() - start_time
|
| 31 |
response_body["message"] = CUSTOM_RESPONSE_MESSAGES[status]
|
| 32 |
response_body["request_id"] = request_id
|
|
@@ -45,17 +46,27 @@ def get_parsed_bbox_points(request_input: Dict) -> Dict:
|
|
| 45 |
app_logger.info(f"try to parsing input request {request_input}...")
|
| 46 |
ne = request_input["ne"]
|
| 47 |
sw = request_input["sw"]
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
]
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
return {
|
| 56 |
"bbox": bbox,
|
| 57 |
"prompt": request_input["prompt"],
|
| 58 |
-
"zoom":
|
| 59 |
}
|
| 60 |
|
| 61 |
|
|
@@ -67,8 +78,8 @@ def lambda_handler(event: dict, context: LambdaContext):
|
|
| 67 |
app_logger.info(f"event version: {event['version']}.")
|
| 68 |
|
| 69 |
try:
|
| 70 |
-
app_logger.
|
| 71 |
-
app_logger.
|
| 72 |
|
| 73 |
try:
|
| 74 |
body = event["body"]
|
|
@@ -76,19 +87,19 @@ def lambda_handler(event: dict, context: LambdaContext):
|
|
| 76 |
app_logger.error(f"e_constants1:{e_constants1}.")
|
| 77 |
body = event
|
| 78 |
|
| 79 |
-
app_logger.
|
| 80 |
|
| 81 |
if isinstance(body, str):
|
| 82 |
body_decoded_str = base64_decode(body)
|
| 83 |
-
app_logger.
|
| 84 |
body = json.loads(body_decoded_str)
|
| 85 |
|
| 86 |
-
app_logger.info(f"body:{body}...")
|
| 87 |
|
| 88 |
try:
|
| 89 |
body_request = get_parsed_bbox_points(body)
|
| 90 |
body_response = samexporter_predict(body_request["bbox"], body_request["prompt"], body_request["zoom"])
|
| 91 |
-
app_logger.info(f"body_response
|
| 92 |
response = get_response(HTTPStatus.OK.value, start_time, context.aws_request_id, body_response)
|
| 93 |
except Exception as ex2:
|
| 94 |
app_logger.error(f"exception2:{ex2}.")
|
|
|
|
| 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_point_latlng_to_pixel_coordinates, 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.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
|
|
|
|
| 46 |
app_logger.info(f"try to parsing input request {request_input}...")
|
| 47 |
ne = request_input["ne"]
|
| 48 |
sw = request_input["sw"]
|
| 49 |
+
ne_latlng = [float(ne["lat"]), float(ne["lng"])]
|
| 50 |
+
sw_latlng = [float(sw["lat"]), float(sw["lng"])]
|
| 51 |
+
bbox = [ne_latlng, sw_latlng]
|
| 52 |
+
zoom = int(request_input["zoom"])
|
| 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 point: {type(data)}, value:{data}.")
|
| 57 |
+
|
| 58 |
+
diff_pixel_coordinates_ne = get_latlng_to_pixel_coordinates(ne, data, zoom)
|
| 59 |
+
app_logger.info(f'current data by current prompt["data"]: {type(data)}, {data} => {diff_pixel_coordinates_ne}.')
|
| 60 |
+
prompt["data"] = [diff_pixel_coordinates_ne["x"], diff_pixel_coordinates_ne["y"]]
|
| 61 |
+
|
| 62 |
+
app_logger.debug(f"bbox {bbox}.")
|
| 63 |
+
app_logger.debug(f'request_input["prompt"]:{request_input["prompt"]}.')
|
| 64 |
+
|
| 65 |
+
app_logger.info(f"unpacking elaborated {request_input}...")
|
| 66 |
return {
|
| 67 |
"bbox": bbox,
|
| 68 |
"prompt": request_input["prompt"],
|
| 69 |
+
"zoom": zoom
|
| 70 |
}
|
| 71 |
|
| 72 |
|
|
|
|
| 78 |
app_logger.info(f"event version: {event['version']}.")
|
| 79 |
|
| 80 |
try:
|
| 81 |
+
app_logger.debug(f"event:{json.dumps(event)}...")
|
| 82 |
+
app_logger.debug(f"context:{context}...")
|
| 83 |
|
| 84 |
try:
|
| 85 |
body = event["body"]
|
|
|
|
| 87 |
app_logger.error(f"e_constants1:{e_constants1}.")
|
| 88 |
body = event
|
| 89 |
|
| 90 |
+
app_logger.debug(f"body, #1: {type(body)}, {body}...")
|
| 91 |
|
| 92 |
if isinstance(body, str):
|
| 93 |
body_decoded_str = base64_decode(body)
|
| 94 |
+
app_logger.debug(f"body_decoded_str: {type(body_decoded_str)}, {body_decoded_str}...")
|
| 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)
|
| 104 |
except Exception as ex2:
|
| 105 |
app_logger.error(f"exception2:{ex2}.")
|
src/io/coordinates_pixel_conversion.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
from typing import TypedDict, List
|
| 3 |
+
|
| 4 |
+
from src import app_logger
|
| 5 |
+
from src.utilities.constants import TILE_SIZE
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class PixelCoordinate(TypedDict):
|
| 9 |
+
x: int
|
| 10 |
+
y: int
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def get_latlng2pixel_projection(latlng) -> PixelCoordinate:
|
| 14 |
+
app_logger.info(f"latlng: {type(latlng)}, value:{latlng}.")
|
| 15 |
+
app_logger.info(f'latlng lat: {type(latlng["lat"])}, value:{latlng["lat"]}.')
|
| 16 |
+
app_logger.info(f'latlng lng: {type(latlng["lng"])}, value:{latlng["lng"]}.')
|
| 17 |
+
try:
|
| 18 |
+
sin_y: float = math.sin(latlng["lat"] * math.pi / 180)
|
| 19 |
+
app_logger.info(f"sin_y, #1:{sin_y}.")
|
| 20 |
+
sin_y = min(max(sin_y, -0.9999), 0.9999)
|
| 21 |
+
app_logger.info(f"sin_y, #2:{sin_y}.")
|
| 22 |
+
x = TILE_SIZE * (0.5 + latlng["lng"] / 360)
|
| 23 |
+
app_logger.info(f"x:{x}.")
|
| 24 |
+
y = TILE_SIZE * (0.5 - math.log((1 + sin_y) / (1 - sin_y)) / (4 * math.pi))
|
| 25 |
+
app_logger.info(f"y:{y}.")
|
| 26 |
+
|
| 27 |
+
return {"x": x, "y": y}
|
| 28 |
+
except Exception as e_get_latlng2pixel_projection:
|
| 29 |
+
app_logger.error(f'e_get_latlng2pixel_projection:{e_get_latlng2pixel_projection}.')
|
| 30 |
+
raise e_get_latlng2pixel_projection
|
| 31 |
+
|
| 32 |
+
|
| 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.debug(f"world_coordinate:{world_coordinate}.")
|
| 37 |
+
scale: int = pow(2, zoom)
|
| 38 |
+
app_logger.debug(f"scale:{scale}.")
|
| 39 |
+
return PixelCoordinate(
|
| 40 |
+
x=math.floor(world_coordinate["x"] * scale),
|
| 41 |
+
y=math.floor(world_coordinate["y"] * scale)
|
| 42 |
+
)
|
| 43 |
+
except Exception as e_format_latlng_to_pixel_coordinates:
|
| 44 |
+
app_logger.error(f'format_latlng_to_pixel_coordinates:{e_format_latlng_to_pixel_coordinates}.')
|
| 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 |
+
return PixelCoordinate(x=diff_coord_x, y=diff_coord_y)
|
src/io/tms2geotiff.py
CHANGED
|
@@ -14,19 +14,19 @@ import concurrent.futures
|
|
| 14 |
from PIL import Image
|
| 15 |
from PIL import TiffImagePlugin
|
| 16 |
|
|
|
|
| 17 |
from src.utilities.constants import EARTH_EQUATORIAL_RADIUS, WKT_3857, DEFAULT_TMS
|
| 18 |
|
| 19 |
-
|
| 20 |
Image.MAX_IMAGE_PIXELS = None
|
| 21 |
|
| 22 |
-
|
| 23 |
try:
|
| 24 |
import httpx
|
|
|
|
| 25 |
SESSION = httpx.Client()
|
| 26 |
except ImportError:
|
| 27 |
import requests
|
| 28 |
-
SESSION = requests.Session()
|
| 29 |
|
|
|
|
| 30 |
|
| 31 |
SESSION.headers.update({
|
| 32 |
"Accept": "*/*",
|
|
@@ -40,14 +40,14 @@ re_coords_split = re.compile('[ ,;]+')
|
|
| 40 |
def from4326_to3857(lat, lon):
|
| 41 |
xtile = math.radians(lon) * EARTH_EQUATORIAL_RADIUS
|
| 42 |
ytile = math.log(math.tan(math.radians(45 + lat / 2.0))) * EARTH_EQUATORIAL_RADIUS
|
| 43 |
-
return
|
| 44 |
|
| 45 |
|
| 46 |
def deg2num(lat, lon, zoom):
|
| 47 |
n = 2 ** zoom
|
| 48 |
xtile = ((lon + 180) / 360 * n)
|
| 49 |
ytile = (1 - math.asinh(math.tan(math.radians(lat))) / math.pi) * n / 2
|
| 50 |
-
return
|
| 51 |
|
| 52 |
|
| 53 |
def is_empty(im):
|
|
@@ -69,12 +69,12 @@ def mbtiles_init(dbname):
|
|
| 69 |
cur.execute("BEGIN")
|
| 70 |
cur.execute("CREATE TABLE IF NOT EXISTS metadata (name TEXT PRIMARY KEY, value TEXT)")
|
| 71 |
cur.execute("CREATE TABLE IF NOT EXISTS tiles ("
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
cur.execute("COMMIT")
|
| 79 |
return db
|
| 80 |
|
|
@@ -89,13 +89,13 @@ def paste_tile(bigim, base_size, tile, corner_xy, bbox):
|
|
| 89 |
base_size[0] = size[0]
|
| 90 |
base_size[1] = size[1]
|
| 91 |
newim = Image.new(mode, (
|
| 92 |
-
size[0]*(bbox[2]-bbox[0]), size[1]*(bbox[3]-bbox[1])))
|
| 93 |
else:
|
| 94 |
newim = bigim
|
| 95 |
|
| 96 |
dx = abs(corner_xy[0] - bbox[0])
|
| 97 |
dy = abs(corner_xy[1] - bbox[1])
|
| 98 |
-
xy0 = (size[0]*dx, size[1]*dy)
|
| 99 |
if mode == 'RGB':
|
| 100 |
newim.paste(im, xy0)
|
| 101 |
else:
|
|
@@ -113,7 +113,8 @@ def get_tile(url):
|
|
| 113 |
try:
|
| 114 |
r = SESSION.get(url, timeout=60)
|
| 115 |
break
|
| 116 |
-
except Exception:
|
|
|
|
| 117 |
retry -= 1
|
| 118 |
if not retry:
|
| 119 |
raise
|
|
@@ -127,7 +128,7 @@ def get_tile(url):
|
|
| 127 |
|
| 128 |
def print_progress(progress, total, done=False):
|
| 129 |
if done:
|
| 130 |
-
print('Downloaded image %d/%d, %.2f%%' % (progress, total, progress*100/total))
|
| 131 |
|
| 132 |
|
| 133 |
class ProgressBar:
|
|
@@ -175,7 +176,7 @@ def mbtiles_save(db, img_data, xy, zoom, img_format):
|
|
| 175 |
else:
|
| 176 |
current_format = 'image/' + im.format.lower()
|
| 177 |
x, y = xy
|
| 178 |
-
y = 2**zoom - 1 - y
|
| 179 |
cur = db.cursor()
|
| 180 |
if img_format is None or img_format == current_format:
|
| 181 |
cur.execute("REPLACE INTO tiles VALUES (?,?,?,?)", (
|
|
@@ -196,10 +197,10 @@ def mbtiles_save(db, img_data, xy, zoom, img_format):
|
|
| 196 |
|
| 197 |
|
| 198 |
def download_extent(
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
):
|
| 204 |
x0, y0 = deg2num(lat0, lon0, zoom)
|
| 205 |
x1, y1 = deg2num(lat1, lon1, zoom)
|
|
@@ -241,7 +242,7 @@ def download_extent(
|
|
| 241 |
cur.execute("REPLACE INTO metadata VALUES ('bounds', ?)", (
|
| 242 |
",".join(map(str, bounds)),))
|
| 243 |
cur.execute("REPLACE INTO metadata VALUES ('center', ?)", ("%s,%s,%d" % (
|
| 244 |
-
(lon_max + lon_min)/2, (lat_max + lat_min)/2, zoom),))
|
| 245 |
cur.execute("""
|
| 246 |
INSERT INTO metadata VALUES ('minzoom', ?)
|
| 247 |
ON CONFLICT(name) DO UPDATE SET value=excluded.value
|
|
@@ -267,7 +268,7 @@ def download_extent(
|
|
| 267 |
with concurrent.futures.ThreadPoolExecutor(5) as executor:
|
| 268 |
for x, y in corners:
|
| 269 |
future = executor.submit(get_tile, source.format(z=zoom, x=x, y=y))
|
| 270 |
-
futures[future] = (x, y)
|
| 271 |
bbox = (math.floor(x0), math.floor(y0), math.ceil(x1), math.ceil(y1))
|
| 272 |
bigim = None
|
| 273 |
base_size = [256, 256]
|
|
@@ -316,11 +317,11 @@ def download_extent(
|
|
| 316 |
|
| 317 |
xfrac = x0 - bbox[0]
|
| 318 |
yfrac = y0 - bbox[1]
|
| 319 |
-
x2 = round(base_size[0]*xfrac)
|
| 320 |
-
y2 = round(base_size[1]*yfrac)
|
| 321 |
-
imgw = round(base_size[0]*(x1-x0))
|
| 322 |
-
imgh = round(base_size[1]*(y1-y0))
|
| 323 |
-
retim = bigim.crop((x2, y2, x2+imgw, y2+imgh))
|
| 324 |
if retim.mode == 'RGBA' and retim.getextrema()[3] == (255, 255):
|
| 325 |
retim = retim.convert('RGB')
|
| 326 |
bigim.close()
|
|
@@ -411,7 +412,7 @@ def save_image_fn(img, filename, matrix, **params):
|
|
| 411 |
elif ext == '.png':
|
| 412 |
img_params['optimize'] = True
|
| 413 |
elif ext.startswith('.tif'):
|
| 414 |
-
if img_memorysize(img) >= 4*1024*1024*1024:
|
| 415 |
# BigTIFF
|
| 416 |
return save_geotiff_gdal(img, filename, matrix)
|
| 417 |
img_params['compression'] = 'tiff_adobe_deflate'
|
|
@@ -437,14 +438,14 @@ def save_geotiff_gdal(img, filename, matrix):
|
|
| 437 |
imgbands = len(img.getbands())
|
| 438 |
driver = gdal.GetDriverByName('GTiff')
|
| 439 |
gdal_options = ['COMPRESS=DEFLATE', 'PREDICTOR=2', 'ZLEVEL=9', 'TILED=YES']
|
| 440 |
-
if img_memorysize(img) >= 4*1024*1024*1024:
|
| 441 |
gdal_options.append('BIGTIFF=YES')
|
| 442 |
-
if img_memorysize(img) >= 50*1024*1024:
|
| 443 |
gdal_options.append('NUM_THREADS=%d' % max(1, os.cpu_count()))
|
| 444 |
|
| 445 |
gtiff = driver.Create(filename, img.size[0], img.size[1],
|
| 446 |
-
|
| 447 |
-
|
| 448 |
gtiff.SetGeoTransform(matrix)
|
| 449 |
gtiff.SetProjection(WKT_3857)
|
| 450 |
for band in range(imgbands):
|
|
@@ -589,12 +590,12 @@ def gui():
|
|
| 589 |
return
|
| 590 |
root_tk.update()
|
| 591 |
try:
|
| 592 |
-
img,
|
| 593 |
*args, progress_callback=update_progress, **kwargs)
|
| 594 |
b_download.configure(text='Saving...', state='disabled')
|
| 595 |
root_tk.update()
|
| 596 |
if filename:
|
| 597 |
-
save_image_auto(img, filename,
|
| 598 |
reset()
|
| 599 |
except TaskCancelled:
|
| 600 |
reset()
|
|
@@ -632,7 +633,6 @@ def gui():
|
|
| 632 |
|
| 633 |
|
| 634 |
def downloader(input_args, input_parser):
|
| 635 |
-
|
| 636 |
download_args = [input_args.source]
|
| 637 |
try:
|
| 638 |
if input_args.extent:
|
|
@@ -652,11 +652,11 @@ def downloader(input_args, input_parser):
|
|
| 652 |
download_args.append(bool(input_args.output))
|
| 653 |
progress_bar = ProgressBar()
|
| 654 |
download_args.append(progress_bar.print_progress)
|
| 655 |
-
|
| 656 |
progress_bar.close()
|
| 657 |
if input_args.output:
|
| 658 |
print(f"Saving image to {input_args.output}.")
|
| 659 |
-
save_image_auto(
|
| 660 |
return 0
|
| 661 |
|
| 662 |
|
|
@@ -670,8 +670,8 @@ def main():
|
|
| 670 |
parser.add_argument("-f", "--from", metavar='LAT,LON', help="one corner")
|
| 671 |
parser.add_argument("-t", "--to", metavar='LAT,LON', help="the other corner")
|
| 672 |
parser.add_argument("-e", "--extent",
|
| 673 |
-
|
| 674 |
-
|
| 675 |
parser.add_argument("-z", "--zoom", type=int, help="zoom level")
|
| 676 |
parser.add_argument("-m", "--mbtiles", help="save MBTiles file")
|
| 677 |
parser.add_argument("-g", "--gui", action='store_true', help="show GUI")
|
|
@@ -690,8 +690,8 @@ if __name__ == '__main__':
|
|
| 690 |
# sys.exit(main())
|
| 691 |
pt0 = 45.699, 127.1
|
| 692 |
pt1 = 30.1, 148.492
|
| 693 |
-
|
| 694 |
-
|
| 695 |
|
| 696 |
-
print(f"Saving image to {
|
| 697 |
-
save_image_auto(
|
|
|
|
| 14 |
from PIL import Image
|
| 15 |
from PIL import TiffImagePlugin
|
| 16 |
|
| 17 |
+
from src import PROJECT_ROOT_FOLDER, app_logger
|
| 18 |
from src.utilities.constants import EARTH_EQUATORIAL_RADIUS, WKT_3857, DEFAULT_TMS
|
| 19 |
|
|
|
|
| 20 |
Image.MAX_IMAGE_PIXELS = None
|
| 21 |
|
|
|
|
| 22 |
try:
|
| 23 |
import httpx
|
| 24 |
+
|
| 25 |
SESSION = httpx.Client()
|
| 26 |
except ImportError:
|
| 27 |
import requests
|
|
|
|
| 28 |
|
| 29 |
+
SESSION = requests.Session()
|
| 30 |
|
| 31 |
SESSION.headers.update({
|
| 32 |
"Accept": "*/*",
|
|
|
|
| 40 |
def from4326_to3857(lat, lon):
|
| 41 |
xtile = math.radians(lon) * EARTH_EQUATORIAL_RADIUS
|
| 42 |
ytile = math.log(math.tan(math.radians(45 + lat / 2.0))) * EARTH_EQUATORIAL_RADIUS
|
| 43 |
+
return xtile, ytile
|
| 44 |
|
| 45 |
|
| 46 |
def deg2num(lat, lon, zoom):
|
| 47 |
n = 2 ** zoom
|
| 48 |
xtile = ((lon + 180) / 360 * n)
|
| 49 |
ytile = (1 - math.asinh(math.tan(math.radians(lat))) / math.pi) * n / 2
|
| 50 |
+
return xtile, ytile
|
| 51 |
|
| 52 |
|
| 53 |
def is_empty(im):
|
|
|
|
| 69 |
cur.execute("BEGIN")
|
| 70 |
cur.execute("CREATE TABLE IF NOT EXISTS metadata (name TEXT PRIMARY KEY, value TEXT)")
|
| 71 |
cur.execute("CREATE TABLE IF NOT EXISTS tiles ("
|
| 72 |
+
"zoom_level INTEGER NOT NULL, "
|
| 73 |
+
"tile_column INTEGER NOT NULL, "
|
| 74 |
+
"tile_row INTEGER NOT NULL, "
|
| 75 |
+
"tile_data BLOB NOT NULL, "
|
| 76 |
+
"UNIQUE (zoom_level, tile_column, tile_row)"
|
| 77 |
+
")")
|
| 78 |
cur.execute("COMMIT")
|
| 79 |
return db
|
| 80 |
|
|
|
|
| 89 |
base_size[0] = size[0]
|
| 90 |
base_size[1] = size[1]
|
| 91 |
newim = Image.new(mode, (
|
| 92 |
+
size[0] * (bbox[2] - bbox[0]), size[1] * (bbox[3] - bbox[1])))
|
| 93 |
else:
|
| 94 |
newim = bigim
|
| 95 |
|
| 96 |
dx = abs(corner_xy[0] - bbox[0])
|
| 97 |
dy = abs(corner_xy[1] - bbox[1])
|
| 98 |
+
xy0 = (size[0] * dx, size[1] * dy)
|
| 99 |
if mode == 'RGB':
|
| 100 |
newim.paste(im, xy0)
|
| 101 |
else:
|
|
|
|
| 113 |
try:
|
| 114 |
r = SESSION.get(url, timeout=60)
|
| 115 |
break
|
| 116 |
+
except Exception as request_tile_exception:
|
| 117 |
+
app_logger.error(f"retry {retry}, request_tile_exception:{request_tile_exception}.")
|
| 118 |
retry -= 1
|
| 119 |
if not retry:
|
| 120 |
raise
|
|
|
|
| 128 |
|
| 129 |
def print_progress(progress, total, done=False):
|
| 130 |
if done:
|
| 131 |
+
print('Downloaded image %d/%d, %.2f%%' % (progress, total, progress * 100 / total))
|
| 132 |
|
| 133 |
|
| 134 |
class ProgressBar:
|
|
|
|
| 176 |
else:
|
| 177 |
current_format = 'image/' + im.format.lower()
|
| 178 |
x, y = xy
|
| 179 |
+
y = 2 ** zoom - 1 - y
|
| 180 |
cur = db.cursor()
|
| 181 |
if img_format is None or img_format == current_format:
|
| 182 |
cur.execute("REPLACE INTO tiles VALUES (?,?,?,?)", (
|
|
|
|
| 197 |
|
| 198 |
|
| 199 |
def download_extent(
|
| 200 |
+
source, lat0, lon0, lat1, lon1, zoom,
|
| 201 |
+
mbtiles=None, save_image=True,
|
| 202 |
+
progress_callback=print_progress,
|
| 203 |
+
callback_interval=0.05
|
| 204 |
):
|
| 205 |
x0, y0 = deg2num(lat0, lon0, zoom)
|
| 206 |
x1, y1 = deg2num(lat1, lon1, zoom)
|
|
|
|
| 242 |
cur.execute("REPLACE INTO metadata VALUES ('bounds', ?)", (
|
| 243 |
",".join(map(str, bounds)),))
|
| 244 |
cur.execute("REPLACE INTO metadata VALUES ('center', ?)", ("%s,%s,%d" % (
|
| 245 |
+
(lon_max + lon_min) / 2, (lat_max + lat_min) / 2, zoom),))
|
| 246 |
cur.execute("""
|
| 247 |
INSERT INTO metadata VALUES ('minzoom', ?)
|
| 248 |
ON CONFLICT(name) DO UPDATE SET value=excluded.value
|
|
|
|
| 268 |
with concurrent.futures.ThreadPoolExecutor(5) as executor:
|
| 269 |
for x, y in corners:
|
| 270 |
future = executor.submit(get_tile, source.format(z=zoom, x=x, y=y))
|
| 271 |
+
futures[future] = (x, y)
|
| 272 |
bbox = (math.floor(x0), math.floor(y0), math.ceil(x1), math.ceil(y1))
|
| 273 |
bigim = None
|
| 274 |
base_size = [256, 256]
|
|
|
|
| 317 |
|
| 318 |
xfrac = x0 - bbox[0]
|
| 319 |
yfrac = y0 - bbox[1]
|
| 320 |
+
x2 = round(base_size[0] * xfrac)
|
| 321 |
+
y2 = round(base_size[1] * yfrac)
|
| 322 |
+
imgw = round(base_size[0] * (x1 - x0))
|
| 323 |
+
imgh = round(base_size[1] * (y1 - y0))
|
| 324 |
+
retim = bigim.crop((x2, y2, x2 + imgw, y2 + imgh))
|
| 325 |
if retim.mode == 'RGBA' and retim.getextrema()[3] == (255, 255):
|
| 326 |
retim = retim.convert('RGB')
|
| 327 |
bigim.close()
|
|
|
|
| 412 |
elif ext == '.png':
|
| 413 |
img_params['optimize'] = True
|
| 414 |
elif ext.startswith('.tif'):
|
| 415 |
+
if img_memorysize(img) >= 4 * 1024 * 1024 * 1024:
|
| 416 |
# BigTIFF
|
| 417 |
return save_geotiff_gdal(img, filename, matrix)
|
| 418 |
img_params['compression'] = 'tiff_adobe_deflate'
|
|
|
|
| 438 |
imgbands = len(img.getbands())
|
| 439 |
driver = gdal.GetDriverByName('GTiff')
|
| 440 |
gdal_options = ['COMPRESS=DEFLATE', 'PREDICTOR=2', 'ZLEVEL=9', 'TILED=YES']
|
| 441 |
+
if img_memorysize(img) >= 4 * 1024 * 1024 * 1024:
|
| 442 |
gdal_options.append('BIGTIFF=YES')
|
| 443 |
+
if img_memorysize(img) >= 50 * 1024 * 1024:
|
| 444 |
gdal_options.append('NUM_THREADS=%d' % max(1, os.cpu_count()))
|
| 445 |
|
| 446 |
gtiff = driver.Create(filename, img.size[0], img.size[1],
|
| 447 |
+
imgbands, gdal.GDT_Byte,
|
| 448 |
+
options=gdal_options)
|
| 449 |
gtiff.SetGeoTransform(matrix)
|
| 450 |
gtiff.SetProjection(WKT_3857)
|
| 451 |
for band in range(imgbands):
|
|
|
|
| 590 |
return
|
| 591 |
root_tk.update()
|
| 592 |
try:
|
| 593 |
+
img, matrix_projection = download_extent(
|
| 594 |
*args, progress_callback=update_progress, **kwargs)
|
| 595 |
b_download.configure(text='Saving...', state='disabled')
|
| 596 |
root_tk.update()
|
| 597 |
if filename:
|
| 598 |
+
save_image_auto(img, filename, matrix_projection)
|
| 599 |
reset()
|
| 600 |
except TaskCancelled:
|
| 601 |
reset()
|
|
|
|
| 633 |
|
| 634 |
|
| 635 |
def downloader(input_args, input_parser):
|
|
|
|
| 636 |
download_args = [input_args.source]
|
| 637 |
try:
|
| 638 |
if input_args.extent:
|
|
|
|
| 652 |
download_args.append(bool(input_args.output))
|
| 653 |
progress_bar = ProgressBar()
|
| 654 |
download_args.append(progress_bar.print_progress)
|
| 655 |
+
img_geo, matrix = download_extent(*download_args)
|
| 656 |
progress_bar.close()
|
| 657 |
if input_args.output:
|
| 658 |
print(f"Saving image to {input_args.output}.")
|
| 659 |
+
save_image_auto(img_geo, input_args.output, matrix)
|
| 660 |
return 0
|
| 661 |
|
| 662 |
|
|
|
|
| 670 |
parser.add_argument("-f", "--from", metavar='LAT,LON', help="one corner")
|
| 671 |
parser.add_argument("-t", "--to", metavar='LAT,LON', help="the other corner")
|
| 672 |
parser.add_argument("-e", "--extent",
|
| 673 |
+
metavar='min_lon,min_lat,max_lon,max_lat',
|
| 674 |
+
help="extent in one string (use either -e, or -f and -t)")
|
| 675 |
parser.add_argument("-z", "--zoom", type=int, help="zoom level")
|
| 676 |
parser.add_argument("-m", "--mbtiles", help="save MBTiles file")
|
| 677 |
parser.add_argument("-g", "--gui", action='store_true', help="show GUI")
|
|
|
|
| 690 |
# sys.exit(main())
|
| 691 |
pt0 = 45.699, 127.1
|
| 692 |
pt1 = 30.1, 148.492
|
| 693 |
+
geo_img_output_filename = PROJECT_ROOT_FOLDER / "tmp" / "japan_out_main.png"
|
| 694 |
+
geo_img, projection_matrix = download_extent(DEFAULT_TMS, pt0[0], pt0[1], pt1[0], pt1[1], 6)
|
| 695 |
|
| 696 |
+
print(f"Saving image to {geo_img_output_filename}.")
|
| 697 |
+
save_image_auto(geo_img, geo_img_output_filename, projection_matrix)
|
src/prediction_api/predictors.py
CHANGED
|
@@ -62,7 +62,7 @@ def samexporter_predict(bbox: input_float_tuples, prompt: list[dict], zoom: floa
|
|
| 62 |
models_instance = models_dict[model_name]["instance"]
|
| 63 |
|
| 64 |
for coord in bbox:
|
| 65 |
-
app_logger.
|
| 66 |
app_logger.info(f"start download_extent using bbox:{bbox}, type:{type(bbox)}, download image...")
|
| 67 |
|
| 68 |
pt0 = bbox[0]
|
|
@@ -70,30 +70,28 @@ def samexporter_predict(bbox: input_float_tuples, prompt: list[dict], zoom: floa
|
|
| 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)}, matrix type {type(matrix)}.")
|
| 73 |
-
app_logger.
|
| 74 |
np_img = np.array(img)
|
| 75 |
-
app_logger.
|
| 76 |
-
app_logger.
|
| 77 |
app_logger.info(f"geotiff created with size/shape {img.size} and transform matrix {str(matrix)}, start to initialize SamGeo instance:")
|
| 78 |
-
app_logger.info(f"use
|
| 79 |
-
|
| 80 |
-
app_logger.info(f"model instantiated, creating embedding...")
|
| 81 |
embedding = models_instance.encode(np_img)
|
| 82 |
app_logger.info(f"embedding created, running predict_masks...")
|
| 83 |
prediction_masks = models_instance.predict_masks(embedding, prompt)
|
| 84 |
-
app_logger.
|
| 85 |
-
app_logger.info(f"prediction masks shape:{prediction_masks.shape}, {prediction_masks.dtype}.")
|
| 86 |
|
| 87 |
mask = np.zeros((prediction_masks.shape[2], prediction_masks.shape[3]), dtype=np.uint8)
|
| 88 |
for m in prediction_masks[0, :, :, :]:
|
| 89 |
mask[m > 0.0] = 255
|
| 90 |
|
| 91 |
mask_unique_values, mask_unique_values_count = serialize(np.unique(mask, return_counts=True))
|
| 92 |
-
app_logger.
|
| 93 |
-
app_logger.
|
| 94 |
|
| 95 |
transform = load_affine_transformation_from_matrix(matrix)
|
| 96 |
-
app_logger.info(f"image/geojson origin matrix:{matrix}, transform:{transform}
|
| 97 |
shapes_generator = ({
|
| 98 |
'properties': {'raster_val': v}, 'geometry': s}
|
| 99 |
for i, (s, v)
|
|
|
|
| 62 |
models_instance = models_dict[model_name]["instance"]
|
| 63 |
|
| 64 |
for coord in bbox:
|
| 65 |
+
app_logger.debug(f"bbox coord:{coord}, type:{type(coord)}.")
|
| 66 |
app_logger.info(f"start download_extent using bbox:{bbox}, type:{type(bbox)}, download image...")
|
| 67 |
|
| 68 |
pt0 = bbox[0]
|
|
|
|
| 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)}, matrix type {type(matrix)}.")
|
| 73 |
+
app_logger.debug(f"matrix values: {serialize(matrix)}.")
|
| 74 |
np_img = np.array(img)
|
| 75 |
+
app_logger.debug(f"np_img type {type(np_img)}.")
|
| 76 |
+
app_logger.debug(f"np_img dtype {np_img.dtype}, shape {np_img.shape}.")
|
| 77 |
app_logger.info(f"geotiff created with size/shape {img.size} and transform matrix {str(matrix)}, start to initialize SamGeo instance:")
|
| 78 |
+
app_logger.info(f"use {model_name} model, ENCODER model {MODEL_ENCODER_NAME} and {MODEL_DECODER_NAME} from {MODEL_FOLDER}): model instantiated, creating embedding...")
|
|
|
|
|
|
|
| 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.debug(f"predict_masks terminated...")
|
| 83 |
+
app_logger.info(f"predict_masks terminated, prediction masks shape:{prediction_masks.shape}, {prediction_masks.dtype}.")
|
| 84 |
|
| 85 |
mask = np.zeros((prediction_masks.shape[2], prediction_masks.shape[3]), dtype=np.uint8)
|
| 86 |
for m in prediction_masks[0, :, :, :]:
|
| 87 |
mask[m > 0.0] = 255
|
| 88 |
|
| 89 |
mask_unique_values, mask_unique_values_count = serialize(np.unique(mask, return_counts=True))
|
| 90 |
+
app_logger.debug(f"mask_unique_values:{mask_unique_values}.")
|
| 91 |
+
app_logger.debug(f"mask_unique_values_count:{mask_unique_values_count}.")
|
| 92 |
|
| 93 |
transform = load_affine_transformation_from_matrix(matrix)
|
| 94 |
+
app_logger.info(f"image/geojson origin matrix:{matrix}, transform:{transform}: create shapes_generator...")
|
| 95 |
shapes_generator = ({
|
| 96 |
'properties': {'raster_val': v}, 'geometry': s}
|
| 97 |
for i, (s, v)
|
src/utilities/constants.py
CHANGED
|
@@ -26,7 +26,10 @@ MODEL_ENCODER_NAME = "mobile_sam.encoder.onnx"
|
|
| 26 |
MODEL_DECODER_NAME = "sam_vit_h_4b8939.decoder.onnx"
|
| 27 |
ZOOM = 13
|
| 28 |
SOURCE_TYPE = "Satellite"
|
| 29 |
-
|
| 30 |
EARTH_EQUATORIAL_RADIUS = 6378137.0
|
| 31 |
DEFAULT_TMS = 'https://tile.openstreetmap.org/{z}/{x}/{y}.png'
|
| 32 |
-
WKT_3857 = 'PROJCS["WGS 84 / Pseudo-Mercator",GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
MODEL_DECODER_NAME = "sam_vit_h_4b8939.decoder.onnx"
|
| 27 |
ZOOM = 13
|
| 28 |
SOURCE_TYPE = "Satellite"
|
| 29 |
+
TILE_SIZE = 256
|
| 30 |
EARTH_EQUATORIAL_RADIUS = 6378137.0
|
| 31 |
DEFAULT_TMS = 'https://tile.openstreetmap.org/{z}/{x}/{y}.png'
|
| 32 |
+
WKT_3857 = 'PROJCS["WGS 84 / Pseudo-Mercator",GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,'
|
| 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"]]'
|