Update annotation_tool.py
Browse files- annotation_tool.py +157 -0
annotation_tool.py
CHANGED
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Simple tool for annotating tumors in medical images
|
3 |
+
"""
|
4 |
+
import os
|
5 |
+
import cv2
|
6 |
+
import numpy as np
|
7 |
+
import argparse
|
8 |
+
import json
|
9 |
+
from glob import glob
|
10 |
+
|
11 |
+
class TumorAnnotator:
|
12 |
+
def __init__(self, images_dir, annotations_dir):
|
13 |
+
self.images_dir = images_dir
|
14 |
+
self.annotations_dir = annotations_dir
|
15 |
+
self.image_files = sorted(glob(os.path.join(images_dir, "*.png")))
|
16 |
+
self.current_idx = 0
|
17 |
+
self.drawing = False
|
18 |
+
self.roi_pt1 = None
|
19 |
+
self.roi_pt2 = None
|
20 |
+
self.annotations = {}
|
21 |
+
|
22 |
+
# Create annotations directory if it doesn't exist
|
23 |
+
os.makedirs(annotations_dir, exist_ok=True)
|
24 |
+
|
25 |
+
# Load existing annotations if available
|
26 |
+
self.load_annotations()
|
27 |
+
|
28 |
+
def load_annotations(self):
|
29 |
+
"""Load existing annotations"""
|
30 |
+
for image_file in self.image_files:
|
31 |
+
image_name = os.path.basename(image_file)
|
32 |
+
ann_file = os.path.join(self.annotations_dir, f"{os.path.splitext(image_name)[0]}.json")
|
33 |
+
if os.path.exists(ann_file):
|
34 |
+
with open(ann_file, 'r') as f:
|
35 |
+
self.annotations[image_name] = json.load(f)
|
36 |
+
|
37 |
+
def save_annotation(self, image_name):
|
38 |
+
"""Save annotation for current image"""
|
39 |
+
if image_name not in self.annotations or not self.annotations[image_name]:
|
40 |
+
return
|
41 |
+
|
42 |
+
ann_file = os.path.join(self.annotations_dir, f"{os.path.splitext(image_name)[0]}.json")
|
43 |
+
with open(ann_file, 'w') as f:
|
44 |
+
json.dump(self.annotations[image_name], f)
|
45 |
+
|
46 |
+
def mouse_callback(self, event, x, y, flags, param):
|
47 |
+
"""Mouse callback for drawing bounding boxes"""
|
48 |
+
if event == cv2.EVENT_LBUTTONDOWN:
|
49 |
+
self.drawing = True
|
50 |
+
self.roi_pt1 = (x, y)
|
51 |
+
self.roi_pt2 = (x, y)
|
52 |
+
|
53 |
+
elif event == cv2.EVENT_MOUSEMOVE:
|
54 |
+
if self.drawing:
|
55 |
+
self.roi_pt2 = (x, y)
|
56 |
+
|
57 |
+
elif event == cv2.EVENT_LBUTTONUP:
|
58 |
+
self.drawing = False
|
59 |
+
self.roi_pt2 = (x, y)
|
60 |
+
|
61 |
+
# Add bounding box to annotations
|
62 |
+
image_name = os.path.basename(self.image_files[self.current_idx])
|
63 |
+
if image_name not in self.annotations:
|
64 |
+
self.annotations[image_name] = []
|
65 |
+
|
66 |
+
x1, y1 = min(self.roi_pt1[0], self.roi_pt2[0]), min(self.roi_pt1[1], self.roi_pt2[1])
|
67 |
+
x2, y2 = max(self.roi_pt1[0], self.roi_pt2[0]), max(self.roi_pt1[1], self.roi_pt2[1])
|
68 |
+
|
69 |
+
# Store as [x, y, width, height]
|
70 |
+
bbox = [x1, y1, x2 - x1, y2 - y1]
|
71 |
+
self.annotations[image_name].append({
|
72 |
+
"bbox": bbox,
|
73 |
+
"label": "tumor"
|
74 |
+
})
|
75 |
+
|
76 |
+
def run(self):
|
77 |
+
"""Run the annotation tool"""
|
78 |
+
cv2.namedWindow("Tumor Annotator")
|
79 |
+
cv2.setMouseCallback("Tumor Annotator", self.mouse_callback)
|
80 |
+
|
81 |
+
while True:
|
82 |
+
if self.current_idx >= len(self.image_files) or self.current_idx < 0:
|
83 |
+
break
|
84 |
+
|
85 |
+
image_path = self.image_files[self.current_idx]
|
86 |
+
image_name = os.path.basename(image_path)
|
87 |
+
image = cv2.imread(image_path)
|
88 |
+
display_image = image.copy()
|
89 |
+
|
90 |
+
# Draw existing annotations
|
91 |
+
if image_name in self.annotations:
|
92 |
+
for ann in self.annotations[image_name]:
|
93 |
+
bbox = ann["bbox"]
|
94 |
+
cv2.rectangle(
|
95 |
+
display_image,
|
96 |
+
(bbox[0], bbox[1]),
|
97 |
+
(bbox[0] + bbox[2], bbox[1] + bbox[3]),
|
98 |
+
(0, 255, 0),
|
99 |
+
2
|
100 |
+
)
|
101 |
+
|
102 |
+
# Draw current selection
|
103 |
+
if self.drawing:
|
104 |
+
cv2.rectangle(
|
105 |
+
display_image,
|
106 |
+
self.roi_pt1,
|
107 |
+
self.roi_pt2,
|
108 |
+
(0, 0, 255),
|
109 |
+
2
|
110 |
+
)
|
111 |
+
|
112 |
+
# Display image with annotations
|
113 |
+
cv2.imshow("Tumor Annotator", display_image)
|
114 |
+
|
115 |
+
# Display instructions
|
116 |
+
print("\nImage:", image_name)
|
117 |
+
print("Controls:")
|
118 |
+
print(" Left-click and drag: Draw bounding box")
|
119 |
+
print(" n: Next image")
|
120 |
+
print(" p: Previous image")
|
121 |
+
print(" d: Delete last annotation")
|
122 |
+
print(" s: Save annotations")
|
123 |
+
print(" q: Quit")
|
124 |
+
|
125 |
+
key = cv2.waitKey(1) & 0xFF
|
126 |
+
|
127 |
+
if key == ord('n'): # Next image
|
128 |
+
self.save_annotation(image_name)
|
129 |
+
self.current_idx = min(self.current_idx + 1, len(self.image_files) - 1)
|
130 |
+
|
131 |
+
elif key == ord('p'): # Previous image
|
132 |
+
self.save_annotation(image_name)
|
133 |
+
self.current_idx = max(self.current_idx - 1, 0)
|
134 |
+
|
135 |
+
elif key == ord('d'): # Delete last annotation
|
136 |
+
if image_name in self.annotations and self.annotations[image_name]:
|
137 |
+
self.annotations[image_name].pop()
|
138 |
+
|
139 |
+
elif key == ord('s'): # Save annotations
|
140 |
+
self.save_annotation(image_name)
|
141 |
+
print("Annotations saved!")
|
142 |
+
|
143 |
+
elif key == ord('q'): # Quit
|
144 |
+
self.save_annotation(image_name)
|
145 |
+
break
|
146 |
+
|
147 |
+
cv2.destroyAllWindows()
|
148 |
+
|
149 |
+
if __name__ == "__main__":
|
150 |
+
parser = argparse.ArgumentParser(description='Tumor annotation tool')
|
151 |
+
parser.add_argument('--images', type=str, required=True, help='Directory containing images')
|
152 |
+
parser.add_argument('--annotations', type=str, default='./annotations', help='Directory to save annotations')
|
153 |
+
|
154 |
+
args = parser.parse_args()
|
155 |
+
|
156 |
+
annotator = TumorAnnotator(args.images, args.annotations)
|
157 |
+
annotator.run()
|