admin commited on
Commit
9eaa53c
·
1 Parent(s): 8f4b133

PascalVocSource = PascalVocSource()

Browse files
Files changed (1) hide show
  1. khandy/label/detect.py +162 -142
khandy/label/detect.py CHANGED
@@ -13,15 +13,21 @@ import lxml.builder
13
  import numpy as np
14
 
15
 
16
- __all__ = ['DetectIrObject', 'DetectIrRecord', 'load_detect',
17
- 'save_detect', 'convert_detect', 'replace_detect_label',
18
- 'load_coco_class_names']
 
 
 
 
 
 
19
 
20
 
21
  @dataclass
22
  class DetectIrObject:
23
- """Intermediate Representation Format of Object
24
- """
25
  label: str
26
  x_min: float
27
  y_min: float
@@ -31,8 +37,8 @@ class DetectIrObject:
31
 
32
  @dataclass
33
  class DetectIrRecord:
34
- """Intermediate Representation Format of Record
35
- """
36
  filename: str
37
  width: int
38
  height: int
@@ -41,9 +47,9 @@ class DetectIrRecord:
41
 
42
  @dataclass
43
  class PascalVocSource:
44
- database: str = ''
45
- annotation: str = ''
46
- image: str = ''
47
 
48
 
49
  @dataclass
@@ -64,7 +70,7 @@ class PascalVocBndbox:
64
  @dataclass
65
  class PascalVocObject:
66
  name: str
67
- pose: str = 'Unspecified'
68
  truncated: int = 0
69
  difficult: int = 0
70
  bndbox: Optional[PascalVocBndbox] = None
@@ -72,10 +78,10 @@ class PascalVocObject:
72
 
73
  @dataclass
74
  class PascalVocRecord:
75
- folder: str = ''
76
- filename: str = ''
77
- path: str = ''
78
- source: PascalVocSource = PascalVocSource()
79
  size: Optional[PascalVocSize] = None
80
  segmented: int = 0
81
  objects: List[PascalVocObject] = field(default_factory=list)
@@ -87,40 +93,40 @@ class PascalVocHandler:
87
  pascal_voc_record = PascalVocRecord()
88
 
89
  xml_tree = ET.parse(filename)
90
- pascal_voc_record.folder = xml_tree.find('folder').text
91
- pascal_voc_record.filename = xml_tree.find('filename').text
92
- pascal_voc_record.path = xml_tree.find('path').text
93
- pascal_voc_record.segmented = xml_tree.find('segmented').text
94
 
95
- source_tag = xml_tree.find('source')
96
  pascal_voc_record.source = PascalVocSource(
97
- database=source_tag.find('database').text,
98
  # annotation=source_tag.find('annotation').text,
99
  # image=source_tag.find('image').text
100
  )
101
 
102
- size_tag = xml_tree.find('size')
103
  pascal_voc_record.size = PascalVocSize(
104
- width=int(size_tag.find('width').text),
105
- height=int(size_tag.find('height').text),
106
- depth=int(size_tag.find('depth').text)
107
  )
108
 
109
- object_tags = xml_tree.findall('object')
110
  for index, object_tag in enumerate(object_tags):
111
- bndbox_tag = object_tag.find('bndbox')
112
  bndbox = PascalVocBndbox(
113
- xmin=float(bndbox_tag.find('xmin').text) - 1,
114
- ymin=float(bndbox_tag.find('ymin').text) - 1,
115
- xmax=float(bndbox_tag.find('xmax').text) - 1,
116
- ymax=float(bndbox_tag.find('ymax').text) - 1
117
  )
118
  pascal_voc_object = PascalVocObject(
119
- name=object_tag.find('name').text,
120
- pose=object_tag.find('pose').text,
121
- truncated=object_tag.find('truncated').text,
122
- difficult=object_tag.find('difficult').text,
123
- bndbox=bndbox
124
  )
125
  pascal_voc_record.objects.append(pascal_voc_object)
126
  return pascal_voc_record
@@ -158,18 +164,17 @@ class PascalVocHandler:
158
  )
159
  xml.append(object_tag)
160
 
161
- if not filename.endswith('.xml'):
162
- filename = filename + '.xml'
163
- with open(filename, 'wb') as f:
164
- f.write(lxml.etree.tostring(
165
- xml, pretty_print=True, encoding='utf-8'))
166
 
167
  @staticmethod
168
  def to_ir(pascal_voc_record: PascalVocRecord) -> DetectIrRecord:
169
  ir_record = DetectIrRecord(
170
  filename=pascal_voc_record.filename,
171
  width=pascal_voc_record.size.width,
172
- height=pascal_voc_record.size.height
173
  )
174
  for pascal_voc_object in pascal_voc_record.objects:
175
  ir_object = DetectIrObject(
@@ -177,7 +182,7 @@ class PascalVocHandler:
177
  x_min=pascal_voc_object.bndbox.xmin,
178
  y_min=pascal_voc_object.bndbox.ymin,
179
  x_max=pascal_voc_object.bndbox.xmax,
180
- y_max=pascal_voc_object.bndbox.ymax
181
  )
182
  ir_record.objects.append(ir_object)
183
  return ir_record
@@ -186,11 +191,7 @@ class PascalVocHandler:
186
  def from_ir(ir_record: DetectIrRecord) -> PascalVocRecord:
187
  pascal_voc_record = PascalVocRecord(
188
  filename=ir_record.filename,
189
- size=PascalVocSize(
190
- width=ir_record.width,
191
- height=ir_record.height,
192
- depth=3
193
- )
194
  )
195
  for ir_object in ir_record.objects:
196
  pascal_voc_object = PascalVocObject(
@@ -200,24 +201,36 @@ class PascalVocHandler:
200
  ymin=ir_object.y_min,
201
  xmax=ir_object.x_max,
202
  ymax=ir_object.y_max,
203
- )
204
  )
205
  pascal_voc_record.objects.append(pascal_voc_object)
206
  return pascal_voc_record
207
 
208
 
209
  class _NumpyEncoder(json.JSONEncoder):
210
- """ Special json encoder for numpy types """
211
 
212
  def default(self, obj):
213
  if isinstance(obj, (np.bool_,)):
214
  return bool(obj)
215
- elif isinstance(obj, (np.int_, np.intc, np.intp, np.int8,
216
- np.int16, np.int32, np.int64, np.uint8,
217
- np.uint16, np.uint32, np.uint64)):
 
 
 
 
 
 
 
 
 
 
 
 
 
218
  return int(obj)
219
- elif isinstance(obj, (np.float_, np.float16, np.float32,
220
- np.float64)):
221
  return float(obj)
222
  elif isinstance(obj, (np.ndarray,)):
223
  return obj.tolist()
@@ -238,7 +251,7 @@ class LabelmeShape:
238
 
239
  @dataclass
240
  class LabelmeRecord:
241
- version: str = '4.5.6'
242
  flags: dict = field(default_factory=dict)
243
  shapes: List[LabelmeShape] = field(default_factory=list)
244
  imagePath: Optional[str] = None
@@ -267,10 +280,10 @@ class LabelmeHandler:
267
  ir_record = DetectIrRecord(
268
  filename=labelme_record.imagePath,
269
  width=labelme_record.imageWidth,
270
- height=labelme_record.imageHeight
271
  )
272
  for labelme_shape in labelme_record.shapes:
273
- if labelme_shape.shape_type != 'rectangle':
274
  continue
275
  ir_object = DetectIrObject(
276
  label=labelme_shape.label,
@@ -287,14 +300,16 @@ class LabelmeHandler:
287
  labelme_record = LabelmeRecord(
288
  imagePath=ir_record.filename,
289
  imageWidth=ir_record.width,
290
- imageHeight=ir_record.height
291
  )
292
  for ir_object in ir_record.objects:
293
  labelme_shape = LabelmeShape(
294
  label=ir_object.label,
295
- shape_type='rectangle',
296
- points=[[ir_object.x_min, ir_object.y_min],
297
- [ir_object.x_max, ir_object.y_max]]
 
 
298
  )
299
  labelme_record.shapes.append(labelme_shape)
300
  return labelme_record
@@ -320,23 +335,26 @@ class YoloRecord:
320
  class YoloHandler:
321
  @staticmethod
322
  def load(filename, **kwargs) -> YoloRecord:
323
- assert 'image_filename' in kwargs
324
- assert 'width' in kwargs and 'height' in kwargs
325
 
326
  records = khandy.load_list(filename)
327
  yolo_record = YoloRecord(
328
- filename=kwargs.get('image_filename'),
329
- width=kwargs.get('width'),
330
- height=kwargs.get('height'))
 
331
  for record in records:
332
  record_parts = record.split()
333
- yolo_record.objects.append(YoloObject(
334
- label=record_parts[0],
335
- x_center=float(record_parts[1]),
336
- y_center=float(record_parts[2]),
337
- width=float(record_parts[3]),
338
- height=float(record_parts[4]),
339
- ))
 
 
340
  return yolo_record
341
 
342
  @staticmethod
@@ -344,9 +362,10 @@ class YoloHandler:
344
  records = []
345
  for object in yolo_record.objects:
346
  records.append(
347
- f'{object.label} {object.x_center} {object.y_center} {object.width} {object.height}')
348
- if not filename.endswith('.txt'):
349
- filename = filename + '.txt'
 
350
  khandy.save_list(filename, records)
351
 
352
  @staticmethod
@@ -354,23 +373,23 @@ class YoloHandler:
354
  ir_record = DetectIrRecord(
355
  filename=yolo_record.filename,
356
  width=yolo_record.width,
357
- height=yolo_record.height
358
  )
359
  for yolo_object in yolo_record.objects:
360
- x_min = (yolo_object.x_center - 0.5 *
361
- yolo_object.width) * yolo_record.width
362
- y_min = (yolo_object.y_center - 0.5 *
363
- yolo_object.height) * yolo_record.height
364
- x_max = (yolo_object.x_center + 0.5 *
365
- yolo_object.width) * yolo_record.width
366
- y_max = (yolo_object.y_center + 0.5 *
367
- yolo_object.height) * yolo_record.height
368
  ir_object = DetectIrObject(
369
  label=yolo_object.label,
370
  x_min=x_min,
371
  y_min=y_min,
372
  x_max=x_max,
373
- y_max=y_max
374
  )
375
  ir_record.objects.append(ir_object)
376
  return ir_record
@@ -378,15 +397,11 @@ class YoloHandler:
378
  @staticmethod
379
  def from_ir(ir_record: DetectIrRecord) -> YoloRecord:
380
  yolo_record = YoloRecord(
381
- filename=ir_record.filename,
382
- width=ir_record.width,
383
- height=ir_record.height
384
  )
385
  for ir_object in ir_record.objects:
386
- x_center = (ir_object.x_max + ir_object.x_min) / \
387
- (2 * ir_record.width)
388
- y_center = (ir_object.y_max + ir_object.y_min) / \
389
- (2 * ir_record.height)
390
  width = abs(ir_object.x_max - ir_object.x_min) / ir_record.width
391
  height = abs(ir_object.y_max - ir_object.y_min) / ir_record.height
392
  yolo_object = YoloObject(
@@ -422,31 +437,32 @@ class CocoHandler:
422
  def load(filename, **kwargs) -> List[CocoRecord]:
423
  json_data = khandy.load_json(filename)
424
 
425
- images = json_data['images']
426
- annotations = json_data['annotations']
427
- categories = json_data['categories']
428
 
429
  label_map = {}
430
  for cat_item in categories:
431
- label_map[cat_item['id']] = cat_item['name']
432
 
433
  coco_records = OrderedDict()
434
  for image_item in images:
435
- coco_records[image_item['id']] = CocoRecord(
436
- filename=image_item['file_name'],
437
- width=image_item['width'],
438
- height=image_item['height'],
439
- objects=[])
 
440
 
441
  for annotation_item in annotations:
442
  coco_object = CocoObject(
443
- label=label_map[annotation_item['category_id']],
444
- x_min=annotation_item['bbox'][0],
445
- y_min=annotation_item['bbox'][1],
446
- width=annotation_item['bbox'][2],
447
- height=annotation_item['bbox'][3])
448
- coco_records[annotation_item['image_id']
449
- ].objects.append(coco_object)
450
  return list(coco_records.values())
451
 
452
  @staticmethod
@@ -462,7 +478,7 @@ class CocoHandler:
462
  x_min=coco_object.x_min,
463
  y_min=coco_object.y_min,
464
  x_max=coco_object.x_min + coco_object.width,
465
- y_max=coco_object.y_min + coco_object.height
466
  )
467
  ir_record.objects.append(ir_object)
468
  return ir_record
@@ -470,9 +486,7 @@ class CocoHandler:
470
  @staticmethod
471
  def from_ir(ir_record: DetectIrRecord) -> CocoRecord:
472
  coco_record = CocoRecord(
473
- filename=ir_record.filename,
474
- width=ir_record.width,
475
- height=ir_record.height
476
  )
477
  for ir_object in ir_record.objects:
478
  coco_object = CocoObject(
@@ -480,26 +494,25 @@ class CocoHandler:
480
  x_min=ir_object.x_min,
481
  y_min=ir_object.y_min,
482
  width=ir_object.x_max - ir_object.x_min,
483
- height=ir_object.y_max - ir_object.y_min
484
  )
485
  coco_record.objects.append(coco_object)
486
  return coco_record
487
 
488
 
489
  def load_detect(filename, fmt, **kwargs) -> DetectIrRecord:
490
- if fmt == 'labelme':
491
  labelme_record = LabelmeHandler.load(filename, **kwargs)
492
  ir_record = LabelmeHandler.to_ir(labelme_record)
493
- elif fmt == 'yolo':
494
  yolo_record = YoloHandler.load(filename, **kwargs)
495
  ir_record = YoloHandler.to_ir(yolo_record)
496
- elif fmt in ('voc', 'pascal', 'pascal_voc'):
497
  pascal_voc_record = PascalVocHandler.load(filename, **kwargs)
498
  ir_record = PascalVocHandler.to_ir(pascal_voc_record)
499
- elif fmt == 'coco':
500
  coco_records = CocoHandler.load(filename, **kwargs)
501
- ir_record = [CocoHandler.to_ir(coco_record)
502
- for coco_record in coco_records]
503
  else:
504
  raise ValueError(f"Unsupported detect label fmt. Got {fmt}")
505
  return ir_record
@@ -507,16 +520,16 @@ def load_detect(filename, fmt, **kwargs) -> DetectIrRecord:
507
 
508
  def save_detect(filename, ir_record: DetectIrRecord, out_fmt):
509
  os.makedirs(os.path.dirname(os.path.abspath(filename)), exist_ok=True)
510
- if out_fmt == 'labelme':
511
  labelme_record = LabelmeHandler.from_ir(ir_record)
512
  LabelmeHandler.save(filename, labelme_record)
513
- elif out_fmt == 'yolo':
514
  yolo_record = YoloHandler.from_ir(ir_record)
515
  YoloHandler.save(filename, yolo_record)
516
- elif out_fmt in ('voc', 'pascal', 'pascal_voc'):
517
  pascal_voc_record = PascalVocHandler.from_ir(ir_record)
518
  PascalVocHandler.save(filename, pascal_voc_record)
519
- elif out_fmt == 'coco':
520
  raise ValueError("Unsupported for `coco` now!")
521
  else:
522
  raise ValueError(f"Unsupported detect label fmt. Got {out_fmt}")
@@ -524,25 +537,32 @@ def save_detect(filename, ir_record: DetectIrRecord, out_fmt):
524
 
525
  def _get_format(record):
526
  if isinstance(record, LabelmeRecord):
527
- return ('labelme',)
528
  elif isinstance(record, YoloRecord):
529
- return ('yolo',)
530
  elif isinstance(record, PascalVocRecord):
531
- return ('voc', 'pascal', 'pascal_voc')
532
  elif isinstance(record, CocoRecord):
533
- return ('coco',)
534
  elif isinstance(record, DetectIrRecord):
535
- return ('ir', 'detect_ir')
536
  else:
537
  return ()
538
 
539
 
540
  def convert_detect(record, out_fmt):
541
- allowed_fmts = ('labelme', 'yolo', 'voc', 'coco',
542
- 'pascal', 'pascal_voc', 'ir', 'detect_ir')
 
 
 
 
 
 
 
 
543
  if out_fmt not in allowed_fmts:
544
- raise ValueError(
545
- "Unsupported label format conversions for given out_fmt")
546
  if out_fmt in _get_format(record):
547
  return record
548
 
@@ -557,17 +577,17 @@ def convert_detect(record, out_fmt):
557
  elif isinstance(record, DetectIrRecord):
558
  ir_record = record
559
  else:
560
- raise TypeError('Unsupported type for record')
561
 
562
- if out_fmt in ('ir', 'detect_ir'):
563
  dst_record = ir_record
564
- elif out_fmt == 'labelme':
565
  dst_record = LabelmeHandler.from_ir(ir_record)
566
- elif out_fmt == 'yolo':
567
  dst_record = YoloHandler.from_ir(ir_record)
568
- elif out_fmt in ('voc', 'pascal', 'pascal_voc'):
569
  dst_record = PascalVocHandler.from_ir(ir_record)
570
- elif out_fmt == 'coco':
571
  dst_record = CocoHandler.from_ir(ir_record)
572
  return dst_record
573
 
@@ -590,5 +610,5 @@ def replace_detect_label(record: DetectIrRecord, label_map, ignore=True):
590
 
591
  def load_coco_class_names(filename):
592
  json_data = khandy.load_json(filename)
593
- categories = json_data['categories']
594
- return [cat_item['name'] for cat_item in categories]
 
13
  import numpy as np
14
 
15
 
16
+ __all__ = [
17
+ "DetectIrObject",
18
+ "DetectIrRecord",
19
+ "load_detect",
20
+ "save_detect",
21
+ "convert_detect",
22
+ "replace_detect_label",
23
+ "load_coco_class_names",
24
+ ]
25
 
26
 
27
  @dataclass
28
  class DetectIrObject:
29
+ """Intermediate Representation Format of Object"""
30
+
31
  label: str
32
  x_min: float
33
  y_min: float
 
37
 
38
  @dataclass
39
  class DetectIrRecord:
40
+ """Intermediate Representation Format of Record"""
41
+
42
  filename: str
43
  width: int
44
  height: int
 
47
 
48
  @dataclass
49
  class PascalVocSource:
50
+ database: str = ""
51
+ annotation: str = ""
52
+ image: str = ""
53
 
54
 
55
  @dataclass
 
70
  @dataclass
71
  class PascalVocObject:
72
  name: str
73
+ pose: str = "Unspecified"
74
  truncated: int = 0
75
  difficult: int = 0
76
  bndbox: Optional[PascalVocBndbox] = None
 
78
 
79
  @dataclass
80
  class PascalVocRecord:
81
+ folder: str = ""
82
+ filename: str = ""
83
+ path: str = ""
84
+ source: PascalVocSource = field(default_factory=PascalVocSource)
85
  size: Optional[PascalVocSize] = None
86
  segmented: int = 0
87
  objects: List[PascalVocObject] = field(default_factory=list)
 
93
  pascal_voc_record = PascalVocRecord()
94
 
95
  xml_tree = ET.parse(filename)
96
+ pascal_voc_record.folder = xml_tree.find("folder").text
97
+ pascal_voc_record.filename = xml_tree.find("filename").text
98
+ pascal_voc_record.path = xml_tree.find("path").text
99
+ pascal_voc_record.segmented = xml_tree.find("segmented").text
100
 
101
+ source_tag = xml_tree.find("source")
102
  pascal_voc_record.source = PascalVocSource(
103
+ database=source_tag.find("database").text,
104
  # annotation=source_tag.find('annotation').text,
105
  # image=source_tag.find('image').text
106
  )
107
 
108
+ size_tag = xml_tree.find("size")
109
  pascal_voc_record.size = PascalVocSize(
110
+ width=int(size_tag.find("width").text),
111
+ height=int(size_tag.find("height").text),
112
+ depth=int(size_tag.find("depth").text),
113
  )
114
 
115
+ object_tags = xml_tree.findall("object")
116
  for index, object_tag in enumerate(object_tags):
117
+ bndbox_tag = object_tag.find("bndbox")
118
  bndbox = PascalVocBndbox(
119
+ xmin=float(bndbox_tag.find("xmin").text) - 1,
120
+ ymin=float(bndbox_tag.find("ymin").text) - 1,
121
+ xmax=float(bndbox_tag.find("xmax").text) - 1,
122
+ ymax=float(bndbox_tag.find("ymax").text) - 1,
123
  )
124
  pascal_voc_object = PascalVocObject(
125
+ name=object_tag.find("name").text,
126
+ pose=object_tag.find("pose").text,
127
+ truncated=object_tag.find("truncated").text,
128
+ difficult=object_tag.find("difficult").text,
129
+ bndbox=bndbox,
130
  )
131
  pascal_voc_record.objects.append(pascal_voc_object)
132
  return pascal_voc_record
 
164
  )
165
  xml.append(object_tag)
166
 
167
+ if not filename.endswith(".xml"):
168
+ filename = filename + ".xml"
169
+ with open(filename, "wb") as f:
170
+ f.write(lxml.etree.tostring(xml, pretty_print=True, encoding="utf-8"))
 
171
 
172
  @staticmethod
173
  def to_ir(pascal_voc_record: PascalVocRecord) -> DetectIrRecord:
174
  ir_record = DetectIrRecord(
175
  filename=pascal_voc_record.filename,
176
  width=pascal_voc_record.size.width,
177
+ height=pascal_voc_record.size.height,
178
  )
179
  for pascal_voc_object in pascal_voc_record.objects:
180
  ir_object = DetectIrObject(
 
182
  x_min=pascal_voc_object.bndbox.xmin,
183
  y_min=pascal_voc_object.bndbox.ymin,
184
  x_max=pascal_voc_object.bndbox.xmax,
185
+ y_max=pascal_voc_object.bndbox.ymax,
186
  )
187
  ir_record.objects.append(ir_object)
188
  return ir_record
 
191
  def from_ir(ir_record: DetectIrRecord) -> PascalVocRecord:
192
  pascal_voc_record = PascalVocRecord(
193
  filename=ir_record.filename,
194
+ size=PascalVocSize(width=ir_record.width, height=ir_record.height, depth=3),
 
 
 
 
195
  )
196
  for ir_object in ir_record.objects:
197
  pascal_voc_object = PascalVocObject(
 
201
  ymin=ir_object.y_min,
202
  xmax=ir_object.x_max,
203
  ymax=ir_object.y_max,
204
+ ),
205
  )
206
  pascal_voc_record.objects.append(pascal_voc_object)
207
  return pascal_voc_record
208
 
209
 
210
  class _NumpyEncoder(json.JSONEncoder):
211
+ """Special json encoder for numpy types"""
212
 
213
  def default(self, obj):
214
  if isinstance(obj, (np.bool_,)):
215
  return bool(obj)
216
+ elif isinstance(
217
+ obj,
218
+ (
219
+ np.int_,
220
+ np.intc,
221
+ np.intp,
222
+ np.int8,
223
+ np.int16,
224
+ np.int32,
225
+ np.int64,
226
+ np.uint8,
227
+ np.uint16,
228
+ np.uint32,
229
+ np.uint64,
230
+ ),
231
+ ):
232
  return int(obj)
233
+ elif isinstance(obj, (np.float_, np.float16, np.float32, np.float64)):
 
234
  return float(obj)
235
  elif isinstance(obj, (np.ndarray,)):
236
  return obj.tolist()
 
251
 
252
  @dataclass
253
  class LabelmeRecord:
254
+ version: str = "4.5.6"
255
  flags: dict = field(default_factory=dict)
256
  shapes: List[LabelmeShape] = field(default_factory=list)
257
  imagePath: Optional[str] = None
 
280
  ir_record = DetectIrRecord(
281
  filename=labelme_record.imagePath,
282
  width=labelme_record.imageWidth,
283
+ height=labelme_record.imageHeight,
284
  )
285
  for labelme_shape in labelme_record.shapes:
286
+ if labelme_shape.shape_type != "rectangle":
287
  continue
288
  ir_object = DetectIrObject(
289
  label=labelme_shape.label,
 
300
  labelme_record = LabelmeRecord(
301
  imagePath=ir_record.filename,
302
  imageWidth=ir_record.width,
303
+ imageHeight=ir_record.height,
304
  )
305
  for ir_object in ir_record.objects:
306
  labelme_shape = LabelmeShape(
307
  label=ir_object.label,
308
+ shape_type="rectangle",
309
+ points=[
310
+ [ir_object.x_min, ir_object.y_min],
311
+ [ir_object.x_max, ir_object.y_max],
312
+ ],
313
  )
314
  labelme_record.shapes.append(labelme_shape)
315
  return labelme_record
 
335
  class YoloHandler:
336
  @staticmethod
337
  def load(filename, **kwargs) -> YoloRecord:
338
+ assert "image_filename" in kwargs
339
+ assert "width" in kwargs and "height" in kwargs
340
 
341
  records = khandy.load_list(filename)
342
  yolo_record = YoloRecord(
343
+ filename=kwargs.get("image_filename"),
344
+ width=kwargs.get("width"),
345
+ height=kwargs.get("height"),
346
+ )
347
  for record in records:
348
  record_parts = record.split()
349
+ yolo_record.objects.append(
350
+ YoloObject(
351
+ label=record_parts[0],
352
+ x_center=float(record_parts[1]),
353
+ y_center=float(record_parts[2]),
354
+ width=float(record_parts[3]),
355
+ height=float(record_parts[4]),
356
+ )
357
+ )
358
  return yolo_record
359
 
360
  @staticmethod
 
362
  records = []
363
  for object in yolo_record.objects:
364
  records.append(
365
+ f"{object.label} {object.x_center} {object.y_center} {object.width} {object.height}"
366
+ )
367
+ if not filename.endswith(".txt"):
368
+ filename = filename + ".txt"
369
  khandy.save_list(filename, records)
370
 
371
  @staticmethod
 
373
  ir_record = DetectIrRecord(
374
  filename=yolo_record.filename,
375
  width=yolo_record.width,
376
+ height=yolo_record.height,
377
  )
378
  for yolo_object in yolo_record.objects:
379
+ x_min = (yolo_object.x_center - 0.5 * yolo_object.width) * yolo_record.width
380
+ y_min = (
381
+ yolo_object.y_center - 0.5 * yolo_object.height
382
+ ) * yolo_record.height
383
+ x_max = (yolo_object.x_center + 0.5 * yolo_object.width) * yolo_record.width
384
+ y_max = (
385
+ yolo_object.y_center + 0.5 * yolo_object.height
386
+ ) * yolo_record.height
387
  ir_object = DetectIrObject(
388
  label=yolo_object.label,
389
  x_min=x_min,
390
  y_min=y_min,
391
  x_max=x_max,
392
+ y_max=y_max,
393
  )
394
  ir_record.objects.append(ir_object)
395
  return ir_record
 
397
  @staticmethod
398
  def from_ir(ir_record: DetectIrRecord) -> YoloRecord:
399
  yolo_record = YoloRecord(
400
+ filename=ir_record.filename, width=ir_record.width, height=ir_record.height
 
 
401
  )
402
  for ir_object in ir_record.objects:
403
+ x_center = (ir_object.x_max + ir_object.x_min) / (2 * ir_record.width)
404
+ y_center = (ir_object.y_max + ir_object.y_min) / (2 * ir_record.height)
 
 
405
  width = abs(ir_object.x_max - ir_object.x_min) / ir_record.width
406
  height = abs(ir_object.y_max - ir_object.y_min) / ir_record.height
407
  yolo_object = YoloObject(
 
437
  def load(filename, **kwargs) -> List[CocoRecord]:
438
  json_data = khandy.load_json(filename)
439
 
440
+ images = json_data["images"]
441
+ annotations = json_data["annotations"]
442
+ categories = json_data["categories"]
443
 
444
  label_map = {}
445
  for cat_item in categories:
446
+ label_map[cat_item["id"]] = cat_item["name"]
447
 
448
  coco_records = OrderedDict()
449
  for image_item in images:
450
+ coco_records[image_item["id"]] = CocoRecord(
451
+ filename=image_item["file_name"],
452
+ width=image_item["width"],
453
+ height=image_item["height"],
454
+ objects=[],
455
+ )
456
 
457
  for annotation_item in annotations:
458
  coco_object = CocoObject(
459
+ label=label_map[annotation_item["category_id"]],
460
+ x_min=annotation_item["bbox"][0],
461
+ y_min=annotation_item["bbox"][1],
462
+ width=annotation_item["bbox"][2],
463
+ height=annotation_item["bbox"][3],
464
+ )
465
+ coco_records[annotation_item["image_id"]].objects.append(coco_object)
466
  return list(coco_records.values())
467
 
468
  @staticmethod
 
478
  x_min=coco_object.x_min,
479
  y_min=coco_object.y_min,
480
  x_max=coco_object.x_min + coco_object.width,
481
+ y_max=coco_object.y_min + coco_object.height,
482
  )
483
  ir_record.objects.append(ir_object)
484
  return ir_record
 
486
  @staticmethod
487
  def from_ir(ir_record: DetectIrRecord) -> CocoRecord:
488
  coco_record = CocoRecord(
489
+ filename=ir_record.filename, width=ir_record.width, height=ir_record.height
 
 
490
  )
491
  for ir_object in ir_record.objects:
492
  coco_object = CocoObject(
 
494
  x_min=ir_object.x_min,
495
  y_min=ir_object.y_min,
496
  width=ir_object.x_max - ir_object.x_min,
497
+ height=ir_object.y_max - ir_object.y_min,
498
  )
499
  coco_record.objects.append(coco_object)
500
  return coco_record
501
 
502
 
503
  def load_detect(filename, fmt, **kwargs) -> DetectIrRecord:
504
+ if fmt == "labelme":
505
  labelme_record = LabelmeHandler.load(filename, **kwargs)
506
  ir_record = LabelmeHandler.to_ir(labelme_record)
507
+ elif fmt == "yolo":
508
  yolo_record = YoloHandler.load(filename, **kwargs)
509
  ir_record = YoloHandler.to_ir(yolo_record)
510
+ elif fmt in ("voc", "pascal", "pascal_voc"):
511
  pascal_voc_record = PascalVocHandler.load(filename, **kwargs)
512
  ir_record = PascalVocHandler.to_ir(pascal_voc_record)
513
+ elif fmt == "coco":
514
  coco_records = CocoHandler.load(filename, **kwargs)
515
+ ir_record = [CocoHandler.to_ir(coco_record) for coco_record in coco_records]
 
516
  else:
517
  raise ValueError(f"Unsupported detect label fmt. Got {fmt}")
518
  return ir_record
 
520
 
521
  def save_detect(filename, ir_record: DetectIrRecord, out_fmt):
522
  os.makedirs(os.path.dirname(os.path.abspath(filename)), exist_ok=True)
523
+ if out_fmt == "labelme":
524
  labelme_record = LabelmeHandler.from_ir(ir_record)
525
  LabelmeHandler.save(filename, labelme_record)
526
+ elif out_fmt == "yolo":
527
  yolo_record = YoloHandler.from_ir(ir_record)
528
  YoloHandler.save(filename, yolo_record)
529
+ elif out_fmt in ("voc", "pascal", "pascal_voc"):
530
  pascal_voc_record = PascalVocHandler.from_ir(ir_record)
531
  PascalVocHandler.save(filename, pascal_voc_record)
532
+ elif out_fmt == "coco":
533
  raise ValueError("Unsupported for `coco` now!")
534
  else:
535
  raise ValueError(f"Unsupported detect label fmt. Got {out_fmt}")
 
537
 
538
  def _get_format(record):
539
  if isinstance(record, LabelmeRecord):
540
+ return ("labelme",)
541
  elif isinstance(record, YoloRecord):
542
+ return ("yolo",)
543
  elif isinstance(record, PascalVocRecord):
544
+ return ("voc", "pascal", "pascal_voc")
545
  elif isinstance(record, CocoRecord):
546
+ return ("coco",)
547
  elif isinstance(record, DetectIrRecord):
548
+ return ("ir", "detect_ir")
549
  else:
550
  return ()
551
 
552
 
553
  def convert_detect(record, out_fmt):
554
+ allowed_fmts = (
555
+ "labelme",
556
+ "yolo",
557
+ "voc",
558
+ "coco",
559
+ "pascal",
560
+ "pascal_voc",
561
+ "ir",
562
+ "detect_ir",
563
+ )
564
  if out_fmt not in allowed_fmts:
565
+ raise ValueError("Unsupported label format conversions for given out_fmt")
 
566
  if out_fmt in _get_format(record):
567
  return record
568
 
 
577
  elif isinstance(record, DetectIrRecord):
578
  ir_record = record
579
  else:
580
+ raise TypeError("Unsupported type for record")
581
 
582
+ if out_fmt in ("ir", "detect_ir"):
583
  dst_record = ir_record
584
+ elif out_fmt == "labelme":
585
  dst_record = LabelmeHandler.from_ir(ir_record)
586
+ elif out_fmt == "yolo":
587
  dst_record = YoloHandler.from_ir(ir_record)
588
+ elif out_fmt in ("voc", "pascal", "pascal_voc"):
589
  dst_record = PascalVocHandler.from_ir(ir_record)
590
+ elif out_fmt == "coco":
591
  dst_record = CocoHandler.from_ir(ir_record)
592
  return dst_record
593
 
 
610
 
611
  def load_coco_class_names(filename):
612
  json_data = khandy.load_json(filename)
613
+ categories = json_data["categories"]
614
+ return [cat_item["name"] for cat_item in categories]