CCockrum commited on
Commit
6c73b1f
·
verified ·
1 Parent(s): a25264c

Update annotation_tool.py

Browse files
Files changed (1) hide show
  1. 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()