[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"]]'
|