TCIA-Detection-model / annotation_tool.py
CCockrum's picture
Update annotation_tool.py
6c73b1f verified
"""
Simple tool for annotating tumors in medical images
"""
import os
import cv2
import numpy as np
import argparse
import json
from glob import glob
class TumorAnnotator:
def __init__(self, images_dir, annotations_dir):
self.images_dir = images_dir
self.annotations_dir = annotations_dir
self.image_files = sorted(glob(os.path.join(images_dir, "*.png")))
self.current_idx = 0
self.drawing = False
self.roi_pt1 = None
self.roi_pt2 = None
self.annotations = {}
# Create annotations directory if it doesn't exist
os.makedirs(annotations_dir, exist_ok=True)
# Load existing annotations if available
self.load_annotations()
def load_annotations(self):
"""Load existing annotations"""
for image_file in self.image_files:
image_name = os.path.basename(image_file)
ann_file = os.path.join(self.annotations_dir, f"{os.path.splitext(image_name)[0]}.json")
if os.path.exists(ann_file):
with open(ann_file, 'r') as f:
self.annotations[image_name] = json.load(f)
def save_annotation(self, image_name):
"""Save annotation for current image"""
if image_name not in self.annotations or not self.annotations[image_name]:
return
ann_file = os.path.join(self.annotations_dir, f"{os.path.splitext(image_name)[0]}.json")
with open(ann_file, 'w') as f:
json.dump(self.annotations[image_name], f)
def mouse_callback(self, event, x, y, flags, param):
"""Mouse callback for drawing bounding boxes"""
if event == cv2.EVENT_LBUTTONDOWN:
self.drawing = True
self.roi_pt1 = (x, y)
self.roi_pt2 = (x, y)
elif event == cv2.EVENT_MOUSEMOVE:
if self.drawing:
self.roi_pt2 = (x, y)
elif event == cv2.EVENT_LBUTTONUP:
self.drawing = False
self.roi_pt2 = (x, y)
# Add bounding box to annotations
image_name = os.path.basename(self.image_files[self.current_idx])
if image_name not in self.annotations:
self.annotations[image_name] = []
x1, y1 = min(self.roi_pt1[0], self.roi_pt2[0]), min(self.roi_pt1[1], self.roi_pt2[1])
x2, y2 = max(self.roi_pt1[0], self.roi_pt2[0]), max(self.roi_pt1[1], self.roi_pt2[1])
# Store as [x, y, width, height]
bbox = [x1, y1, x2 - x1, y2 - y1]
self.annotations[image_name].append({
"bbox": bbox,
"label": "tumor"
})
def run(self):
"""Run the annotation tool"""
cv2.namedWindow("Tumor Annotator")
cv2.setMouseCallback("Tumor Annotator", self.mouse_callback)
while True:
if self.current_idx >= len(self.image_files) or self.current_idx < 0:
break
image_path = self.image_files[self.current_idx]
image_name = os.path.basename(image_path)
image = cv2.imread(image_path)
display_image = image.copy()
# Draw existing annotations
if image_name in self.annotations:
for ann in self.annotations[image_name]:
bbox = ann["bbox"]
cv2.rectangle(
display_image,
(bbox[0], bbox[1]),
(bbox[0] + bbox[2], bbox[1] + bbox[3]),
(0, 255, 0),
2
)
# Draw current selection
if self.drawing:
cv2.rectangle(
display_image,
self.roi_pt1,
self.roi_pt2,
(0, 0, 255),
2
)
# Display image with annotations
cv2.imshow("Tumor Annotator", display_image)
# Display instructions
print("\nImage:", image_name)
print("Controls:")
print(" Left-click and drag: Draw bounding box")
print(" n: Next image")
print(" p: Previous image")
print(" d: Delete last annotation")
print(" s: Save annotations")
print(" q: Quit")
key = cv2.waitKey(1) & 0xFF
if key == ord('n'): # Next image
self.save_annotation(image_name)
self.current_idx = min(self.current_idx + 1, len(self.image_files) - 1)
elif key == ord('p'): # Previous image
self.save_annotation(image_name)
self.current_idx = max(self.current_idx - 1, 0)
elif key == ord('d'): # Delete last annotation
if image_name in self.annotations and self.annotations[image_name]:
self.annotations[image_name].pop()
elif key == ord('s'): # Save annotations
self.save_annotation(image_name)
print("Annotations saved!")
elif key == ord('q'): # Quit
self.save_annotation(image_name)
break
cv2.destroyAllWindows()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Tumor annotation tool')
parser.add_argument('--images', type=str, required=True, help='Directory containing images')
parser.add_argument('--annotations', type=str, default='./annotations', help='Directory to save annotations')
args = parser.parse_args()
annotator = TumorAnnotator(args.images, args.annotations)
annotator.run()