Skip to content

Commit 7a654b0

Browse files
committed
Refactor dataset adding Dataset class
1 parent edad721 commit 7a654b0

4 files changed

Lines changed: 277 additions & 178 deletions

File tree

abraia/training/__init__.py

Lines changed: 12 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,24 @@
1-
2-
from ..client import Abraia
3-
from ..utils import url_path, make_dirs
4-
from . import classify, detect
5-
61
import os
72
import shutil
83
import itertools
4+
95
from PIL import Image
10-
from tqdm.contrib.concurrent import process_map
11-
from sklearn.model_selection import train_test_split
126
from typing import Dict, Any
7+
from tqdm.contrib.concurrent import process_map
138

14-
15-
abraia = Abraia()
16-
17-
18-
def list_datasets():
19-
folders = abraia.list_files()[1]
20-
return [folder['name'] for folder in folders if abraia.check_file(f"{folder['name']}/annotations.json")]
21-
22-
23-
def list_images(project):
24-
files = abraia.list_files(f"{project}/")[0]
25-
dataset = [f for f in files if f['type'] in ['image/jpeg', 'image/png']]
26-
for data in dataset:
27-
data['url'] = url_path(f"{abraia.userid}/{data['path']}")
28-
return dataset
29-
30-
31-
def load_annotations(project):
32-
annotations = abraia.load_json(f"{project}/annotations.json")
33-
for annotation in annotations:
34-
annotation['path'] = f"{project}/{annotation['filename']}"
35-
annotation['url'] = url_path(f"{abraia.userid}/{annotation['path']}")
36-
return annotations
37-
38-
39-
def save_annotations(project, annotations):
40-
abraia.save_json(f"{project}/annotations.json", annotations)
41-
42-
43-
def load_labels(annotations):
44-
labels = []
45-
for annotation in annotations:
46-
for object in annotation.get('objects', []):
47-
label = object.get('label')
48-
if label and label not in labels:
49-
labels.append(label)
50-
return list(set(labels))
9+
from . import classify, detect
10+
from .dataset import list_datasets, load_dataset, search_images, list_models
11+
from ..utils import make_dirs, download_file
5112

5213

53-
def load_task(annotations):
54-
classify, detect, segment = False, False, False
55-
for annotation in annotations:
56-
for object in annotation.get('objects', []):
57-
if 'polygon' in object:
58-
segment = True
59-
elif 'box' in object:
60-
detect = True
61-
elif 'label' in object:
62-
classify = True
63-
if segment:
64-
return 'segment'
65-
if detect:
66-
return 'detect'
67-
if classify:
68-
return 'classify'
69-
70-
71-
def load_tasks(annotations):
14+
def load_tasks(task):
7215
tasks = ['classify', 'detect', 'segment']
73-
task = load_task(annotations)
7416
if task:
7517
idx = tasks.index(task)
7618
return tasks[:idx+1]
7719
return []
7820

7921

80-
def download_file(path, folder):
81-
dest = os.path.join(folder, os.path.basename(path))
82-
if not os.path.exists(dest):
83-
abraia.download_file(path, dest)
84-
return dest
85-
86-
8722
def save_annotation(annotation, folder, classes, task):
8823
if task == 'classify':
8924
for object in annotation.get('objects', []):
@@ -146,19 +81,10 @@ def save_config(dataset, classes):
14681
f.write(yaml_content)
14782

14883

149-
def split_dataset(annotations):
150-
# TODO: Split dataset by classes to avoid class imbalance
151-
backgrounds = [annotation for annotation in annotations if not annotation.get('objects')]
152-
annotations = [annotation for annotation in annotations if annotation.get('objects')]
153-
train, test = train_test_split(annotations, test_size=0.3)
154-
val, test = train_test_split(test, test_size=0.5)
155-
train.extend(backgrounds)
156-
return train, val, test
157-
158-
15984
class ModelTrainer:
16085
"""High-level trainer orchestrator using models and dataset utilities."""
161-
def __init__(self, task: str, imgsz: int = 640):
86+
def __init__(self, task: str, imgsz: int = None):
87+
imgsz = imgsz or (224 if task == 'classify' else 640)
16288
self.task = task
16389
if task == 'classify':
16490
self.model = classify.Model()
@@ -167,15 +93,15 @@ def __init__(self, task: str, imgsz: int = 640):
16793

16894
def prepare_dataset(self, project: str, classes, force: bool = False):
16995
if force or not os.path.exists(project):
170-
annotations = load_annotations(project)
171-
train, val, test = split_dataset(annotations)
96+
dataset = load_dataset(project)
97+
train, val, test = dataset.split()
17298
data_annotations = {'train': train, 'val': val, 'test': test}
173-
#TODO: Download files in one single step
17499
for x in ['train', 'val', 'test']:
175100
save_data(data_annotations[x], f"{project}/{x}", classes, self.task)
176101
save_config(project, classes)
177102

178-
def train(self, dataset: str, epochs: int, batch: int = 32, **kwargs) -> None:
103+
def train(self, dataset: str, epochs: int = None, batch: int = 32) -> None:
104+
epochs = epochs or (30 if self.task == 'classify' else 300)
179105
if self.task == 'classify':
180106
self.model.train(dataset, epochs=epochs)
181107
else:
@@ -193,4 +119,3 @@ def save(self, dataset: str, classes, device='cpu') -> None:
193119

194120
def run(self, img):
195121
return self.model.run(img)
196-

abraia/training/dataset.py

Lines changed: 132 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,13 @@
99

1010
from tqdm import tqdm
1111
from PIL import Image
12-
from ..utils import HEADERS, load_image, load_url, list_dir
13-
from . import list_datasets, list_images, load_annotations, save_annotations
12+
from sklearn.model_selection import train_test_split
13+
14+
from ..client import Abraia
15+
from ..utils import HEADERS, load_image, load_url, list_dir, url_path
16+
from ..inference.detect import segment_objects
17+
18+
abraia = Abraia()
1419

1520
GOOGLE_BASE_URL = 'https://www.google.com/search?q='
1621
GOOGLE_PICTURE_ID = '''&biw=1536&bih=674&tbm=isch&sxsrf=ACYBGNSXXpS6YmAKUiLKKBs6xWb4uUY5gA:1581168823770&source=lnms&sa=X&ved=0ahUKEwioj8jwiMLnAhW9AhAIHbXTBMMQ_AUI3QUoAQ'''
@@ -24,7 +29,7 @@ def download_page(url):
2429
return resp.text
2530

2631

27-
def save_image(link, output_dir, timeout=10, max_size=1920):
32+
def save_image_file(link, output_dir, timeout=10, max_size=1920):
2833
resp = requests.get(link, headers=HEADERS, allow_redirects=True, timeout=timeout)
2934
kind = filetype.guess(resp.content)
3035
if kind and kind.mime.startswith('image'):
@@ -39,27 +44,27 @@ def save_image(link, output_dir, timeout=10, max_size=1920):
3944

4045

4146
def get_filter(shorthand):
42-
if shorthand == "line" or shorthand == "linedrawing":
43-
return "+filterui:photo-linedrawing"
44-
elif shorthand == "photo":
45-
return "+filterui:photo-photo"
46-
elif shorthand == "clipart":
47-
return "+filterui:photo-clipart"
48-
elif shorthand == "gif" or shorthand == "animatedgif":
49-
return "+filterui:photo-animatedgif"
50-
elif shorthand == "transparent":
51-
return "+filterui:photo-transparent"
52-
else:
53-
return ""
54-
47+
if shorthand == "line" or shorthand == "linedrawing":
48+
return "+filterui:photo-linedrawing"
49+
elif shorthand == "photo":
50+
return "+filterui:photo-photo"
51+
elif shorthand == "clipart":
52+
return "+filterui:photo-clipart"
53+
elif shorthand == "gif" or shorthand == "animatedgif":
54+
return "+filterui:photo-animatedgif"
55+
elif shorthand == "transparent":
56+
return "+filterui:photo-transparent"
57+
else:
58+
return ""
59+
5560

5661
def scan_bing_page(html):
5762
links = re.findall('murl":"(.*?)"', html)
5863
for link in links:
5964
link = link.replace(" ", "%20")
6065
yield link
6166

62-
67+
6368
def search_bing(query, limit=50, adult='off', filter=''):
6469
for page_counter in range(100):
6570
# Parse the page source and download pics
@@ -106,7 +111,7 @@ def download(query, limit=100, output_dir='dataset', verbose=True):
106111
seen.add(link)
107112
if download_count < limit:
108113
try:
109-
save_image(link, output_dir)
114+
save_image_file(link, output_dir)
110115
download_count += 1
111116
if verbose:
112117
print(f"[%] Downloaded Image #{download_count} from {link}")
@@ -119,12 +124,20 @@ def download(query, limit=100, output_dir='dataset', verbose=True):
119124
if set(ends) == {True}:
120125
break
121126

127+
122128
def search_images(query, limit=100, output_dir='dataset', verbose=True):
123129
"""Search and download images from Google and Bing."""
124130
download(query, limit=limit, output_dir=output_dir, verbose=verbose)
125131
return list_dir(output_dir)
126132

127133

134+
def download_file(path, folder):
135+
dest = os.path.join(folder, os.path.basename(path))
136+
if not os.path.exists(dest):
137+
abraia.download_file(path, dest)
138+
return dest
139+
140+
128141
# As the Grounding DINO model was trained with a "." after each text, we'll do the same here.
129142
def preprocess_caption(caption: str) -> str:
130143
result = caption.lower().strip()
@@ -149,17 +162,116 @@ def annotate_image(pipe, img, classes, threshold=0.3):
149162
return objects
150163

151164

152-
def annotate_images(images, classes):
165+
def annotate_images(images, classes, segment=False):
153166
"""Annotate a dataset using Grounding Dino."""
154167
from transformers import pipeline
155168
pipe = pipeline(task="zero-shot-object-detection", model="IDEA-Research/grounding-dino-tiny")
156-
#pipe = pipeline(task="zero-shot-object-detection", model="google/owlv2-base-patch16-ensemble")
157169
annotations = []
158170
for row in tqdm(images):
159171
url, filename = row['url'], row['name']
160172
img = load_image(load_url(url))
161173
objects = annotate_image(pipe, img, classes)
162174
if objects:
175+
if segment:
176+
try:
177+
objects = segment_objects(img, objects)
178+
except:
179+
continue
163180
annotation = {'url': url, 'filename': filename, 'objects': objects}
164181
annotations.append(annotation)
165182
return annotations
183+
184+
185+
def list_datasets():
186+
folders = abraia.list_files()[1]
187+
return [folder['name'] for folder in folders if abraia.check_file(f"{folder['name']}/annotations.json")]
188+
189+
190+
def list_models(project):
191+
files = abraia.list_files(f"{project}/")[0]
192+
return [f['name'] for f in files if f['name'].endswith('.onnx')]
193+
194+
195+
def load_annotations(project):
196+
annotations = abraia.load_json(f"{project}/annotations.json")
197+
for annotation in annotations:
198+
annotation['path'] = f"{project}/{annotation['filename']}"
199+
annotation['url'] = url_path(f"{abraia.userid}/{annotation['path']}")
200+
return annotations
201+
202+
203+
def load_labels(annotations):
204+
labels = []
205+
for annotation in annotations:
206+
for object in annotation.get('objects', []):
207+
label = object.get('label')
208+
if label and label not in labels:
209+
labels.append(label)
210+
return list(set(labels))
211+
212+
213+
def load_task(annotations):
214+
classify, detect, segment = False, False, False
215+
for annotation in annotations:
216+
for object in annotation.get('objects', []):
217+
if 'polygon' in object:
218+
segment = True
219+
elif 'box' in object:
220+
detect = True
221+
elif 'label' in object:
222+
classify = True
223+
return 'segment' if segment else 'detect' if detect else 'classify' if classify else ''
224+
225+
226+
def list_images(project):
227+
files = abraia.list_files(f"{project}/")[0]
228+
files = [f for f in files if f['type'] in ['image/jpeg', 'image/png']]
229+
for data in files:
230+
data['url'] = url_path(f"{abraia.userid}/{data['path']}")
231+
return files
232+
233+
234+
def save_annotations(project, annotations):
235+
abraia.save_json(f"{project}/annotations.json", annotations)
236+
237+
238+
class Dataset:
239+
def __init__(self, project):
240+
self.project = project
241+
self.annotations = []
242+
self.classes = []
243+
self.task = ''
244+
self.images = []
245+
246+
def load(self):
247+
if self.project in list_datasets():
248+
self.annotations = load_annotations(self.project)
249+
self.classes = load_labels(self.annotations)
250+
self.task = load_task(self.annotations)
251+
self.images = list_images(self.project)
252+
return self
253+
254+
def annotate(self, label, segment=False):
255+
annotated_filenames = {a['filename'] for a in self.annotations}
256+
images = [img for img in self.images if img['name'] not in annotated_filenames]
257+
new_annotations = annotate_images(images, [label], segment=segment)
258+
self.annotations.extend(new_annotations)
259+
return self.annotations
260+
261+
def save(self, annotations=None):
262+
if annotations is not None:
263+
self.annotations = annotations
264+
save_annotations(self.project, self.annotations)
265+
266+
def split(self):
267+
# TODO: Split dataset by classes to avoid class imbalance
268+
backgrounds = [annotation for annotation in self.annotations if not annotation.get('objects')]
269+
annotations = [annotation for annotation in self.annotations if annotation.get('objects')]
270+
train, test = train_test_split(annotations, test_size=0.3)
271+
val, test = train_test_split(test, test_size=0.5)
272+
train.extend(backgrounds)
273+
return train, val, test
274+
275+
276+
def load_dataset(project):
277+
return Dataset(project).load()

0 commit comments

Comments
 (0)