Skip to content

Commit

Permalink
[Feature] Enable import/export of ODVG annotations to and from the Gr…
Browse files Browse the repository at this point in the history
…ounding DINO dataset
  • Loading branch information
CVHub520 committed Jul 30, 2024
1 parent f667768 commit 918a8b5
Show file tree
Hide file tree
Showing 10 changed files with 87,158 additions and 86,734 deletions.
Binary file added anylabeling/resources/images/format_odvg.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
173,660 changes: 86,941 additions & 86,719 deletions anylabeling/resources/resources.py

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions anylabeling/resources/resources.qrc
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
<file>images/fit.png</file>
<file>images/format_classify.png</file>
<file>images/format_default.png</file>
<file>images/format_odvg.png</file>
<file>images/format_mot.png</file>
<file>images/format_ppocr.png</file>
<file>images/format_voc.png</file>
Expand Down
86 changes: 86 additions & 0 deletions anylabeling/views/labeling/label_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import os.path as osp
import cv2
import json
import jsonlines
import math
import yaml
import pathlib
Expand Down Expand Up @@ -720,6 +721,43 @@ def mot_to_custom(self, input_file, output_path, image_path):
with open(output_file, "w", encoding="utf-8") as f:
json.dump(self.custom_data, f, indent=2, ensure_ascii=False)

def odvg_to_custom(self, input_file, output_path):
# Load od.json or od.jsonl
with jsonlines.open(input_file, 'r') as reader:
od_data = list(reader)
# Save custom info
for data in od_data:
self.reset()
shapes = []
for instance in data["detection"]["instance"]:
xmin, ymin, xmax, ymax = instance["bbox"]
points = [
[xmin, ymin],
[xmax, ymin],
[xmax, ymax],
[xmin, ymax],
]
shape = {
"label": instance["category"],
"description": None,
"points": points,
"group_id": None,
"difficult": False,
"direction": 0,
"shape_type": "rectangle",
"flags": {},
}
shapes.append(shape)
self.custom_data["imagePath"] = data["filename"]
self.custom_data["imageHeight"] = data["height"]
self.custom_data["imageWidth"] = data["width"]
self.custom_data["shapes"] = shapes
output_file = osp.join(
output_path, osp.splitext(data["filename"])[0] + ".json"
)
with open(output_file, "w", encoding="utf-8") as f:
json.dump(self.custom_data, f, indent=2, ensure_ascii=False)

def ppocr_to_custom(self, input_file, output_path, image_path, mode):
if mode in ["rec", "kie"]:
with open(input_file, "r", encoding="utf-8") as f:
Expand Down Expand Up @@ -1252,6 +1290,54 @@ def custom_to_mot(self, input_path, save_path):
for row in mot_structure["gt"]:
f.write(",".join(map(str, row)) + "\n")

def custom_to_odvg(self, image_list, label_path, save_path):
# Save label_map.json
label_map = {}
for i, c in enumerate(self.classes):
label_map[i] = c
label_map_file = osp.join(save_path, "label_map.json")
with open(label_map_file, 'w') as f:
json.dump(label_map, f)
# Save od.json
od_data = []
for image_file in image_list:
image_name = osp.basename(image_file)
label_name = osp.splitext(image_name)[0] + ".json"
label_file = osp.join(label_path, label_name)
img = cv2.imdecode(np.fromfile(image_file, dtype=np.uint8), 1)
height, width = img.shape[:2]
with open(label_file, "r", encoding="utf-8") as f:
data = json.load(f)
instance = []
for shape in data["shapes"]:
if (shape["shape_type"] != "rectangle"
or shape["label"] not in self.classes):
continue
points = shape["points"]
xmin = float(points[0][0])
ymin = float(points[0][1])
xmax = float(points[2][0])
ymax = float(points[2][1])
bbox = [xmin, ymin, xmax, ymax]
label = self.classes.index(shape["label"])
category = shape["label"]
instance.append({
"bbox": bbox,
"label": label,
"category": category
})
od_data.append({
"filename": image_name,
"height": height,
"width": width,
"detection": {
"instance": instance
}
})
od_file = osp.join(save_path, "od.json")
with jsonlines.open(od_file, mode='w') as writer:
writer.write_all(od_data)

def custom_to_pporc(self, image_file, label_file, save_path, mode):
if not osp.exists(label_file):
return
Expand Down
134 changes: 122 additions & 12 deletions anylabeling/views/labeling/label_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -951,6 +951,13 @@ def __init__(
icon="format_mot",
tip=self.tr("Upload Custom Multi-Object-Tracking Annotations"),
)
upload_odvg_annotation = action(
self.tr("&Upload ODVG Annotations"),
self.upload_odvg_annotation,
None,
icon="format_odvg",
tip=self.tr("Upload Custom Object Detection Visual Grounding Annotations"),
)
upload_ppocr_rec_annotation = action(
self.tr("&Upload PPOCR-Rec Annotations"),
lambda: self.upload_ppocr_annotation("rec"),
Expand Down Expand Up @@ -1048,6 +1055,13 @@ def __init__(
icon="format_mot",
tip=self.tr("Export Custom Multi-Object-Tracking Annotations"),
)
export_odvg_annotation = action(
self.tr("&Export ODVG Annotations"),
self.export_odvg_annotation,
None,
icon="format_odvg",
tip=self.tr("Export Custom Object Detection Visual Grounding Annotations"),
)
export_pporc_rec_annotation = action(
self.tr("&Export PPOCR-Rec Annotations"),
lambda: self.export_pporc_annotation("rec"),
Expand Down Expand Up @@ -1164,6 +1178,7 @@ def __init__(
upload_dota_annotation=upload_dota_annotation,
upload_mask_annotation=upload_mask_annotation,
upload_mot_annotation=upload_mot_annotation,
upload_odvg_annotation=upload_odvg_annotation,
upload_ppocr_rec_annotation=upload_ppocr_rec_annotation,
upload_ppocr_kie_annotation=upload_ppocr_kie_annotation,
export_yolo_hbb_annotation=export_yolo_hbb_annotation,
Expand All @@ -1177,6 +1192,7 @@ def __init__(
export_dota_annotation=export_dota_annotation,
export_mask_annotation=export_mask_annotation,
export_mot_annotation=export_mot_annotation,
export_odvg_annotation=export_odvg_annotation,
export_pporc_rec_annotation=export_pporc_rec_annotation,
export_pporc_kie_annotation=export_pporc_kie_annotation,
zoom=zoom,
Expand Down Expand Up @@ -1351,6 +1367,7 @@ def __init__(
upload_dota_annotation,
upload_mask_annotation,
upload_mot_annotation,
upload_odvg_annotation,
None,
upload_ppocr_rec_annotation,
upload_ppocr_kie_annotation,
Expand All @@ -1373,6 +1390,7 @@ def __init__(
export_dota_annotation,
export_mask_annotation,
export_mot_annotation,
export_odvg_annotation,
None,
export_pporc_rec_annotation,
export_pporc_kie_annotation,
Expand Down Expand Up @@ -4185,12 +4203,6 @@ def upload_mot_annotation(self, _value=False, dirpath=None):
return

if not self.filename:
QtWidgets.QMessageBox.warning(
self,
self.tr("Warning"),
self.tr("Please load an image folder before proceeding!"),
QtWidgets.QMessageBox.Ok,
)
return

filter = "Classes Files (*.txt);;All Files (*)"
Expand All @@ -4201,12 +4213,6 @@ def upload_mot_annotation(self, _value=False, dirpath=None):
filter,
)
if not self.classes_file:
QtWidgets.QMessageBox.warning(
self,
self.tr("Warning"),
self.tr("Please select a specific classes file!"),
QtWidgets.QMessageBox.Ok,
)
return
with open(self.classes_file, "r", encoding="utf-8") as f:
labels = f.read().splitlines()
Expand Down Expand Up @@ -4255,6 +4261,47 @@ def upload_mot_annotation(self, _value=False, dirpath=None):
# update and refresh the current canvas
self.load_file(self.filename)

def upload_odvg_annotation(self, _value=False, dirpath=None):
if not self.may_continue():
return

if not self.filename:
return

filter = "OD Files (*.json *.jsonl);;All Files (*)"
input_file, _ = QtWidgets.QFileDialog.getOpenFileName(
self,
self.tr("Select a specific OD file"),
"",
filter,
)

if (
not input_file
or QtWidgets.QMessageBox.warning(
self,
self.tr("Current annotation will be lost"),
self.tr(
"You are going to upload new annotations to this task. Continue?"
),
QtWidgets.QMessageBox.Cancel | QtWidgets.QMessageBox.Ok,
)
!= QtWidgets.QMessageBox.Ok
):
return

output_dir_path = osp.dirname(self.filename)
if self.output_dir:
output_dir_path = self.output_dir
converter = LabelConverter()
converter.odvg_to_custom(
input_file=input_file,
output_path=output_dir_path,
)

# update and refresh the current canvas
self.load_file(self.filename)

def upload_ppocr_annotation(self, mode, _value=False, dirpath=None):
if not self.may_continue():
return
Expand Down Expand Up @@ -4909,6 +4956,69 @@ def export_mot_annotation(self, _value=False, dirpath=None):
)
return

def export_odvg_annotation(self, _value=False, dirpath=None):
if not self.may_continue():
return

if not self.filename:
QtWidgets.QMessageBox.warning(
self,
self.tr("Warning"),
self.tr("Please load an image folder before proceeding!"),
QtWidgets.QMessageBox.Ok,
)
return

if not self.classes_file:
filter = "Classes Files (*.txt);;All Files (*)"
self.classes_file, _ = QtWidgets.QFileDialog.getOpenFileName(
self,
self.tr("Select a specific classes file"),
"",
filter,
)
if not self.classes_file:
return

label_dir_path = osp.dirname(self.filename)
if self.output_dir:
label_dir_path = self.output_dir

selected_dir = QtWidgets.QFileDialog.getExistingDirectory(
self,
self.tr("Select a directory to save the odvg annotations"),
label_dir_path,
QtWidgets.QFileDialog.ShowDirsOnly
)
if not selected_dir:
return

save_path = osp.realpath(selected_dir)
image_list = self.image_list
if not image_list:
image_list = [self.filename]
os.makedirs(save_path, exist_ok=True)
converter = LabelConverter(classes_file=self.classes_file)
try:
converter.custom_to_odvg(image_list, label_dir_path, save_path)
QtWidgets.QMessageBox.information(
self,
self.tr("Success"),
self.tr(
f"Annotation exported successfully!\n"
f"Check the results in: {save_path}."
),
QtWidgets.QMessageBox.Ok,
)
except Exception as e:
QtWidgets.QMessageBox.warning(
self,
self.tr("Error"),
self.tr(f"{e}"),
QtWidgets.QMessageBox.Ok,
)
return

def export_pporc_annotation(self, mode, _value=False, dirpath=None):
if not self.may_continue():
return
Expand Down
1 change: 1 addition & 0 deletions assets/ODVG/label_map.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"0": "person", "1": "bicycle", "2": "car", "3": "motorcycle", "4": "airplane", "5": "bus", "6": "train", "7": "truck", "8": "boat", "9": "traffic light", "10": "fire hydrant", "11": "stop sign", "12": "parking meter", "13": "bench", "14": "bird", "15": "cat", "16": "dog", "17": "horse", "18": "sheep", "19": "cow", "20": "elephant", "21": "bear", "22": "zebra", "23": "giraffe", "24": "backpack", "25": "umbrella", "26": "handbag", "27": "tie", "28": "suitcase", "29": "frisbee", "30": "skis", "31": "snowboard", "32": "sports ball", "33": "kite", "34": "baseball bat", "35": "baseball glove", "36": "skateboard", "37": "surfboard", "38": "tennis racket", "39": "bottle", "40": "wine glass", "41": "cup", "42": "fork", "43": "knife", "44": "spoon", "45": "bowl", "46": "banana", "47": "apple", "48": "sandwich", "49": "orange", "50": "broccoli", "51": "carrot", "52": "hot dog", "53": "pizza", "54": "donut", "55": "cake", "56": "chair", "57": "sofa", "58": "pottedplant", "59": "bed", "60": "diningtable", "61": "toilet", "62": "tvmonitor", "63": "laptop", "64": "mouse", "65": "remote", "66": "keyboard", "67": "cell phone", "68": "microwave", "69": "oven", "70": "toaster", "71": "sink", "72": "refrigerator", "73": "book", "74": "clock", "75": "vase", "76": "scissors", "77": "teddy bear", "78": "hair drier", "79": "toothbrush"}
1 change: 1 addition & 0 deletions assets/ODVG/od.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"filename": "demo.jpg", "height": 1944, "width": 2592, "detection": {"instance": [{"bbox": [568.3057319641114, 1435.8236297607423, 801.2323951721191, 1940.6873748779299], "label": 0, "category": "person"}, {"bbox": [250.73743057250977, 88.3039478302002, 642.3008560180665, 416.0265106201172], "label": 5, "category": "bus"}, {"bbox": [2044.598277282715, 961.5066833496094, 2592.0, 1443.4265258789064], "label": 2, "category": "car"}, {"bbox": [2168.0969512939455, 291.9082489013672, 2590.0523712158206, 632.3925201416016], "label": 7, "category": "truck"}, {"bbox": [935.0150756835938, 467.1713562011719, 1389.4009002685548, 709.4285430908203], "label": 2, "category": "car"}, {"bbox": [1490.3331344604494, 210.9319793701172, 1983.798097229004, 467.6459655761719], "label": 7, "category": "truck"}, {"bbox": [2474.505505371094, 517.0932792663574, 2591.8625610351564, 759.1917068481446], "label": 2, "category": "car"}, {"bbox": [1387.124382019043, 491.3653106689453, 1790.5220809936525, 701.9515914916992], "label": 2, "category": "car"}, {"bbox": [681.8321090698242, 577.1008369445801, 1127.0446243286133, 879.6766113281251], "label": 2, "category": "car"}, {"bbox": [1701.209248352051, 1034.6568420410156, 1909.2545150756837, 1272.3461608886719], "label": 1, "category": "bicycle"}, {"bbox": [1577.2593246459962, 1287.5478744506836, 1782.4759689331056, 1750.773422241211], "label": 0, "category": "person"}, {"bbox": [341.1669548034668, 837.0524871826173, 495.9798980712891, 1196.966326904297], "label": 0, "category": "person"}, {"bbox": [19.24847688674927, 1068.9359985351564, 176.856941986084, 1450.8116455078125], "label": 0, "category": "person"}, {"bbox": [867.1709632873535, 1638.5384399414063, 1039.1333724975586, 1939.5025817871094], "label": 0, "category": "person"}, {"bbox": [1730.0543746948242, 859.913703918457, 1904.4883987426758, 1192.5239089965821], "label": 0, "category": "person"}, {"bbox": [700.1718063354492, 302.9357482910156, 921.9890258789063, 439.0647171020508], "label": 2, "category": "car"}, {"bbox": [1464.6719696044922, 670.2848258972168, 1594.598016357422, 838.7980361938477], "label": 1, "category": "bicycle"}, {"bbox": [1053.4611373901369, 1550.143803405762, 1263.1692672729494, 1937.8229095458985], "label": 0, "category": "person"}, {"bbox": [76.19504356384277, 262.1977020263672, 340.8912117004395, 425.138639831543], "label": 2, "category": "car"}, {"bbox": [496.75765800476074, 811.980258178711, 632.1751762390137, 1106.7453094482423], "label": 0, "category": "person"}, {"bbox": [483.645276260376, 424.18151092529297, 856.9735977172852, 732.0099380493165], "label": 2, "category": "car"}, {"bbox": [1771.154187011719, 561.9675956726074, 2408.6232971191407, 918.0072509765625], "label": 2, "category": "car"}, {"bbox": [915.2098983764649, 1255.077053833008, 1116.117855834961, 1691.7488250732422], "label": 0, "category": "person"}, {"bbox": [136.0616397857666, 792.3811157226563, 250.77661056518556, 1102.5672637939454], "label": 0, "category": "person"}, {"bbox": [1473.6370193481446, 565.5210479736328, 1589.4288528442385, 795.7627075195313], "label": 0, "category": "person"}, {"bbox": [832.0695831298829, 970.7194198608399, 998.2965728759766, 1371.3438674926758], "label": 0, "category": "person"}, {"bbox": [571.2070289611817, 1130.0795288085938, 720.904387664795, 1497.7851196289064], "label": 0, "category": "person"}, {"bbox": [1071.136134338379, 182.94281845092775, 1350.3359756469727, 361.1679084777832], "label": 2, "category": "car"}, {"bbox": [1541.1687423706055, 396.60825119018557, 1971.5012649536134, 587.4710517883301], "label": 2, "category": "car"}, {"bbox": [960.4221267700195, 348.6872268676758, 1279.5368637084962, 502.6705307006836], "label": 2, "category": "car"}, {"bbox": [325.6000144958496, 349.9100875854492, 389.2794296264649, 518.6988555908204], "label": 0, "category": "person"}, {"bbox": [1543.162843322754, 397.4399917602539, 1965.3402420043947, 590.7352890014648], "label": 7, "category": "truck"}, {"bbox": [1222.1846466064453, 970.3025299072266, 1342.1696044921875, 1370.0215118408205], "label": 0, "category": "person"}, {"bbox": [1808.9995880126953, 352.1537910461426, 2187.0086517333984, 556.4679359436035], "label": 7, "category": "truck"}, {"bbox": [265.45752067565917, 688.9645980834962, 358.0454669952393, 991.6645248413087], "label": 0, "category": "person"}, {"bbox": [626.5533416748048, 755.5319000244141, 750.7314239501953, 1082.483624267578], "label": 0, "category": "person"}, {"bbox": [1193.545555114746, 1233.8073852539064, 1256.292869567871, 1315.7788513183596], "label": 26, "category": "handbag"}, {"bbox": [1541.7802963256836, 1533.2621704101564, 1653.9361770629885, 1689.3923400878907], "label": 26, "category": "handbag"}, {"bbox": [1180.547314453125, 1387.6520141601563, 1259.1935485839845, 1533.373406982422], "label": 26, "category": "handbag"}, {"bbox": [14.264483642578126, 600.0972061157227, 110.88783016204835, 860.7037307739258], "label": 0, "category": "person"}, {"bbox": [345.31243286132815, 994.7911376953125, 394.4514976501465, 1116.7568481445312], "label": 26, "category": "handbag"}, {"bbox": [461.77211837768556, 608.4305557250976, 561.3444854736329, 825.7744583129884], "label": 0, "category": "person"}, {"bbox": [0.0, 894.918864440918, 77.95618114471436, 1284.5674758911134], "label": 0, "category": "person"}, {"bbox": [141.36901473999023, 346.4582313537598, 218.52517318725586, 523.6349784851075], "label": 0, "category": "person"}, {"bbox": [76.45697479248047, 1147.9113693237305, 140.87081394195556, 1236.9033462524415], "label": 26, "category": "handbag"}, {"bbox": [0.7425890922546388, 157.6424617767334, 75.8009262084961, 316.4888122558594], "label": 2, "category": "car"}, {"bbox": [1821.8278839111329, 350.3739440917969, 2190.6821777343753, 559.7829711914063], "label": 2, "category": "car"}, {"bbox": [73.8185359954834, 994.9901275634766, 140.259352684021, 1073.2087188720704], "label": 26, "category": "handbag"}, {"bbox": [1294.401530456543, 1097.295021057129, 1370.207276916504, 1230.059083557129], "label": 26, "category": "handbag"}]}}
3 changes: 2 additions & 1 deletion requirements-gpu.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ scipy
shapely
pyclipper
filterpy
tokenizers
tokenizers
jsonlines
3 changes: 2 additions & 1 deletion requirements-macos.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ scipy
shapely
pyclipper
filterpy
tokenizers
tokenizers
jsonlines
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ scipy
shapely
pyclipper
filterpy
tokenizers
tokenizers
jsonlines

0 comments on commit 918a8b5

Please sign in to comment.