Coretex
voc_converter.py
1 # Copyright (C) 2023 Coretex LLC
2 
3 # This file is part of Coretex.ai
4 
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License as
7 # published by the Free Software Foundation, either version 3 of the
8 # License, or (at your option) any later version.
9 
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU Affero General Public License for more details.
14 
15 # You should have received a copy of the GNU Affero General Public License
16 # along with this program. If not, see <https://www.gnu.org/licenses/>.
17 
18 from typing import Optional, List, Set
19 
20 import os
21 import logging
22 import glob
23 import xml.etree.ElementTree as ET
24 
25 from .pascal.shared import getTag, getBoxes, toInt
26 from ..base_converter import BaseConverter
27 from ...annotation import CoretexImageAnnotation, CoretexSegmentationInstance, BBox
28 
29 
30 class VOCConverter(BaseConverter):
31 
32  def __init__(self, datasetName: str, projectId: int, datasetPath: str) -> None:
33  super().__init__(datasetName, projectId, datasetPath)
34 
35  self.__imagesPath = os.path.join(datasetPath, "images")
36 
37  annotations = os.path.join(datasetPath, "annotations")
38  self.__fileNames = glob.glob(os.path.join(annotations, "*.xml"))
39 
40  def _dataSource(self) -> List[str]:
41  return self.__fileNames
42 
43  def _extractLabels(self) -> Set[str]:
44  labels: Set[str] = set()
45 
46  for filename in self.__fileNames:
47  tree = ET.parse(filename)
48  root = tree.getroot()
49  objects = root.findall("object")
50 
51  for obj in objects:
52  labelElement = obj.find("name")
53  if labelElement is None:
54  continue
55 
56  label = labelElement.text
57  if label is None:
58  continue
59 
60  labels.add(label)
61 
62  return labels
63 
64  def __extractInstance(self, obj: ET.Element) -> Optional[CoretexSegmentationInstance]:
65  label = getTag(obj, "name")
66  if label is None:
67  return None
68 
69  coretexClass = self._dataset.classByName(label)
70  if coretexClass is None:
71  logging.getLogger("coretexpylib").info(f">> [Coretex] Class: ({label}) is not a part of dataset")
72  return None
73 
74  bboxElement = obj.find('bndbox')
75  if bboxElement is None:
76  return None
77 
78  encodedBbox = getBoxes(bboxElement)
79  if encodedBbox is None:
80  return None
81 
82  bbox = BBox.decode(encodedBbox)
83  return CoretexSegmentationInstance.create(coretexClass.classIds[0], bbox, [bbox.polygon])
84 
85  def _extractImageAnnotation(self, root: ET.Element) -> None:
86  fileName = getTag(root, "filename")
87  if fileName is None:
88  return
89 
90  size = root.find('size')
91  if size is None:
92  return
93 
94  width, height = toInt(size, "width", "height")
95  if width is None or height is None:
96  return
97 
98  coretexAnnotation = CoretexImageAnnotation.create(fileName, width, height, [])
99 
100  for obj in root.findall("object"):
101  instance = self.__extractInstance(obj)
102  if instance is None:
103  continue
104 
105  coretexAnnotation.instances.append(instance)
106 
107  self._saveImageAnnotationPair(os.path.join(self.__imagesPath, fileName), coretexAnnotation)
108 
109  def _extractSingleAnnotation(self, fileName: str) -> None:
110  tree = ET.parse(fileName)
111  root = tree.getroot()
112 
113  self._extractImageAnnotation(root)