Skip to content

Commit 5006eee

Browse files
authored
Merge pull request #396 from aperture-data/release-0.4.21
Release 0.4.21
2 parents 937b5bf + 40460e0 commit 5006eee

File tree

9 files changed

+360
-78
lines changed

9 files changed

+360
-78
lines changed

aperturedb/Connector.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,16 @@ def _renew_session(self):
462462
count += 1
463463

464464
def create_new_connection(self) -> Connector:
465+
"""
466+
Create a new connection object with the same parameters as the current one.
467+
This is important in multi-threaded applications, where each thread should have its own connection object.
468+
Because the connection object is not thread-safe, it is not safe to share it between threads.
469+
Be cautious when using this method, as it will create a new connection to the database, which will consume resources.
470+
Ideally, this method should be called once for each thread.
471+
472+
Returns:
473+
connection: Clone of original connection
474+
"""
465475
return type(self)(
466476
self.host,
467477
self.port,

aperturedb/Images.py

Lines changed: 99 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from __future__ import annotations
2-
from typing import Tuple, Union
2+
from typing import Any, Dict, Iterable, List, Tuple, Union
33
import cv2
44
import math
55
import numpy as np
@@ -19,26 +19,55 @@
1919
logger = logging.getLogger(__name__)
2020

2121

22-
def np_arr_img_to_bytes(arr, format='JPEG') -> bytes:
22+
def np_arr_img_to_bytes(arr, format: str = 'JPEG') -> bytes:
23+
"""
24+
**Convert a NumPy array to bytes**
25+
26+
Args:
27+
arr: The NumPy array to convert
28+
format: The format of the image. Defaults to "JPEG".
29+
30+
Returns:
31+
bytes: The image as bytes
32+
"""
2333
return image_to_bytes(Image.fromarray(arr, 'RGB'), format=format)
2434

2535

26-
def image_to_bytes(image: Image, format="JPEG") -> bytes:
36+
def image_to_bytes(image: Image, format: str = "JPEG") -> bytes:
37+
"""
38+
**Convert an image to bytes**
39+
40+
Args:
41+
image (PIL.Image): The image to convert
42+
format (str, optional): The format of the image. Defaults to "JPEG".
43+
44+
Returns:
45+
bytes: The image as bytes
46+
"""
2747
with BytesIO() as bytes:
2848
image.save(bytes, format=format)
2949
return bytes.getvalue()
3050

3151

32-
def rotate(points, angle, c_x=None, c_y=None):
52+
def rotate(points, angle, c_x=0, c_y=0):
3353
"""
3454
**Rotate a set of points around a center**
55+
56+
Args:
57+
points: The points to rotate; iterable of (x, y) pairs
58+
angle: The angle of counterclockwise rotation in degrees
59+
c_x: The x coordinate of the center of rotation
60+
c_y: The y coordinate of the center of rotation
61+
62+
Returns:
63+
points: The rotated points as a NumPy array of shape (n,2) and type int
3564
"""
3665
ANGLE = np.deg2rad(angle)
3766
return np.array(
3867
[
3968
[
40-
c_x + np.cos(ANGLE) * (px - c_x) - np.sin(ANGLE) * (py - c_x),
41-
c_y + np.sin(ANGLE) * (px - c_y) + np.cos(ANGLE) * (py - c_y)
69+
c_x + np.cos(ANGLE) * (px - c_x) - np.sin(ANGLE) * (py - c_y),
70+
c_y + np.sin(ANGLE) * (px - c_x) + np.cos(ANGLE) * (py - c_y)
4271
]
4372
for px, py in points
4473
]
@@ -49,12 +78,15 @@ def resolve(points: np.array, image_meta, operations) -> np.array:
4978
"""
5079
**Resolve the coordinates of a bounding box to the original image size**
5180
81+
Given an array of (x,y) points with respect to the coordinates of an image, returns the corresponding coordinates with respect to a the resized or rotated image that resulted from a series of operations.
82+
5283
Args:
53-
coordinates (dict): The coordinates of the bounding box
84+
points (NumPy array): The coordinates of the bounding box; shape (...,2)
5485
image (dict): The image properties
86+
operations (list): The operations applied to the image
5587
5688
Returns:
57-
dict: The resolved coordinates
89+
resolved_points (NumPy array): The resolved coordinates, same shape as points
5890
"""
5991
resolved = points.copy()
6092
if image_meta["adb_image_width"] and image_meta["adb_image_height"]:
@@ -94,6 +126,9 @@ class Images(Entities):
94126
***It includes utility methods to visualize image and annotations**
95127
Inter convert the representation into NumPy matrices and find similar images,
96128
related bounding boxes, etc.
129+
130+
Args:
131+
db: The database connector, perhaps as returned by `Utils.create_connector`
97132
"""
98133
db_object = "_Image"
99134

@@ -153,27 +188,27 @@ def __init__(self, db, batch_size=100, response=None, **kwargs):
153188

154189
self.db_connector = db
155190

156-
self.images = {}
157-
self.images_ids = []
158-
self.image_sizes = []
159-
self.images_bboxes = {}
191+
self.images = {}
192+
self.images_ids = []
193+
self.image_sizes = []
194+
self.images_bboxes = {}
160195
self.images_polygons = {}
161196
self.overlays = []
162197
self.color_for_tag = {}
163198

164199
self.constraints = None
165-
self.operations = None
166-
self.format = None
167-
self.limit = None
168-
self.query = None
200+
self.operations = None
201+
self.format = None
202+
self.limit = None
203+
self.query = None
169204

170205
self.adjacent = {}
171206

172207
self.batch_size = batch_size
173208
self.total_cached_images = 0
174209
self.display_limit = 20
175210

176-
self.img_id_prop = "_uniqueid"
211+
self.img_id_prop = "_uniqueid"
177212
self.bbox_label_prop = "_label"
178213
self.get_image = True
179214

@@ -204,10 +239,10 @@ def __retrieve_batch(self, index):
204239
# for that batch
205240

206241
total_batches = math.ceil(len(self.images_ids) / self.batch_size)
207-
batch_id = int(math.floor(index / self.batch_size))
242+
batch_id = int(math.floor(index / self.batch_size))
208243

209244
start = batch_id * self.batch_size
210-
end = min(start + self.batch_size, len(self.images_ids))
245+
end = min(start + self.batch_size, len(self.images_ids))
211246

212247
query = []
213248

@@ -290,8 +325,8 @@ def __retrieve_polygons(self, index, constraints, tag_key, tag_format):
290325
res, _ = self.db_connector.query(query)
291326

292327
polygons = []
293-
bounds = []
294-
tags = []
328+
bounds = []
329+
tags = []
295330
meta = []
296331
polys = res[1]["FindPolygon"]["entities"]
297332
operations = self.query["operations"] if self.query and "operations" in self.query else [
@@ -380,7 +415,7 @@ def __retrieve_bounding_boxes(self, index, constraints):
380415
try:
381416
res, images = self.db_connector.query(query)
382417
bboxes = []
383-
tags = []
418+
tags = []
384419
meta = []
385420
bounds = []
386421
if "entities" in res[1]["FindBoundingBox"]:
@@ -403,8 +438,8 @@ def __retrieve_bounding_boxes(self, index, constraints):
403438
f"Cannot retrieve bounding boxes for image {uniqueid}", exc_info=True)
404439
finally:
405440
self.images_bboxes[uniqueid_str]["bboxes"] = bboxes
406-
self.images_bboxes[uniqueid_str]["tags"] = tags
407-
self.images_bboxes[uniqueid_str]["meta"] = meta
441+
self.images_bboxes[uniqueid_str]["tags"] = tags
442+
self.images_bboxes[uniqueid_str]["meta"] = meta
408443
self.images_bboxes[uniqueid_str]["bounds"] = bounds
409444

410445
def total_results(self) -> int:
@@ -493,9 +528,9 @@ def search(self, constraints=None, operations=None, format=None, limit=None, sor
493528
"""
494529

495530
self.constraints = constraints
496-
self.operations = operations
497-
self.format = format
498-
self.limit = limit
531+
self.operations = operations
532+
self.format = format
533+
self.limit = limit
499534

500535
self.images = {}
501536
self.images_ids = []
@@ -745,9 +780,9 @@ def __draw_polygon_and_tag(self, image, polygon, tag, bounds, meta, deferred_tag
745780
self.__draw_polygon(image, polygon, color)
746781

747782
if tag:
748-
left = bounds["x"]
749-
top = bounds["y"]
750-
right = bounds["x"] + bounds["width"]
783+
left = bounds["x"]
784+
top = bounds["y"]
785+
right = bounds["x"] + bounds["width"]
751786
FONT_WIDTH = 10
752787
FONT_HEIGHT = 16
753788

@@ -769,23 +804,25 @@ def display(self,
769804
limit: Union[int, object] = None,
770805
polygon_constraints: Constraints = None,
771806
polygon_tag_key: str = "_label",
772-
polygon_tag_format: str = "{}"):
807+
polygon_tag_format: str = "{}") -> None:
773808
"""
774809
**Display images with annotations**
775-
show_bboxes: bool, optional
776-
Show bounding boxes, by default False
777-
bbox_constraints: Constraints, optional
778-
Constraints for bounding boxes, by default None
779-
show_polygons: bool, optional
780-
Show polygons, by default False
781-
limit: Union[int, object], optional
782-
Number of images to display, by default None
783-
polygon_constraints: Constraints, optional
784-
Constraints for polygons, by default None
785-
polygon_tag_key: str, optional
786-
Key for the polygon tag, by default "_label"
787-
polygon_tag_format: str, optional
788-
Format for the polygon tag, by default "{}"
810+
811+
Args:
812+
show_bboxes: bool, optional
813+
Show bounding boxes, by default False
814+
bbox_constraints: Constraints, optional
815+
Constraints for bounding boxes, by default None
816+
show_polygons: bool, optional
817+
Show polygons, by default False
818+
limit: Union[int, object], optional
819+
Number of images to display, by default None
820+
polygon_constraints: Constraints, optional
821+
Constraints for polygons, by default None
822+
polygon_tag_key: str, optional
823+
Key for the polygon tag, by default "_label"
824+
polygon_tag_format: str, optional
825+
Format for the polygon tag, by default "{}"
789826
"""
790827
if not limit:
791828
limit = self.display_limit
@@ -803,7 +840,7 @@ def display(self,
803840
limit -= 1
804841

805842
uniqueid = str(self.images_ids[i])
806-
image = self.get_image_by_index(i)
843+
image = self.get_image_by_index(i)
807844

808845
# Just decode the image from buffer
809846
nparr = np.frombuffer(image, dtype=np.uint8)
@@ -815,7 +852,7 @@ def display(self,
815852
self.__retrieve_bounding_boxes(i, bbox_constraints)
816853

817854
bboxes = self.images_bboxes[uniqueid]["bboxes"]
818-
tags = self.images_bboxes[uniqueid]["tags"]
855+
tags = self.images_bboxes[uniqueid]["tags"]
819856
meta = self.images_bboxes[uniqueid]["meta"]
820857
bounds = self.images_bboxes[uniqueid]["bounds"]
821858

@@ -860,8 +897,13 @@ def display(self,
860897
fig1, ax1 = plt.subplots()
861898
plt.imshow(image)
862899

863-
def get_props_names(self):
900+
def get_props_names(self) -> List[str]:
901+
"""
902+
**Get the names of the properties that apply to images**
864903
904+
Returns:
905+
properties (List[str]): The names of the properties of the images
906+
"""
865907
dbutils = Utils.Utils(self.db_connector)
866908
schema = dbutils.get_schema()
867909

@@ -874,8 +916,16 @@ def get_props_names(self):
874916

875917
return props_array
876918

877-
def get_properties(self, prop_list=[]):
919+
def get_properties(self, prop_list: Iterable[str] = []) -> Dict[str, Any]:
920+
"""
921+
**Get the properties of the images**
922+
923+
Args:
924+
prop_list (List[str], optional): The list of properties to retrieve. Defaults to [].
878925
926+
Returns:
927+
property_values Dict[str, Any]: The properties of the images
928+
"""
879929
if len(prop_list) == 0:
880930
return {}
881931

aperturedb/PolygonDataCSV.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class PolygonDataCSV(CSVParser.CSVParser):
3232
3333
**_label**: optionally applies a label to the polygon objects.
3434
35-
**polygons**: a JSON array of polygon regions. Each polygon region is itself an array of [x,y] vertices that describe the boundary of a single contiguous polygon. See also [Polygon API parameter](query_language/Reference/shared_command_parameters/polygons).
35+
**polygons**: a JSON array of polygon regions. Each polygon region is itself an array of [x,y] vertices that describe the boundary of a single contiguous polygon. See also [Polygon API parameter](/query_language/Reference/shared_command_parameters/polygons).
3636
3737
Example CSV file::
3838

0 commit comments

Comments
 (0)