forked from lozuwa/impy
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathObjectDetectionDataset.py
executable file
·892 lines (858 loc) · 36.8 KB
/
ObjectDetectionDataset.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
"""
Author: Rodrigo Loza
Email: [email protected]
Description: A class that allows to load a dataset and perform
useful operations with it.
"""
import os
import json
import math
import numpy as np
from interface import implements
from tqdm import tqdm
try:
from .ObjectDetectionDatasetPreprocessMethods import *
except:
from ObjectDetectionDatasetPreprocessMethods import *
try:
from .ObjectDetectionDatasetStatisticsMethods import *
except:
from ObjectDetectionDatasetStatisticsMethods import *
try:
from .ImagePreprocess import *
except:
from ImagePreprocess import *
try:
from .ImageAnnotation import *
except:
from ImageAnnotation import *
try:
from .VectorOperations import *
except:
from VectorOperations import *
try:
from .Util import *
except:
from Util import *
try:
from .AssertDataTypes import *
except:
from AssertDataTypes import *
try:
from .AugmentationConfigurationFile import *
except:
from AugmentationConfigurationFile import *
try:
from .ApplyAugmentation import applyBoundingBoxAugmentation, applyColorAugmentation
except:
from ApplyAugmentation import applyBoundingBoxAugmentation, applyColorAugmentation
prep = ImagePreprocess()
dataAssertion = AssertDataTypes()
class ObjectDetectionDataset(implements(ObjectDetectionDatasetPreprocessMethods, \
ObjectDetectionDatasetStatisticsMethods)):
def __init__(self, imagesDirectory = None, annotationsDirectory = None, databaseName = None):
"""
A high level data structure used for image localization datasets.
Args:
imagesDirectory = None,
annotationsDirectory = None,
databaseName = None
Returns:
None
"""
super(ObjectDetectionDataset, self).__init__()
# Assert images and annotations
if (not os.path.isdir(imagesDirectory)):
raise Exception("Path to images does not exist.")
if (not os.path.isdir(annotationsDirectory)):
raise Exception("Path to annotations does not exist.")
if (databaseName == None):
databaseName = "Unspecified"
# Class variables
self.imagesDirectory = imagesDirectory
self.annotationsDirectory = annotationsDirectory
self.databaseName = databaseName
# Preprocessing.
def dataConsistency(self):
"""
Checks whether data is consistent. It analyses if there is the same amount of
of images and annotations. Then it reviews if the annotation and image names
are consistent with each other.
Args:
None
Returns:
None
Raises:
- Exception: when the extension of the image is not allowed. Only jpgs and pngs are allowed.
- Exception: When an annotation file does not have a .xml extension.
- Exception: When the amount of annotations and images is not equal.
- Exception: When there are images that don't have annotations.
- Exception: When there are annotations that don't have images.
"""
# Local variables.
images = []
annotations = []
# Preprocess images.
for image in tqdm(os.listdir(self.imagesDirectory)):
# Extract name.
extension = Util.detect_file_extension(filename = image)
if (extension == None):
raise Exception("Your image extension is not valid: {}".format(extension) +\
" Only jpgs and pngs are allowed.")
images.append(image.split(extension)[0])
# Preprocess annotations.
for annotation in tqdm(os.listdir(self.annotationsDirectory)):
if (not annotation.endswith(".xml")):
raise Exception("Only xml annotations are allowed: {}".format(annotation))
annotations.append(annotation.split(".xml")[0])
# Convert lists to sets.
imagesSet = set(images)
annotationsSet = set(annotations)
# Check name consistency.
imgToAnnt = imagesSet.difference(annotationsSet)
anntToImg = annotationsSet.difference(imagesSet)
# Check size consistency.
if (len(imagesSet) != len(annotationsSet)):
print("Images to annotations: ", imgToAnnt)
print("Annotations to images: ", anntToImg)
raise Exception("The amount of images({}) and annotations({}) is not equal."\
.format(len(imagesSet), len(annotationsSet)))
if (len(imgToAnnt) != 0):
raise Exception("There are more images than annotations: {}".format(imgToAnnt))
if (len(anntToImg) != 0):
raise Exception("There are more annotations than images: {}".format(anntToImg))
def findEmptyOrWrongAnnotations(self, removeEmpty = None):
"""
Find empty or irregular annotations in the annotation files. An empty
annotation is an annotation that includes no objects. And a irregular
annotation is an annotation that has a bounding box with coordinates that
are off the image's boundaries.
Args:
removeEmpty: A boolean that if True removes the annotation and image that are empty.
Returns:
None
Raises:
- Exception: when the extension of the image is not allowed. Only jpgs and pngs are allowed.
- Exception: when an annotation file is empty.
- Exception: when a coordinate is not valid. Either less than zero or greater than image's size.
"""
# Assertions
if (removeEmpty == None):
removeEmpty = False
# Local variables
emptyAnnotations = []
files = os.listdir(self.imagesDirectory)
# Logic
for file in tqdm(files):
# In case a folder is found, report it.
if (os.path.isdir(file)):
continue
# Otherwise, continue.
extension = Util.detect_file_extension(filename = file)
if (extension == None):
raise Exception("ERROR: Your image extension is not valid: {}".format(extension) +\
" Only jpgs and pngs are allowed.")
# Extract name
filename = os.path.split(file)[1].split(extension)[0]
# Create xml and img name
imgFullPath = os.path.join(self.imagesDirectory, filename + extension)
xmlFullPath = os.path.join(self.annotationsDirectory, filename + ".xml")
# Create an object of ImageAnnotation.
annt = ImageAnnotation(path = xmlFullPath)
# Check if it is empty.
if (len(annt.propertyBoundingBoxes) == 0):
emptyAnnotations.append(file)
print("WARNING: Annotation {} does not have any annotations.".format(xmlFullPath))
# Check if we need to remove this annotation.
if (removeEmpty == True):
#os.remove(imgFullPath)
os.remove(xmlFullPath)
# Check if it is irregular
height, width, depth = annt.propertySize
for each in annt.propertyBoundingBoxes:
ix, iy, x, y = each
if (ix < 0):
raise ValueError("ERROR: Negative coordinate found in {}".format(file))
if (iy < 0):
raise ValueError("ERROR: Negative coordinate found in {}".format(file))
if (x > width):
raise ValueError("ERROR: Coordinate {} bigger than width {} found in {}"\
.format(x, width, file))
if (y > height):
raise ValueError("ERROR: Coordinate {} bigger than height {} found in {}"\
.format(y, height, file))
# Return empty annotations
return emptyAnnotations
# Stats.
def computeBoundingBoxStats(self, saveDataFrame = None, outputDirDataFrame = None):
"""
Compute basic stats for the dataset's bounding boxes.
Args:
saveDataFrame: A boolean that defines whether to save the dataframe or not.
outputDirDataFrame: A string that contains the path where the dataframe will
be saved.
Returns:
None
"""
# Assertions
if (saveDataFrame == None):
saveDataFrame = False
else:
if (type(saveDataFrame) == bool):
if (outputDirDataFrame == None):
raise ValueError("Parameter directory dataframe cannot be empty.")
else:
raise TypeError("saveDataFrame must be of type bool.")
# Local variables
namesFrequency = {}
files = os.listdir(self.imagesDirectory)
columns = ["path", "name", "width", "height", "xmin", "ymin", "xmax", "ymax"]
paths = []
names = []
widths = []
heights = []
boundingBoxesLists = []
# Logic
for file in tqdm(files):
extension = Util.detect_file_extension(filename = file)
if (extension == None):
raise Exception("ERROR: Your image extension is not valid: {}".format(extension) +\
" Only jpgs and pngs are allowed.")
# Extract name.
filename = os.path.split(file)[1].split(extension)[0]
# Create xml and img name.
imgFullPath = os.path.join(self.imagesDirectory, filename + extension)
xmlFullPath = os.path.join(self.annotationsDirectory, filename + ".xml")
# Create an object of ImageAnnotation.
annt = ImageAnnotation(path = xmlFullPath)
# Check if it is empty.
boundingBoxes = annt.propertyBoundingBoxes
names = annt.propertyNames
height, width, depth = annt.propertySize
for i in range(len(names)):
if (not (names[i] in namesFrequency)):
namesFrequency[names[i]] = 0
else:
namesFrequency[names[i]] += 1
paths.append(file)
names.append(names[i])
widths.append(width)
heights.append(height)
boundingBoxesLists.append(boundingBoxes[i])
# Print stats.
print("Total number of bounding boxes: {}"\
.format(sum([i for i in namesFrequency.values()])))
print("Unique classes: {}".format(namesFrequency))
# Save data?
if (saveDataFrame):
Util.save_lists_in_dataframe(columns = columns,
data = [paths, names, widths, heights, boundingBoxesLists],
output_directory = outputDirDataFrame)
# Save bounding boxes as files.
def saveBoundingBoxes(self, outputDirectory = None, filterClasses = None):
"""
Saves the bounding boxes as images of each image in the dataset.
Args:
outputDirectory: A string that contains the directory where the images will be saved.
filterClasses: A list of Strings that contains names of the classes to be filtered and saved.
Returns:
None
"""
# Assertions
if (outputDirectory == None):
raise ValueError("outputDirectory cannot be empty")
if (type(outputDirectory) != str):
raise TyperError("outputDirectory must be a string.")
if (not (os.path.isdir(outputDirectory))):
raise FileNotFoundError("outputDirectory's path does not exist: ".format(outputDirectory))
if (filterClasses == None):
filterClasses = []
if (type(filterClasses) != list):
raise TyperError("filterClasses must be of type list.")
# Local variables
images = [os.path.join(self.imagesDirectory, i) for i in os.listdir(self.imagesDirectory)]
# Logic
for img in tqdm(images):
# Get extension
extension = Util.detect_file_extension(filename = img)
if (extension == None):
raise Exception("ERROR: Your image extension is not valid." +\
"Only jpgs and pngs are allowed.")
# Extract name
filename = os.path.split(img)[1].split(extension)[0]
# Create xml and img name
imgFullPath = os.path.join(self.imagesDirectory, filename + extension)
xmlFullPath = os.path.join(self.annotationsDirectory, filename + ".xml")
# Load annotation.
annt = ImageAnnotation(path = xmlFullPath)
# Get bounding boxes.
boundingBoxes = annt.propertyBoundingBoxes
names = annt.propertyNames
# Save image.
frame = cv2.imread(img)
# Save bounding boxes as png images.
for name, boundingBox in zip(names, boundingBoxes):
if ((len(filterClasses) == 0) or (name in filterClasses)):
ix, iy, x, y = boundingBox
# Detect extension.
extension = Util.detect_file_extension(filename = img)
if (extension == None):
raise Exception("Your image extension is not valid. " +\
"Only jpgs and pngs are allowed. {}".format(extension))
# Generate a new name.
newName = Util.create_random_name(name = self.databaseName, length = 4)
imgName = newName + extension
# Check bounding box does not get out of boundaries.
if (x == frame.shape[1]):
x -= 1
if (y == frame.shape[0]):
y -= 1
# Check bounding boxes are ok.
if (((y-iy) == 0) or ((x - ix) == 0) or \
((ix < 0) or (iy < 0)) or \
((x > frame.shape[1]) or (y > frame.shape[0]))):
print(img)
print(ix, iy, x, y)
raise Exception("Bounding box does not exist.")
# Save image.
Util.save_img(frame = frame[iy:y, ix:x, :],
img_name = imgName,
output_image_directory = outputDirectory)
# Reduce and data augmentation.
def reduceDatasetByRois(self, offset = None, outputImageDirectory = None, outputAnnotationDirectory = None):
"""
Reduce that images of a dataset by grouping its bounding box annotations and
creating smaller images that contain them.
Args:
offset: An int that contains the amount of pixels in which annotations
can be grouped.
outputImageDirectory: A string that contains the path to the directory
where the images will be stored.
outputAnnotationDirectory: A string that contains the path to the directory
where the annotations will be stored.
Returns:
None
"""
# Assertions
if (offset == None):
raise ValueError("Offset parameter cannot be empty.")
if (outputImageDirectory == None):
outputImageDirectory = os.getcwd()
Util.create_folder(os.path.join(outputImageDirectory, "images"))
outputImageDirectory = os.path.join(os.getcwd(), "images")
if (not (os.path.isdir(outputImageDirectory))):
raise Exception("Path to output directory does not exist. {}"\
.format(outputImageDirectory))
if (outputAnnotationDirectory == None):
outputAnnotationDirectory = os.getcwd()
Util.create_folder(os.path.join(outputAnnotationDirectory, "annotations"))
Util.create_folder(os.path.join(outputAnnotationDirectory, "annotations", "xmls"))
outputAnnotationDirectory = os.path.join(os.getcwd(), "annotations", "xmls")
if (not (os.path.isdir(outputAnnotationDirectory))):
raise Exception("Path to output annotation directory does not exist. {}"\
.format(outputAnnotationDirectory))
# Get images and annotations full paths
imagesPath = [os.path.join(self.imagesDirectory, each) for each in \
os.listdir(self.imagesDirectory)]
for img in tqdm(imagesPath):
#print(img)
# Get extension
extension = Util.detect_file_extension(filename = img)
if (extension == None):
raise Exception("Your image extension is not valid." +\
"Only jpgs and pngs are allowed.")
# Extract name
filename = os.path.split(img)[1].split(extension)[0]
# Create xml and img name
imgFullPath = os.path.join(self.imagesDirectory, filename + extension)
xmlFullPath = os.path.join(self.annotationsDirectory, filename + ".xml")
self.reduceImageDataPointByRoi(imagePath = imgFullPath,
annotationPath = xmlFullPath,
offset = offset,
outputImageDirectory = outputImageDirectory,
outputAnnotationDirectory = outputAnnotationDirectory)
def reduceImageDataPointByRoi(self, imagePath = None, annotationPath = None, offset = None, outputImageDirectory = None, outputAnnotationDirectory = None):
"""
Group an image's bounding boxes into Rois and create smaller images.
Args:
imagePath: A string that contains the path to an image.
annotationPath: A string that contains the path to an annotation.
offset: An int that contains the offset.
outputImageDirectory: A string that contains the path where the images
will be stored.
outputAnnotationDirectory: A string that contains the path where the annotations
will be stored.
Returns:
None
Example:
Given an image and its bounding boxes, create ROIs of size offset
that enclose the maximum possible amount of bounding boxes.
--------------------------------- --------------------------------
| | | |
| --- | | Roi0------ |
| | | | | | | | |
| --- | | |--- | |
| | | | --- | |
| --- | -> | | | | | |
| | | | | | --- | |
| --- | | ------Roi0 |
| | | |
| | | |
| | | |
| --- | | Roi1---- |
| | | | | | | |
| --- | | | | |
| | | | --- | |
| | | | | | | |
| | | | --- | |
| | | ----Roi1 |
--------------------------------- ---------------------------------
Then, the rois are saved with their respective annotations.
"""
# Assertions
if (imagePath == None):
raise ValueError("ERROR: Path to imagePath parameter cannot be empty.")
if (annotationPath == None):
raise ValueError("ERROR: Path to annotation parameter cannot be empty.")
if (not os.path.isfile(imagePath)):
raise ValueError("ERROR: Path to image does not exist {}.".format(imagePath))
if (not os.path.isfile(annotationPath)):
raise ValueError("ERROR: Path to annotation does not exist {}.".format(annotationPath))
if (offset == None):
raise ValueError("ERROR: Offset parameter cannot be empty.")
if (not (os.path.isdir(outputImageDirectory))):
raise ValueError("ERROR: Output image directory does not exist.")
if (not (os.path.isdir(outputAnnotationDirectory))):
raise ValueError("ERROR: Output annotation directory does not exist.")
# Load image annotation.
annotation = ImageAnnotation(path = annotationPath)
height, width, depth = annotation.propertySize
names = annotation.propertyNames
objects = annotation.propertyObjects
boundingBoxes = annotation.propertyBoundingBoxes
# Create a list of classes with the annotations.
annotations = []
index = 0
for boundingBox, name in zip(boundingBoxes, names):
# Compute the module
ix, iy, x, y = boundingBox
module = VectorOperations.compute_module(vector = [ix, iy])
annotations.append(Annotation(name = name, bndbox = boundingBox, \
module = module, corePoint = True))
index += 1
# Sort the list of Annotations by its module from lowest to highest.
for i in range(len(annotations)):
for j in range(len(annotations)-1):
module0 = annotations[j].propertyModule
module1 = annotations[j+1].propertyModule
if (module0 >= module1):
# Swap Annotation
aux = annotations[j+1]
annotations[j+1] = annotations[j]
annotations[j] = aux
# Debug
# for each in annotations:
# print(each.propertyName, each.propertyModule)
# print("\n")
# Work on the points.
for i in range(len(annotations)):
# Ignore non-core points.
if (annotations[i].propertyCorePoint == False):
pass
else:
# Center the core point in an allowed image space.
RoiXMin, RoiYMin, \
RoiXMax, RoiYMax = prep.adjustImage(frameHeight = height,
frameWidth = width,
boundingBoxes = [annotations[i].propertyBndbox],
offset = offset)
# Find the annotations that can be included in the allowed image space.
for j in range(len(annotations)):
# Get bounding box.
ix, iy, x, y = annotations[j].propertyBndbox
# Check current bounding box is inside the allowed space.
if ((ix >= RoiXMin) and (x <= RoiXMax)) and \
((iy >= RoiYMin) and (y <= RoiYMax)):
# Disable point from being a core point. Check it is not the
# current point of reference.
if (not (annotations[i].propertyBndbox == annotations[j].propertyBndbox)):
annotations[j].propertyCorePoint = False
# Include the corresponding bounding boxes in the region of interest.
newBoundingBoxes, \
newNames = prep.includeBoundingBoxes(edges = [RoiXMin, RoiYMin, RoiXMax, RoiYMax],
boundingBoxes = boundingBoxes,
names = names)
if (len(newBoundingBoxes) == 0):
print(boundingBoxes)
print(RoiXMin, RoiYMin, RoiXMax, RoiYMax)
raise Exception("ERROR: No bounding boxes: {}. Please report this problem.".format(imagePath))
# Read image.
frame = cv2.imread(imagePath)
extension = Util.detect_file_extension(filename = imagePath)
if (extension == None):
raise Exception("Your image extension is not valid. " +\
"Only jpgs and pngs are allowed. {}".format(extension))
# Generate a new name.
newName = Util.create_random_name(name = self.databaseName, length = 4)
imgName = newName + extension
xmlName = newName + ".xml"
# Save image.
Util.save_img(frame = frame[RoiYMin:RoiYMax, RoiXMin:RoiXMax, :],
img_name = imgName,
output_image_directory = outputImageDirectory)
# Save annotation.
Util.save_annotation(filename = imgName,
path = os.path.join(outputImageDirectory, imgName),
database_name = self.databaseName,
frame_size = frame[RoiYMin:RoiYMax, RoiXMin:RoiXMax, :].shape,
data_augmentation_type = "Unspecified",
bounding_boxes = newBoundingBoxes,
names = newNames,
origin = imagePath,
output_directory = os.path.join(outputAnnotationDirectory, xmlName))
def applyDataAugmentation(self, configurationFile = None, outputImageDirectory = None, outputAnnotationDirectory = None, threshold = None):
"""
Applies one or multiple data augmentation methods to the dataset.
Args:
configurationFile: A string with a path to a json file that contains the
configuration of the data augmentation methods.
outputImageDirectory: A string that contains the path to the directory where
images will be saved.
outputAnnotationDirectory: A string that contains the path the directory where
annotations will be saved.
threshold: A float that contains a number between 0 and 1.
Returns:
None
"""
# Assertions
if (configurationFile == None):
raise ValueError("ERROR: Augmenter parameter cannot be empty.")
else:
if (not os.path.isfile(configurationFile)):
raise Exception("ERROR: Path to json file ({}) does not exist."\
.format(configurationFile))
jsonConf = AugmentationConfigurationFile(file = configurationFile)
typeAugmentation = jsonConf.runAllAssertions()
if (outputImageDirectory == None):
outputImageDirectory = os.getcwd()
Util.create_folder(os.path.join(outputImageDirectory, "images"))
outputImageDirectory = os.path.join(os.getcwd(), "images")
if (not (os.path.isdir(outputImageDirectory))):
raise Exception("ERROR: Path to output directory does not exist. {}"\
.format(outputImageDirectory))
if (outputAnnotationDirectory == None):
outputAnnotationDirectory = os.getcwd()
Util.create_folder(os.path.join(outputAnnotationDirectory, "annotations"))
Util.create_folder(os.path.join(outputAnnotationDirectory, "annotations", "xmls"))
outputAnnotationDirectory = os.path.join(os.getcwd(), "annotations", "xmls")
if (not (os.path.isdir(outputAnnotationDirectory))):
raise Exception("ERROR: Path to output annotation directory does not exist. {}"\
.format(outputAnnotationDirectory))
if (threshold == None):
threshold = 0.5
if (type(threshold) != float):
raise TyperError("ERROR: threshold parameter must be of type float.")
if ((threshold > 1) or (threshold < 0)):
raise ValueError("ERROR: threshold paramater should be a number between" +\
" 0-1.")
# Load configuration data.
f = open(configurationFile)
data = json.load(f)
f.close()
# Iterate over the images.
for img in tqdm(os.listdir(self.imagesDirectory)):
# Get the extension
extension = Util.detect_file_extension(filename = img)
if (extension == None):
raise Exception("ERROR: Your image extension is not valid." +\
"Only jpgs and pngs are allowed.")
# Extract name.
filename = os.path.split(img)[1].split(extension)[0]
# Create xml and img name.
imgFullPath = os.path.join(self.imagesDirectory, filename + extension)
xmlFullPath = os.path.join(self.annotationsDirectory, filename + ".xml")
imgAnt = ImageAnnotation(path = xmlFullPath)
boundingBoxes = imgAnt.propertyBoundingBoxes
names = imgAnt.propertyNames
# Apply augmentation.
if (typeAugmentation == 0):
for i in data["bounding_box_augmenters"]:
if (i == "Sequential"):
# Prepare data for sequence
frame = cv2.imread(imgFullPath)
bndboxes = boundingBoxes
# Read elements of vector
assert type(data["bounding_box_augmenters"][i]) == list, "Not list"
for k in range(len(data["bounding_box_augmenters"][i])):
# Extract information
augmentationType = list(data["bounding_box_augmenters"][i][k].keys())[0]
if (not jsonConf.isValidBoundingBoxAugmentation(augmentation = augmentationType)):
raise Exception("ERROR: {} is not valid.".format(augmentationType))
parameters = data["bounding_box_augmenters"][i][k][augmentationType]
# Save?
saveParameter = jsonConf.extractSavingParameter(parameters = parameters)
frame, bndboxes = applyBoundingBoxAugmentation(frame = frame,
boundingBoxes = bndboxes,
augmentationType = augmentationType, #j,
parameters = parameters)
if (saveParameter == True):
# Generate a new name.
newName = Util.create_random_name(name = self.databaseName, length = 4)
imgName = newName + extension
xmlName = newName + ".xml"
# Save image.
Util.save_img(frame = frame,
img_name = imgName,
output_image_directory = outputImageDirectory)
# Save annotation.
Util.save_annotation(filename = imgName,
path = os.path.join(outputImageDirectory, imgName),
database_name = self.databaseName,
frame_size = frame.shape,
data_augmentation_type = augmentationType,
bounding_boxes = bndboxes,
names = names,
origin = imgFullPath,
output_directory = os.path.join(outputAnnotationDirectory, xmlName))
else:
parameters = data["bounding_box_augmenters"][i]
# Save?
saveParameter = jsonConf.extractSavingParameter(parameters = parameters)
frame, bndboxes = applyBoundingBoxAugmentation(frame = cv2.imread(imgFullPath),
boundingBoxes = boundingBoxes,
augmentationType = i,
parameters = parameters)
# Save frame
if (saveParameter == True):
# Generate a new name.
newName = Util.create_random_name(name = self.databaseName, length = 4)
imgName = newName + extension
xmlName = newName + ".xml"
# Save image.
Util.save_img(frame = frame,
img_name = imgName,
output_image_directory = outputImageDirectory)
# Save annotation.
Util.save_annotation(filename = imgName,
path = os.path.join(outputImageDirectory, imgName),
database_name = self.databaseName,
frame_size = frame.shape,
data_augmentation_type = augmentationType,
bounding_boxes = bndboxes,
names = names,
origin = imgFullPath,
output_directory = os.path.join(outputAnnotationDirectory, xmlName))
elif (typeAugmentation == 1):
# Geometric data augmentations
raise ValueError("Image geometric data augmentations are not " +\
"supported for bounding boxes. Use bounding box " +\
"augmentation types.")
elif (typeAugmentation == 2):
# Color data augmentations
for i in data["image_color_augmenters"]:
if (i == "Sequential"):
# Prepare data for sequence
frame = cv2.imread(imgFullPath)
# Read elements of vector
assert type(data["image_color_augmenters"][i]) == list, "Not list"
for k in range(len(data["image_color_augmenters"][i])):
# Extract information
augmentationType = list(data["image_color_augmenters"][i][k].keys())[0]
if (not jsonConf.isValidColorAugmentation(augmentation = augmentationType)):
raise Exception("ERROR: {} is not valid.".format(augmentationType))
parameters = data["image_color_augmenters"][i][k][augmentationType]
# Save?
saveParameter = jsonConf.extractSavingParameter(parameters = parameters)
# Apply augmentation
frame = applyColorAugmentation(frame = frame,
augmentationType = augmentationType, #j,
parameters = parameters)
if (saveParameter == True):
# Generate a new name.
newName = Util.create_random_name(name = self.databaseName, length = 4)
imgName = newName + extension
xmlName = newName + ".xml"
# Save image.
Util.save_img(frame = frame,
img_name = imgName,
output_image_directory = outputImageDirectory)
# Save annotation.
Util.save_annotation(filename = imgName,
path = os.path.join(outputImageDirectory, imgName),
database_name = self.databaseName,
frame_size = frame.shape,
data_augmentation_type = augmentationType,
bounding_boxes = bndboxes,
names = names,
origin = imgFullPath,
output_directory = os.path.join(outputAnnotationDirectory, xmlName))
else:
parameters = data["image_color_augmenters"][i]
# Save?
saveParameter = jsonConf.extractSavingParameter(parameters = parameters)
frame = applyColorAugmentation(frame = cv2.imread(imgFullPath),
augmentationType = i,
parameters = parameters)
# Save frame
if (saveParameter == True):
# Generate a new name.
newName = Util.create_random_name(name = self.databaseName, length = 4)
imgName = newName + extension
xmlName = newName + ".xml"
# Save image.
Util.save_img(frame = frame,
img_name = imgName,
output_image_directory = outputImageDirectory)
# Save annotation.
Util.save_annotation(filename = imgName,
path = os.path.join(outputImageDirectory, imgName),
database_name = self.databaseName,
frame_size = frame.shape,
data_augmentation_type = augmentationType,
bounding_boxes = bndboxes,
names = names,
origin = imgFullPath,
output_directory = os.path.join(outputAnnotationDirectory, xmlName))
elif (typeAugmentation == 3):
# Assert sequential follows multiple_image_augmentations.
if (not ("Sequential" in data["multiple_image_augmentations"])):
raise Exception("ERROR: Data after multiple_image_augmentations is not recognized.")
# Multiple augmentation configurations, get a list of hash maps of all the confs.
list_of_augmenters_confs = data["multiple_image_augmentations"]["Sequential"]
# Assert list_of_augmenters_confs is a list.
if (not (type(list_of_augmenters_confs) == list)):
raise TypeError("ERROR: Data inside [multiple_image_augmentations][Sequential] must be a list.")
# Prepare data for sequence.
frame = cv2.imread(imgFullPath)
bndboxes = boundingBoxes
# print("\n*", list_of_augmenters_confs, "\n")
for k in range(len(list_of_augmenters_confs)):
# Get augmenter type ("bounding_box_augmenter" or "color_augmenter") position
# in the list of multiple augmentations.
augmentationConf = list(list_of_augmenters_confs[k].keys())[0]
if (not (jsonConf.isBndBxAugConfFile(keys = [augmentationConf]) or
jsonConf.isColorConfFile(keys = [augmentationConf]))):
raise Exception("{} is not a valid configuration.".format(augmentationConf))
# Get sequential information from there. This information is a list of
# the types of augmenters that belong to augmentationConf.
list_of_augmenters_confs_types = list_of_augmenters_confs[k][augmentationConf]["Sequential"]
# Assert list_of_augmenters_confs is a list
if (not (type(list_of_augmenters_confs_types) == list)):
raise TypeError("Data inside [multiple_image_augmentations][Sequential][{}][Sequential] must be a list."\
.format(augmentationConf))
# Iterate over augmenters inside sequential of type.
for l in range(len(list_of_augmenters_confs_types)):
# Get augmentation type and its parameters.
augmentationType = list(list_of_augmenters_confs_types[l].keys())[0]
# Assert augmentation is valid.
if (not (jsonConf.isValidBoundingBoxAugmentation(augmentation = augmentationType) or
jsonConf.isValidColorAugmentation(augmentation = augmentationType))):
raise Exception("ERROR: {} is not valid.".format(augmentationType))
parameters = list_of_augmenters_confs_types[l][augmentationType]
# Save?
saveParameter = jsonConf.extractSavingParameter(parameters = parameters)
# Restart frame to original?
restartFrameParameter = jsonConf.extractRestartFrameParameter(parameters = parameters)
# Probability of augmentation happening.
randomEvent = jsonConf.randomEvent(parameters = parameters, threshold = threshold)
# print(augmentationType, parameters)
# Apply augmentation.
if (augmentationConf == "image_color_augmenters"):
# print(augmentationConf, augmentationType, parameters)
if (randomEvent == True):
frame = applyColorAugmentation(frame = frame,
augmentationType = augmentationType,
parameters = parameters)
elif (augmentationConf == "bounding_box_augmenters"):
# print(augmentationConf, augmentationType, parameters)
if (randomEvent == True):
frame, bndboxes = applyBoundingBoxAugmentation(frame = frame,
boundingBoxes = bndboxes,
augmentationType = augmentationType, #j,
parameters = parameters)
# Save?
if ((saveParameter == True) and (randomEvent == True)):
# Generate a new name.
newName = Util.create_random_name(name = self.databaseName, length = 4)
imgName = newName + extension
xmlName = newName + ".xml"
# Save image.
Util.save_img(frame = frame,
img_name = imgName,
output_image_directory = outputImageDirectory)
# Save annotation.
Util.save_annotation(filename = imgName,
path = os.path.join(outputImageDirectory, imgName),
database_name = self.databaseName,
frame_size = frame.shape,
data_augmentation_type = augmentationType,
bounding_boxes = bndboxes,
names = names,
origin = imgFullPath,
output_directory = os.path.join(outputAnnotationDirectory, xmlName))
# Restart frame?
if (restartFrameParameter == True):
frame = cv2.imread(imgFullPath)
bndboxes = boundingBoxes
else:
raise Exception("Type augmentation {} not valid.".format(typeAugmentation))
class Annotation(object):
def __init__(self, name = None, bndbox = None, module = None, corePoint = None):
"""
A class that holds parameters of a common annotation.
Args:
name: A string that contains a name.
bndbox: A list of ints.
module: A float.
corePoint: A boolean.
Returns:
None
"""
super(Annotation, self).__init__()
# Assertions
if (name == None):
raise ValueError("Name parameter cannot be empty.")
if (bndbox == None):
raise ValueError("Bounding box parameter cannot be empty.")
if (module == None):
module = -1
if (corePoint == None):
raise ValueError("corePoint parameter cannot be empty.")
# Class variables
self.name = name
self.bndbox = bndbox
self.module = module
self.corePoint = corePoint
self.otherAnnotations = []
self.otherAnnotationsName = []
@property
def propertyModule(self):
return self.module
@property
def propertyName(self):
return self.name
@property
def propertyBndbox(self):
return self.bndbox
@property
def propertyModule(self):
return self.module
@propertyModule.setter
def propertyModule(self, module):
self.module = module
@property
def propertyCorePoint(self):
return self.corePoint
@propertyCorePoint.setter
def propertyCorePoint(self, corePoint):
self.corePoint = corePoint
@property
def propertyOtherAnnotation(self):
return self.otherAnnotations
def includeOtherAnnotation(self, annt):
self.otherAnnotations.append(annt)
@property
def propertyOtherAnnotationName(self):
return self.otherAnnotationsName
def includeOtherAnnotationName(self, name):
self.otherAnnotationsName.append(name)