11import os
22import re
33import io
4+ import json
45import urllib
56import requests
67import filetype
910from tqdm import tqdm
1011from PIL import Image
1112
13+ from abraia .inference .ops import mask_to_polygon
14+
1215from ..client import Abraia
1316from ..utils import HEADERS , load_image , load_url , list_dir , url_path
1417from .ops import train_test_split
15- from ..inference .detect import segment_objects
18+ from ..inference .sam import SAM
1619
1720
1821abraia = Abraia ()
@@ -28,7 +31,9 @@ def convert_to_jpg(src, save_output, max_size=1920):
2831 im = Image .open (src ).convert ('RGB' )
2932 im .thumbnail ([max_size , max_size ], Image .LANCZOS )
3033 phash = str (imagehash .phash (im ))
31- im .save (os .path .join (save_output , phash + '.jpg' ))
34+ filename = phash + '.jpg'
35+ im .save (os .path .join (save_output , filename ))
36+ return filename
3237
3338
3439def download_page (url ):
@@ -37,12 +42,19 @@ def download_page(url):
3742 return resp .text
3843
3944
40- def save_image_file (link , save_output , timeout = 10 , max_size = 1920 ):
45+ def save_image_file (link , upload_folder , existing_filenames = None , timeout = 10 , max_size = 1920 ):
4146 resp = requests .get (link , headers = HEADERS , allow_redirects = True , timeout = timeout )
4247 kind = filetype .guess (resp .content )
4348 if kind and kind .mime .startswith ('image' ):
4449 d = io .BytesIO (resp .content )
45- convert_to_jpg (d , save_output , max_size )
50+ import tempfile
51+ with tempfile .TemporaryDirectory () as temp_dir :
52+ filename = convert_to_jpg (d , temp_dir , max_size )
53+ if existing_filenames is None or filename not in existing_filenames :
54+ local_path = os .path .join (temp_dir , filename )
55+ abraia .upload_file (local_path , upload_folder )
56+ return True , filename
57+ return False , filename
4658 else :
4759 raise ValueError (f'Invalid image, not saving' )
4860
@@ -89,31 +101,45 @@ def search_images(query, limit=100, save_output='dataset', callback=None):
89101 """Search and download images from Google and Bing."""
90102 seen = set ()
91103 download_count = 0
92- os .makedirs (save_output , exist_ok = True )
104+ try :
105+ files = abraia .list_files (f"{ save_output } " )[0 ]
106+ existing_filenames = {f ['name' ] for f in files }
107+ except :
108+ existing_filenames = set ()
109+
93110 links = [search_google (query ), search_bing (query )]
94111 ends = [False ] * len (links )
112+
113+ pbar = tqdm (total = limit , desc = "Downloading images" ) if callback is None else None
114+
95115 for id in itertools .cycle (range (len (links ))):
96116 try :
97117 link = next (links [id ])
98118 if link not in seen :
99119 seen .add (link )
100120 if download_count < limit :
101121 try :
102- save_image_file (link , save_output )
103- download_count += 1
104- if callback :
105- callback ({'current' : download_count , 'total' : limit , 'link' : link })
106- else :
107- print (f"[%] Downloaded Image #{ download_count } from { link } " )
122+ uploaded , filename = save_image_file (link , save_output , existing_filenames = existing_filenames )
123+ if uploaded :
124+ download_count += 1
125+ if callback :
126+ callback ({'current' : download_count , 'total' : limit , 'filename' : filename })
127+ elif pbar :
128+ pbar .set_description (f"Downloaded { filename } " )
129+ pbar .update (1 )
108130 except Exception :
109- print ( f"[!] Error Image # { download_count } from { link } " )
131+ pass
110132 else :
111133 break
112134 except StopIteration :
113135 ends [id ] = True
114136 if set (ends ) == {True }:
115137 break
116- return list_dir (save_output )
138+
139+ if pbar :
140+ pbar .close ()
141+
142+ return abraia .list_files (f"{ save_output } /" )[0 ]
117143
118144
119145def download_file (path , folder ):
@@ -123,24 +149,6 @@ def download_file(path, folder):
123149 return dest
124150
125151
126- def detect_dino (img , classes , threshold = 0.3 , pipe = None ):
127- """Detect objects in an image using Grounding Dino."""
128- classes = [label .lower ().strip () for label in classes ]
129- labels = [f"{ label } ." if not label .endswith ('.' ) else label for label in classes ]
130- if pipe is None :
131- from transformers import pipeline
132- pipe = pipeline (task = "zero-shot-object-detection" , model = "IDEA-Research/grounding-dino-tiny" )
133- results = pipe (Image .fromarray (img ), candidate_labels = labels , threshold = threshold )
134- objects = []
135- for result in results :
136- score = result ["score" ]
137- if score > threshold :
138- label = result ["label" ].rpartition ('.' )[0 ]
139- xmin , ymin , xmax , ymax = result ['box' ].values ()
140- objects .append ({"label" : label , "score" : score , "box" : [xmin , ymin , xmax - xmin , ymax - ymin ]})
141- return objects
142-
143-
144152def list_datasets ():
145153 folders = abraia .list_files ()[1 ]
146154 return [folder ['name' ] for folder in folders if abraia .check_file (f"{ folder ['name' ]} /annotations.json" )]
@@ -151,47 +159,43 @@ def list_models(project):
151159 return [f ['name' ] for f in files if f ['name' ].endswith ('.onnx' )]
152160
153161
154- def load_annotations (project ):
155- annotations = abraia .load_json (f"{ project } /annotations.json" )
156- for annotation in annotations :
157- annotation ['path' ] = f"{ project } /{ annotation ['filename' ]} "
158- annotation ['url' ] = url_path (f"{ abraia .userid } /{ annotation ['path' ]} " )
159- return annotations
160-
161-
162- def load_labels (annotations ):
163- labels = []
164- for annotation in annotations :
165- for object in annotation .get ('objects' , []):
166- label = object .get ('label' )
167- if label and label not in labels :
168- labels .append (label )
169- return list (set (labels ))
170-
171-
172- def load_task (annotations ):
173- classify , detect , segment = False , False , False
174- for annotation in annotations :
175- for object in annotation .get ('objects' , []):
176- if 'polygon' in object :
177- segment = True
178- elif 'box' in object :
179- detect = True
180- elif 'label' in object :
181- classify = True
182- return 'segment' if segment else 'detect' if detect else 'classify' if classify else ''
183-
184-
185- def list_images (project ):
186- files = abraia .list_files (f"{ project } /" )[0 ]
187- files = [f for f in files if f ['type' ] in ['image/jpeg' , 'image/png' ]]
188- for data in files :
189- data ['url' ] = url_path (f"{ abraia .userid } /{ data ['path' ]} " )
190- return files
191-
192-
193- def save_annotations (project , annotations ):
194- abraia .save_json (f"{ project } /annotations.json" , annotations )
162+ class Annotator :
163+ def __init__ (self , model = "IDEA-Research/grounding-dino-tiny" , segment = False ):
164+ from transformers import pipeline
165+ self .pipe = pipeline (task = "zero-shot-object-detection" , model = model )
166+ self .segment_enabled = segment
167+ if self .segment_enabled :
168+ self .sam = SAM ()
169+
170+ def detect (self , img , classes , threshold = 0.3 ):
171+ classes = [label .lower ().strip () for label in classes ]
172+ labels = [f"{ label } ." if not label .endswith ('.' ) else label for label in classes ]
173+ results = self .pipe (Image .fromarray (img ), candidate_labels = labels , threshold = threshold )
174+ objects = []
175+ for result in results :
176+ score = result ["score" ]
177+ if score > threshold :
178+ label = result ["label" ].rpartition ('.' )[0 ]
179+ xmin , ymin , xmax , ymax = result ['box' ].values ()
180+ objects .append ({"label" : label , "score" : score , "box" : [xmin , ymin , xmax - xmin , ymax - ymin ]})
181+ return objects
182+
183+ def segment (self , img , objects ):
184+ self .sam .encode (img )
185+ for result in objects :
186+ x , y , w , h = result ['box' ]
187+ mask = self .sam .predict (img , prompt = json .dumps ([{"type" : "rectangle" , "data" : [x , y , x + w , y + h ]}]))
188+ result ['polygon' ] = mask_to_polygon (mask [y :y + h , x :x + w ], (x , y ))
189+ return objects
190+
191+ def annotate (self , img , label , threshold = 0.3 ):
192+ objects = self .detect (img , [label ], threshold = threshold )
193+ if objects and self .segment_enabled :
194+ try :
195+ objects = self .segment (img , objects )
196+ except :
197+ return None
198+ return objects
195199
196200
197201class Dataset :
@@ -204,37 +208,64 @@ def __init__(self, project):
204208
205209 def load (self ):
206210 if self .project in list_datasets ():
207- self .annotations = load_annotations (self .project )
208- self .classes = load_labels (self .annotations )
209- self .task = load_task (self .annotations )
210- self .images = list_images (self .project )
211+ self .annotations = self ._load_annotations (self .project )
212+ self .classes , self .task = self ._process_annotations (self .annotations )
213+ self .images = self ._list_images (self .project )
211214 return self
212215
216+ def _load_annotations (self , project ):
217+ annotations = abraia .load_json (f"{ project } /annotations.json" )
218+ for annotation in annotations :
219+ annotation ['path' ] = f"{ project } /{ annotation ['filename' ]} "
220+ annotation ['url' ] = url_path (f"{ abraia .userid } /{ annotation ['path' ]} " )
221+ return annotations
222+
223+ def _process_annotations (self , annotations ):
224+ labels = set ()
225+ classify , detect , segment = False , False , False
226+ for annotation in annotations :
227+ for obj in annotation .get ('objects' , []):
228+ label = obj .get ('label' )
229+ if label :
230+ labels .add (label )
231+ classify = True
232+ if 'polygon' in obj :
233+ segment = True
234+ elif 'box' in obj :
235+ detect = True
236+ return list (labels ), 'segment' if segment else 'detect' if detect else 'classify' if classify else ''
237+
238+ def _list_images (self , project ):
239+ files = abraia .list_files (f"{ project } /" )[0 ]
240+ files = [f for f in files if f ['type' ] in ['image/jpeg' , 'image/png' ]]
241+ for data in files :
242+ data ['url' ] = url_path (f"{ abraia .userid } /{ data ['path' ]} " )
243+ return files
244+
213245 def annotate (self , label , segment = False , callback = None ):
214- from transformers import pipeline
215246 annotated_filenames = {a ['filename' ] for a in self .annotations }
216247 images = [img for img in self .images if img ['name' ] not in annotated_filenames ]
217- pipe = pipeline (task = "zero-shot-object-detection" , model = "IDEA-Research/grounding-dino-tiny" )
218- for i , row in enumerate (tqdm (images )):
248+ annotator = Annotator (segment = segment )
249+
250+ pbar = tqdm (images ) if callback is None else None
251+ iterable = pbar if pbar else images
252+ for i , row in enumerate (iterable ):
253+ if pbar :
254+ pbar .set_description (f"Annotating { row ['name' ]} " )
219255 url , filename = row ['url' ], row ['name' ]
220256 img = load_image (load_url (url ))
221- objects = detect_dino (img , [label ], pipe = pipe )
257+ objects = annotator .annotate (img , label )
258+ annotation = {'url' : url , 'filename' : filename , 'objects' : objects }
259+ self .annotations .append (annotation )
260+ self .save ()
222261 if callback :
223- callback ({'current' : i + 1 , 'total' : len (images )})
224- if objects :
225- if segment :
226- try :
227- objects = segment_objects (img , objects )
228- except :
229- continue
230- annotation = {'url' : url , 'filename' : filename , 'objects' : objects }
231- self .annotations .append (annotation )
262+ callback ({'current' : i + 1 , 'total' : len (images ), 'filename' : filename })
263+ if pbar :
264+ pbar .close ()
232265 return self .annotations
233266
234- def save (self , annotations = None ):
235- if annotations is not None :
236- self .annotations = annotations
237- save_annotations (self .project , self .annotations )
267+ def save (self ):
268+ abraia .save_json (f"{ self .project } /annotations.json" , self .annotations )
238269
239270 def split (self ):
240271 # TODO: Split dataset by classes to avoid class imbalance
0 commit comments