Skip to content

Commit cfa7b95

Browse files
authored
Merge pull request #390 from Labelbox/geo-nits
updates
2 parents 981834a + 0455bc2 commit cfa7b95

File tree

2 files changed

+47
-52
lines changed

2 files changed

+47
-52
lines changed

examples/annotation_types/tiled_imagery_basics.ipynb

Lines changed: 12 additions & 14 deletions
Large diffs are not rendered by default.

labelbox/data/annotation_types/data/tiled_image.py

Lines changed: 35 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1+
from functools import lru_cache
12
import math
23
import logging
34
from enum import Enum
4-
from typing import Optional, List, Tuple, Any, Union
5+
from typing import Optional, List, Tuple, Any, Union, Dict, Callable
56
from concurrent.futures import ThreadPoolExecutor
67
from io import BytesIO
78

89
import requests
910
import numpy as np
10-
1111
from google.api_core import retry
1212
from PIL import Image
1313
from pyproj import Transformer
@@ -19,7 +19,6 @@
1919
from labelbox.data.annotation_types.geometry.point import Point
2020
from labelbox.data.annotation_types.geometry.line import Line
2121
from labelbox.data.annotation_types.geometry.rectangle import Rectangle
22-
2322
from ..geometry import Point
2423
from .base_data import BaseData
2524
from .raster import RasterData
@@ -100,7 +99,7 @@ class TileLayer(BaseModel):
10099
url: str
101100
name: Optional[str] = "default"
102101

103-
def asdict(self):
102+
def asdict(self) -> Dict:
104103
return {"tileLayerUrl": self.url, "name": self.name}
105104

106105
@validator('url')
@@ -139,11 +138,11 @@ class TiledImageData(BaseData):
139138
version: Optional[int] = 2
140139
multithread: bool = True
141140

142-
def __post_init__(self):
141+
def __post_init__(self) -> None:
143142
if self.max_native_zoom is None:
144143
self.max_native_zoom = self.zoom_levels[0]
145144

146-
def asdict(self):
145+
def asdict(self) -> Dict:
147146
return {
148147
"tileLayerUrl": self.tile_layer.url,
149148
"bounds": [[
@@ -160,10 +159,10 @@ def asdict(self):
160159
"version": self.version
161160
}
162161

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:
167166
"""Converts the tiled image asset into a RasterData object containing an
168167
np.ndarray.
169168
@@ -172,23 +171,18 @@ def as_raster_data(self,
172171
if self.tile_bounds.epsg == EPSG.SIMPLEPIXEL:
173172
xstart, ystart, xend, yend = self._get_simple_image_params(zoom)
174173
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)
176176
elif self.tile_bounds.epsg == EPSG.EPSG3857:
177177
#transform to 4326
178178
transformer = EPSGTransformer.create_geo_to_geo_transformer(
179179
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 = [
189181
transformer(self.tile_bounds.bounds[0]),
190182
transformer(self.tile_bounds.bounds[1])
191183
]
184+
xstart, ystart, xend, yend = self._get_3857_image_params(
185+
zoom, transforming_bounds)
192186
else:
193187
raise ValueError(f"Unsupported epsg found: {self.tile_bounds.epsg}")
194188

@@ -207,8 +201,8 @@ def as_raster_data(self,
207201
def value(self) -> np.ndarray:
208202
"""Returns the value of a generated RasterData object.
209203
"""
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
212206

213207
def _get_simple_image_params(self,
214208
zoom) -> Tuple[float, float, float, float]:
@@ -229,14 +223,14 @@ def _get_simple_image_params(self,
229223
for x in [xstart, ystart, xend, yend]
230224
],)
231225

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]:
233229
"""Computes the x and y tile bounds for fetching an image that
234230
captures the entire labeling region (TiledData.bounds) given a specific zoom
235231
"""
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
240234

241235
# Convert to zoom 0 tile coordinates
242236
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,
350344
total_n_tiles = (yend - ystart + 1) * (xend - xstart + 1)
351345
if total_n_tiles > max_tiles:
352346
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.")
354349

355350
@validator('zoom_levels')
356351
def validate_zoom_levels(cls, zoom_levels):
@@ -378,7 +373,7 @@ def _is_simple(epsg: EPSG) -> bool:
378373
return epsg == EPSG.SIMPLEPIXEL
379374

380375
@staticmethod
381-
def _get_ranges(bounds: np.ndarray):
376+
def _get_ranges(bounds: np.ndarray) -> Tuple[int, int]:
382377
"""helper function to get the range between bounds.
383378
384379
returns a tuple (x_range, y_range)"""
@@ -387,7 +382,7 @@ def _get_ranges(bounds: np.ndarray):
387382
return (x_range, y_range)
388383

389384
@staticmethod
390-
def _min_max_x_y(bounds: np.ndarray):
385+
def _min_max_x_y(bounds: np.ndarray) -> Tuple[int, int, int, int]:
391386
"""returns the min x, max x, min y, max y of a numpy array
392387
"""
393388
return np.min(bounds[:, 0]), np.max(bounds[:, 0]), np.min(
@@ -398,7 +393,7 @@ def geo_and_pixel(cls,
398393
src_epsg,
399394
pixel_bounds: TiledBounds,
400395
geo_bounds: TiledBounds,
401-
zoom=0):
396+
zoom=0) -> Callable:
402397
"""method to change from one projection to simple projection"""
403398

404399
pixel_bounds = pixel_bounds.bounds
@@ -420,7 +415,7 @@ def geo_and_pixel(cls,
420415

421416
if src_epsg == EPSG.SIMPLEPIXEL:
422417

423-
def transform(x: int, y: int):
418+
def transform(x: int, y: int) -> Callable:
424419
scaled_xy = (x * (global_x_range) / (local_x_range),
425420
y * (global_y_range) / (local_y_range))
426421

@@ -440,7 +435,7 @@ def transform(x: int, y: int):
440435
#handles 4326 from lat,lng
441436
elif src_epsg == EPSG.EPSG4326:
442437

443-
def transform(x: int, y: int):
438+
def transform(x: int, y: int) -> Callable:
444439
point_in_px = PygeoPoint.from_latitude_longitude(
445440
latitude=y, longitude=x).pixels(zoom)
446441

@@ -455,7 +450,7 @@ def transform(x: int, y: int):
455450
#handles 3857 from meters
456451
elif src_epsg == EPSG.EPSG3857:
457452

458-
def transform(x: int, y: int):
453+
def transform(x: int, y: int) -> Callable:
459454
point_in_px = PygeoPoint.from_meters(meter_y=y,
460455
meter_x=x).pixels(zoom)
461456

@@ -469,7 +464,7 @@ def transform(x: int, y: int):
469464

470465
@classmethod
471466
def create_geo_to_geo_transformer(cls, src_epsg: EPSG,
472-
tgt_epsg: EPSG) -> None:
467+
tgt_epsg: EPSG) -> Callable:
473468
"""method to change from one projection to another projection.
474469
475470
supports EPSG transformations not Simple.
@@ -487,7 +482,7 @@ def create_geo_to_pixel_transformer(cls,
487482
src_epsg,
488483
pixel_bounds: TiledBounds,
489484
geo_bounds: TiledBounds,
490-
zoom=0):
485+
zoom=0) -> Callable:
491486
"""method to change from a geo projection to Simple"""
492487

493488
transform_function = cls.geo_and_pixel(src_epsg=src_epsg,
@@ -501,7 +496,7 @@ def create_pixel_to_geo_transformer(cls,
501496
src_epsg,
502497
pixel_bounds: TiledBounds,
503498
geo_bounds: TiledBounds,
504-
zoom=0):
499+
zoom=0) -> Callable:
505500
"""method to change from a geo projection to Simple"""
506501
transform_function = cls.geo_and_pixel(src_epsg=src_epsg,
507502
pixel_bounds=pixel_bounds,
@@ -513,7 +508,9 @@ def _get_point_obj(self, point) -> Point:
513508
point = self.transformer(point.x, point.y)
514509
return Point(x=point[0], y=point[1])
515510

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]:
517514
if isinstance(shape, Point):
518515
return self._get_point_obj(shape)
519516
if isinstance(shape, Line) or isinstance(shape, Polygon):

0 commit comments

Comments
 (0)