1
+ from functools import lru_cache
1
2
import math
2
3
import logging
3
4
from enum import Enum
4
- from typing import Optional , List , Tuple , Any , Union
5
+ from typing import Optional , List , Tuple , Any , Union , Dict , Callable
5
6
from concurrent .futures import ThreadPoolExecutor
6
7
from io import BytesIO
7
8
8
9
import requests
9
10
import numpy as np
10
-
11
11
from google .api_core import retry
12
12
from PIL import Image
13
13
from pyproj import Transformer
19
19
from labelbox .data .annotation_types .geometry .point import Point
20
20
from labelbox .data .annotation_types .geometry .line import Line
21
21
from labelbox .data .annotation_types .geometry .rectangle import Rectangle
22
-
23
22
from ..geometry import Point
24
23
from .base_data import BaseData
25
24
from .raster import RasterData
@@ -100,7 +99,7 @@ class TileLayer(BaseModel):
100
99
url : str
101
100
name : Optional [str ] = "default"
102
101
103
- def asdict (self ):
102
+ def asdict (self ) -> Dict :
104
103
return {"tileLayerUrl" : self .url , "name" : self .name }
105
104
106
105
@validator ('url' )
@@ -139,11 +138,11 @@ class TiledImageData(BaseData):
139
138
version : Optional [int ] = 2
140
139
multithread : bool = True
141
140
142
- def __post_init__ (self ):
141
+ def __post_init__ (self ) -> None :
143
142
if self .max_native_zoom is None :
144
143
self .max_native_zoom = self .zoom_levels [0 ]
145
144
146
- def asdict (self ):
145
+ def asdict (self ) -> Dict :
147
146
return {
148
147
"tileLayerUrl" : self .tile_layer .url ,
149
148
"bounds" : [[
@@ -160,10 +159,10 @@ def asdict(self):
160
159
"version" : self .version
161
160
}
162
161
163
- def as_raster_data (self ,
164
- zoom : int = 0 ,
165
- max_tiles : int = 32 ,
166
- multithread = True ) -> RasterData :
162
+ def raster_data (self ,
163
+ zoom : int = 0 ,
164
+ max_tiles : int = 32 ,
165
+ multithread = True ) -> RasterData :
167
166
"""Converts the tiled image asset into a RasterData object containing an
168
167
np.ndarray.
169
168
@@ -172,23 +171,18 @@ def as_raster_data(self,
172
171
if self .tile_bounds .epsg == EPSG .SIMPLEPIXEL :
173
172
xstart , ystart , xend , yend = self ._get_simple_image_params (zoom )
174
173
elif self .tile_bounds .epsg == EPSG .EPSG4326 :
175
- xstart , ystart , xend , yend = self ._get_3857_image_params (zoom )
174
+ xstart , ystart , xend , yend = self ._get_3857_image_params (
175
+ zoom , self .tile_bounds )
176
176
elif self .tile_bounds .epsg == EPSG .EPSG3857 :
177
177
#transform to 4326
178
178
transformer = EPSGTransformer .create_geo_to_geo_transformer (
179
179
EPSG .EPSG3857 , EPSG .EPSG4326 )
180
- self .tile_bounds .bounds = [
181
- transformer (self .tile_bounds .bounds [0 ]),
182
- transformer (self .tile_bounds .bounds [1 ])
183
- ]
184
- xstart , ystart , xend , yend = self ._get_3857_image_params (zoom )
185
- #transform back to 3857
186
- transformer = EPSGTransformer .create_geo_to_geo_transformer (
187
- EPSG .EPSG4326 , EPSG .EPSG3857 )
188
- self .tile_bounds .bounds = [
180
+ transforming_bounds = [
189
181
transformer (self .tile_bounds .bounds [0 ]),
190
182
transformer (self .tile_bounds .bounds [1 ])
191
183
]
184
+ xstart , ystart , xend , yend = self ._get_3857_image_params (
185
+ zoom , transforming_bounds )
192
186
else :
193
187
raise ValueError (f"Unsupported epsg found: { self .tile_bounds .epsg } " )
194
188
@@ -207,8 +201,8 @@ def as_raster_data(self,
207
201
def value (self ) -> np .ndarray :
208
202
"""Returns the value of a generated RasterData object.
209
203
"""
210
- return self .as_raster_data (self .zoom_levels [0 ],
211
- multithread = self .multithread ).value
204
+ return self .raster_data (self .zoom_levels [0 ],
205
+ multithread = self .multithread ).value
212
206
213
207
def _get_simple_image_params (self ,
214
208
zoom ) -> Tuple [float , float , float , float ]:
@@ -229,14 +223,14 @@ def _get_simple_image_params(self,
229
223
for x in [xstart , ystart , xend , yend ]
230
224
],)
231
225
232
- def _get_3857_image_params (self , zoom ) -> Tuple [float , float , float , float ]:
226
+ def _get_3857_image_params (
227
+ self , zoom : int ,
228
+ bounds : TiledBounds ) -> Tuple [float , float , float , float ]:
233
229
"""Computes the x and y tile bounds for fetching an image that
234
230
captures the entire labeling region (TiledData.bounds) given a specific zoom
235
231
"""
236
- lat_start , lat_end = self .tile_bounds .bounds [
237
- 1 ].y , self .tile_bounds .bounds [0 ].y
238
- lng_start , lng_end = self .tile_bounds .bounds [
239
- 1 ].x , self .tile_bounds .bounds [0 ].x
232
+ lat_start , lat_end = bounds .bounds [1 ].y , bounds .bounds [0 ].y
233
+ lng_start , lng_end = bounds .bounds [1 ].x , bounds .bounds [0 ].x
240
234
241
235
# Convert to zoom 0 tile coordinates
242
236
xstart , ystart = self ._latlng_to_tile (lat_start , lng_start )
@@ -350,7 +344,8 @@ def _validate_num_tiles(self, xstart: float, ystart: float, xend: float,
350
344
total_n_tiles = (yend - ystart + 1 ) * (xend - xstart + 1 )
351
345
if total_n_tiles > max_tiles :
352
346
raise ValueError (f"Requested zoom results in { total_n_tiles } tiles."
353
- f"Max allowed tiles are { max_tiles } " )
347
+ f"Max allowed tiles are { max_tiles } "
348
+ f"Increase max tiles or reduce zoom level." )
354
349
355
350
@validator ('zoom_levels' )
356
351
def validate_zoom_levels (cls , zoom_levels ):
@@ -378,7 +373,7 @@ def _is_simple(epsg: EPSG) -> bool:
378
373
return epsg == EPSG .SIMPLEPIXEL
379
374
380
375
@staticmethod
381
- def _get_ranges (bounds : np .ndarray ):
376
+ def _get_ranges (bounds : np .ndarray ) -> Tuple [ int , int ] :
382
377
"""helper function to get the range between bounds.
383
378
384
379
returns a tuple (x_range, y_range)"""
@@ -387,7 +382,7 @@ def _get_ranges(bounds: np.ndarray):
387
382
return (x_range , y_range )
388
383
389
384
@staticmethod
390
- def _min_max_x_y (bounds : np .ndarray ):
385
+ def _min_max_x_y (bounds : np .ndarray ) -> Tuple [ int , int , int , int ] :
391
386
"""returns the min x, max x, min y, max y of a numpy array
392
387
"""
393
388
return np .min (bounds [:, 0 ]), np .max (bounds [:, 0 ]), np .min (
@@ -398,7 +393,7 @@ def geo_and_pixel(cls,
398
393
src_epsg ,
399
394
pixel_bounds : TiledBounds ,
400
395
geo_bounds : TiledBounds ,
401
- zoom = 0 ):
396
+ zoom = 0 ) -> Callable :
402
397
"""method to change from one projection to simple projection"""
403
398
404
399
pixel_bounds = pixel_bounds .bounds
@@ -420,7 +415,7 @@ def geo_and_pixel(cls,
420
415
421
416
if src_epsg == EPSG .SIMPLEPIXEL :
422
417
423
- def transform (x : int , y : int ):
418
+ def transform (x : int , y : int ) -> Callable :
424
419
scaled_xy = (x * (global_x_range ) / (local_x_range ),
425
420
y * (global_y_range ) / (local_y_range ))
426
421
@@ -440,7 +435,7 @@ def transform(x: int, y: int):
440
435
#handles 4326 from lat,lng
441
436
elif src_epsg == EPSG .EPSG4326 :
442
437
443
- def transform (x : int , y : int ):
438
+ def transform (x : int , y : int ) -> Callable :
444
439
point_in_px = PygeoPoint .from_latitude_longitude (
445
440
latitude = y , longitude = x ).pixels (zoom )
446
441
@@ -455,7 +450,7 @@ def transform(x: int, y: int):
455
450
#handles 3857 from meters
456
451
elif src_epsg == EPSG .EPSG3857 :
457
452
458
- def transform (x : int , y : int ):
453
+ def transform (x : int , y : int ) -> Callable :
459
454
point_in_px = PygeoPoint .from_meters (meter_y = y ,
460
455
meter_x = x ).pixels (zoom )
461
456
@@ -469,7 +464,7 @@ def transform(x: int, y: int):
469
464
470
465
@classmethod
471
466
def create_geo_to_geo_transformer (cls , src_epsg : EPSG ,
472
- tgt_epsg : EPSG ) -> None :
467
+ tgt_epsg : EPSG ) -> Callable :
473
468
"""method to change from one projection to another projection.
474
469
475
470
supports EPSG transformations not Simple.
@@ -487,7 +482,7 @@ def create_geo_to_pixel_transformer(cls,
487
482
src_epsg ,
488
483
pixel_bounds : TiledBounds ,
489
484
geo_bounds : TiledBounds ,
490
- zoom = 0 ):
485
+ zoom = 0 ) -> Callable :
491
486
"""method to change from a geo projection to Simple"""
492
487
493
488
transform_function = cls .geo_and_pixel (src_epsg = src_epsg ,
@@ -501,7 +496,7 @@ def create_pixel_to_geo_transformer(cls,
501
496
src_epsg ,
502
497
pixel_bounds : TiledBounds ,
503
498
geo_bounds : TiledBounds ,
504
- zoom = 0 ):
499
+ zoom = 0 ) -> Callable :
505
500
"""method to change from a geo projection to Simple"""
506
501
transform_function = cls .geo_and_pixel (src_epsg = src_epsg ,
507
502
pixel_bounds = pixel_bounds ,
@@ -513,7 +508,9 @@ def _get_point_obj(self, point) -> Point:
513
508
point = self .transformer (point .x , point .y )
514
509
return Point (x = point [0 ], y = point [1 ])
515
510
516
- def __call__ (self , shape : Union [Point , Line , Rectangle , Polygon ]):
511
+ def __call__ (
512
+ self , shape : Union [Point , Line , Rectangle , Polygon ]
513
+ ) -> Union [Point , Line , Rectangle , Polygon ]:
517
514
if isinstance (shape , Point ):
518
515
return self ._get_point_obj (shape )
519
516
if isinstance (shape , Line ) or isinstance (shape , Polygon ):
0 commit comments