Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add a useful function: image list panel #68

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,5 @@ coverage.xml
dmypy.json

# IDEA config files
.idea/
.idea/
.vscode/
Binary file added coco_viewer.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
104 changes: 74 additions & 30 deletions cocoviewer.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,12 @@
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")

parser = argparse.ArgumentParser(description="View images with bboxes from the COCO dataset")
parser.add_argument("-i", "--images", default="", type=str, metavar="PATH", help="path to images folder")
parser.add_argument(
"-a",
"--annotations",
default="",
"annotations",
type=str,
metavar="PATH",
help="path to annotations json file",
)

parser.add_argument("imagedir", default="./",nargs='?', type=str, help="path to images folder")

class Data:
"""Handles data related stuff."""
Expand Down Expand Up @@ -84,6 +80,9 @@ def next_image(self):
def previous_image(self):
"""Loads the previous image in a list."""
self.current_image = self.images.prev()

def select_image(self, ind):
self.current_image = self.images.set(ind)


def parse_coco(annotations_file: str) -> tuple:
Expand Down Expand Up @@ -137,7 +136,7 @@ def prepare_colors(n_objects: int, shuffle: bool = True) -> list:
def get_categories(instances: dict) -> dict:
"""Extracts categories from annotations file and prepares color for each one."""
# Parse categories
colors = prepare_colors(n_objects=80, shuffle=True)
colors = prepare_colors(n_objects=len(instances["categories"]), shuffle=True)
categories = list(
zip(
[[category["id"], category["name"]] for category in instances["categories"]],
Expand Down Expand Up @@ -180,7 +179,9 @@ def draw_bboxes(draw, objects, labels, obj_categories, ignore, width, label_size
# TODO: Implement notification message as popup window
font = ImageFont.load_default()

tw, th = draw.textsize(text, font)
text_size = draw.textbbox((0, 0), text, font=font)
tw = text_size[2] - text_size[0]
th = text_size[3] - text_size[1]
tx0 = b[0]
ty0 = b[1] - th

Expand Down Expand Up @@ -212,7 +213,10 @@ def draw_masks(draw, objects, obj_categories, ignore, alpha):
if isinstance(m, list):
for m_ in m:
if m_:
draw.polygon(m_, outline=fill, fill=fill)
try:
draw.polygon(m_, outline=fill, fill=fill)
except:
print('WARNING draw_masks: invalid polygon', m_)
# RLE mask for collection of objects (iscrowd=1)
elif isinstance(m, dict) and objects[i]["iscrowd"]:
mask = rle_to_mask(m["counts"][:-1], m["size"][0], m["size"][1])
Expand Down Expand Up @@ -267,6 +271,12 @@ def prev(self):
self.n -= 1
current_image = self.image_list[self.n]
return current_image

def set(self,ind):
assert ind>=0 and ind < self.max
self.n = ind
current_image = self.image_list[self.n]
return current_image


class ImagePanel(ttk.Frame):
Expand Down Expand Up @@ -487,6 +497,20 @@ def __init__(self, parent):
self.object_box.pack(side=tk.TOP, fill=tk.Y, expand=True)
self.add(self.object_subpanel)

class ImagelistPanel(ttk.PanedWindow):
def __init__(self, parent):
super().__init__(parent)
# Image list subpanel
self.pack(side=tk.LEFT, fill=tk.Y)
self.imglist_subpanel = ttk.Frame()
ttk.Label(self.imglist_subpanel, text="image list", borderwidth=2, background="gray50").pack(side=tk.TOP, fill=tk.X)
# image list controller
scrollbar = tk.Scrollbar(self.imglist_subpanel)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.imglist_box = tk.Listbox(self.imglist_subpanel, selectmode=tk.SINGLE, exportselection=0,yscrollcommand=scrollbar.set)
scrollbar.config(command=self.imglist_box.yview)
self.imglist_box.pack(side=tk.TOP, fill=tk.Y, expand=True)
self.add(self.imglist_subpanel)

class SlidersBar(ttk.Frame):
def __init__(self, parent):
Expand All @@ -504,16 +528,18 @@ def __init__(self, parent):
# Mask transparency controller
self.mask_slider = tk.Scale(self, label="mask", from_=0, to=255, tickinterval=50, orient=tk.HORIZONTAL)
self.mask_slider.pack(side=tk.LEFT, fill=tk.X, expand=True)



class Controller:
def __init__(self, data, root, image_panel, statusbar, menu, objects_panel, sliders):
def __init__(self, data, root, image_panel, statusbar, menu, objects_panel, sliders, imglist_panel):
self.data = data # data layer
self.root = root # root window
self.image_panel = image_panel # image panel
self.statusbar = statusbar # statusbar on the bottom
self.menu = menu # main menu on the top
self.objects_panel = objects_panel
self.imglist_panel = imglist_panel
self.sliders = sliders

# StatusBar Vars
Expand Down Expand Up @@ -564,9 +590,10 @@ def __init__(self, data, root, image_panel, statusbar, menu, objects_panel, slid
self.selected_objs = None
self.category_box_content = tk.StringVar()
self.object_box_content = tk.StringVar()
self.imglist_box_content = tk.StringVar()
self.objects_panel.category_box.configure(listvariable=self.category_box_content)
self.objects_panel.object_box.configure(listvariable=self.object_box_content)

self.imglist_panel.imglist_box.configure(listvariable=self.imglist_box_content)
# Sliders Setup
self.bbox_thickness = tk.IntVar()
self.bbox_thickness.set(3)
Expand All @@ -587,6 +614,8 @@ def __init__(self, data, root, image_panel, statusbar, menu, objects_panel, slid
self.current_img_categories = None
self.update_img()

self.update_imglist_box()

def set_locals(self):
self.bboxes_on_local = self.bboxes_on_global.get()
self.labels_on_local = self.labels_on_global.get()
Expand Down Expand Up @@ -688,18 +717,20 @@ def exit(self, event=None):
self.root.quit()

def next_img(self, event=None):
self.data.next_image()
self.set_locals()
self.selected_cats = None
self.selected_objs = None
self.update_img(local=False)
cur_id = self.imglist_panel.imglist_box.index('active')
cur_id = min(self.imglist_panel.imglist_box.size() - 1,cur_id+1)
self.imglist_panel.imglist_box.selection_clear(0, tk.END)
self.imglist_panel.imglist_box.activate(cur_id)
self.imglist_panel.imglist_box.selection_set(cur_id)
self.select_img(None)

def prev_img(self, event=None):
self.data.previous_image()
self.set_locals()
self.selected_cats = None
self.selected_objs = None
self.update_img(local=False)
cur_id = self.imglist_panel.imglist_box.index('active')
cur_id = max(0,cur_id-1)
self.imglist_panel.imglist_box.selection_clear(0, tk.END)
self.imglist_panel.imglist_box.activate(cur_id)
self.imglist_panel.imglist_box.selection_set(cur_id)
self.select_img(None)

def save_image(self, event=None):
"""Saves composed image as png file."""
Expand Down Expand Up @@ -825,6 +856,21 @@ def select_object(self, event):
self.selected_cats = selected_cats
self.update_img()

def update_imglist_box(self):
imglist = [f'{i:04d} {name}' for i,name in self.data.images.image_list]
self.imglist_box_content.set(imglist)
max_len = max(len(item) for item in imglist)
self.imglist_panel.imglist_box.config(width = max_len)
self.imglist_panel.imglist_box.select_set(0,tk.END)

def select_img(self, event):
ind = self.imglist_panel.imglist_box.curselection()[0]
self.data.select_image(ind)
self.set_locals()
self.selected_cats = None
self.selected_objs = None
self.update_img(local=False)

def update_sliders_state(self):
self.bbox_slider_status_update()
self.label_slider_status_update()
Expand Down Expand Up @@ -864,6 +910,7 @@ def bind_events(self):
# Objects Panel
self.objects_panel.category_box.bind("<<ListboxSelect>>", self.select_category)
self.objects_panel.object_box.bind("<<ListboxSelect>>", self.select_object)
self.imglist_panel.imglist_box.bind("<<ListboxSelect>>", self.select_img)
self.image_panel.bind("<Button-1>", lambda e: self.image_panel.focus_set())


Expand All @@ -876,21 +923,18 @@ def main():
args = parser.parse_args()
root = tk.Tk()
root.title("COCO Viewer")
# Set the window icon
icon_path = os.path.dirname(os.path.realpath(__file__))+'/coco_viewer.png'
root.iconphoto(True, tk.PhotoImage(file=icon_path))

if not args.images or not args.annotations:
root.geometry("300x150") # app size when no data is provided
messagebox.showwarning("Warning!", "Nothing to show.\nPlease specify a path to the COCO dataset!")
print_info("Exiting...")
root.destroy()
return

data = Data(args.images, args.annotations)
data = Data(args.imagedir, args.annotations)
statusbar = StatusBar(root)
sliders = SlidersBar(root)
objects_panel = ObjectsPanel(root)
imglist_panel = ImagelistPanel(root)
menu = Menu(root)
image_panel = ImagePanel(root)
Controller(data, root, image_panel, statusbar, menu, objects_panel, sliders)
Controller(data, root, image_panel, statusbar, menu, objects_panel, sliders, imglist_panel)
root.mainloop()


Expand Down