You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This is a quick prototype that I was working on today
importtkinterastkfromtkinterimportfiledialogfromPILimportImage, ImageTkimportjsonimportosfrompathlibimportPathPATH_WITH_PATCHES="ipywidgets-test-patches"PER_ROW=4ROWS=3classAnnotationTool:
def__init__(self, root, username="#user#"):
self.username=usernameself.root=rootself.root.title("Image Annotation Tool")
# Read settings file for labelsself.settings_file="settings.json"self.load_settings()
# Load annotationsself.load_annotations()
annotated_files= [x[0] forxinself.image_labels]
# Load images (replace with your own paths or file loader)self.images= [
str(x)
forxinPath(PATH_WITH_PATCHES).glob("*.png")
ifnotstr(x) inannotated_files
]
self.load_images()
# Track which images are currently visible (starting images)self.current_images=self.images[: PER_ROW*ROWS]
# Add instructional textself.add_instruction_text()
# Layout images in a gridself.create_image_grid()
# Add status bar at the bottomself.add_status_bar()
defload_settings(self):
"""Load the labels from the settings file."""ifos.path.exists(self.settings_file):
withopen(self.settings_file, "r") asfile:
self.labels=json.load(file)
else:
self.labels= {
"true": "Label1",
"false": "Label2",
} # Default labelsprint(f"Loaded labels: {self.labels}")
defload_images(self):
"""Load and resize the images."""self.tk_images= []
forimg_pathinself.images:
image=Image.open(img_path).resize((100, 100))
self.tk_images.append(ImageTk.PhotoImage(image))
defadd_instruction_text(self):
"""Add a label to display instructional text."""instructions= (
"Click an image to annotate it as 'True'.\n""Hold the Command key (Mac) or Ctrl key (Windows) and click to annotate as 'False'.\n""The next image will appear after a click."
)
self.instruction_label=tk.Label(
self.root,
text=instructions,
font=("Helvetica", 12),
justify="left",
)
self.instruction_label.grid(row=0, column=0, columnspan=3, pady=10)
defcreate_image_grid(self):
"""Display images in a 3x3 grid and set up event handlers."""self.labels_widgets= []
foriinrange(PER_ROW*ROWS):
row, col=divmod(i, PER_ROW)
label=tk.Label(
self.root,
image=self.tk_images[i],
borderwidth=2,
relief="solid",
)
label.grid(row=row+1, column=col, padx=5, pady=5)
# Save reference to the label and bind mouse eventsself.labels_widgets.append(label)
label.bind(
"<Button-1>",
lambdaevent, idx=i: self.annotate_image(event, idx),
)
defannotate_image(self, event, index):
"""Annotate an image based on a click event."""if (
event.state&0x0004
): # Command key held down (for MacOS) or 'Ctrl' for other systemslabel=self.labels["false"]
else:
label=self.labels["true"]
self.image_labels.append((str(self.images[index]), label))
print(f"Annotated {self.images[index]} as {label}")
# Save annotation after each clickself.save_annotations()
# Remove the clicked image from the queue and show the next oneself.show_next_image(index)
# Update the status bar with the annotated image and labelself.update_status_bar(self.images[index], label)
defload_annotations(self):
"""Load annotations from a JSON file."""ifos.path.exists(self.annotation_file):
withopen(self.annotation_file, "r") asfile:
self.image_labels=json.load(file)
else:
self.image_labels= []
print(f"Loaded annotations: {self.image_labels}")
defsave_annotations(self):
"""Save annotations to a JSON file."""withopen(self.annotation_file, "w") asfile:
json.dump(self.image_labels, file)
print(f"Annotations saved: {self.image_labels}")
defshow_next_image(self, index):
"""Show the next image in the queue after an image is clicked."""# Remove the current image and load the next image in the queueifself.images:
next_image_index=len(self.image_labels)
ifnext_image_index<len(self.images):
# Replace the clicked image with the next one in the listnext_image=self.tk_images[next_image_index]
self.labels_widgets[index].configure(image=next_image)
self.labels_widgets[index].image= (
next_image# To prevent image garbage collection
)
else:
# If no more images, hide the labelself.labels_widgets[index].grid_forget()
defadd_status_bar(self):
"""Create a status bar at the bottom that shows the last annotated image and label."""self.status_frame=tk.Frame(self.root, relief="sunken", bd=2)
self.status_frame.grid(row=5, column=0, columnspan=3, sticky="we")
# Placeholder for imageself.status_image_label=tk.Label(self.status_frame)
self.status_image_label.grid(row=0, column=0, padx=10)
# Placeholder for textself.status_text_label=tk.Label(
self.status_frame,
text="No image annotated yet",
font=("Helvetica", 10),
)
self.status_text_label.grid(row=0, column=1, padx=10)
defupdate_status_bar(self, image_path, label):
"""Update the status bar with a small version of the last annotated image and its label."""# Load and resize the image to 50x50 for the status barsmall_image=Image.open(image_path).resize((50, 50))
tk_small_image=ImageTk.PhotoImage(small_image)
# Update the image label in the status barself.status_image_label.configure(image=tk_small_image)
self.status_image_label.image= (
tk_small_image# Prevent garbage collection
)
# Update the text label in the status barself.status_text_label.configure(text=f"Last annotation: {label}")
@propertydefannotation_file(self):
returnf"{self.username}_annotations.json"if__name__=="__main__":
root=tk.Tk()
tool=AnnotationTool(root)
root.mainloop()
The text was updated successfully, but these errors were encountered:
If we could split out the logic for (a) saving annotations and (b) into different code pieces (objects/methods/functions), we could implement different tools for doing the annotation. tkinter could be one, LabelStudio another, Zooniverse a third... 🤔
This is a quick prototype that I was working on today
The text was updated successfully, but these errors were encountered: