From 3cf0369157d50f93fec1071bb4ac62b9e8cc28de Mon Sep 17 00:00:00 2001 From: Shi Qiu Date: Sun, 21 May 2017 15:15:08 +0800 Subject: [PATCH 1/5] PR for multi-class support (#11) * modified main.py * add more pics * add more pics * update * update * update * a temp demo * temp * update * update * update * add .gitignore --- .gitignore | 4 + .../Cute-Dog-Wallpaper.JPG} | Bin Examples/{001/dog.JPEG => demo/dog.JPG} | Bin .../dogs-wallpaper.JPG} | Bin Images/001/{test.JPEG => test.JPG} | Bin Images/001/{test2.JPEG => test2.JPG} | Bin Images/001/{test3.JPEG => test3.JPG} | Bin Labels/001/test.txt | 5 +- Labels/001/test2.txt | 2 - Labels/001/test3.txt | 2 - README.md | 18 +++++ class.txt | 3 + main.py | 69 +++++++++++++----- 13 files changed, 79 insertions(+), 24 deletions(-) create mode 100644 .gitignore rename Examples/{001/Cute-Dog-Wallpaper.JPEG => demo/Cute-Dog-Wallpaper.JPG} (100%) rename Examples/{001/dog.JPEG => demo/dog.JPG} (100%) rename Examples/{001/dogs-wallpaper.JPEG => demo/dogs-wallpaper.JPG} (100%) rename Images/001/{test.JPEG => test.JPG} (100%) rename Images/001/{test2.JPEG => test2.JPG} (100%) rename Images/001/{test3.JPEG => test3.JPG} (100%) delete mode 100644 Labels/001/test2.txt delete mode 100644 Labels/001/test3.txt create mode 100644 class.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..c5c32eb8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/Images +!/Images/001 +/Labels +!Labels/001 \ No newline at end of file diff --git a/Examples/001/Cute-Dog-Wallpaper.JPEG b/Examples/demo/Cute-Dog-Wallpaper.JPG similarity index 100% rename from Examples/001/Cute-Dog-Wallpaper.JPEG rename to Examples/demo/Cute-Dog-Wallpaper.JPG diff --git a/Examples/001/dog.JPEG b/Examples/demo/dog.JPG similarity index 100% rename from Examples/001/dog.JPEG rename to Examples/demo/dog.JPG diff --git a/Examples/001/dogs-wallpaper.JPEG b/Examples/demo/dogs-wallpaper.JPG similarity index 100% rename from Examples/001/dogs-wallpaper.JPEG rename to Examples/demo/dogs-wallpaper.JPG diff --git a/Images/001/test.JPEG b/Images/001/test.JPG similarity index 100% rename from Images/001/test.JPEG rename to Images/001/test.JPG diff --git a/Images/001/test2.JPEG b/Images/001/test2.JPG similarity index 100% rename from Images/001/test2.JPEG rename to Images/001/test2.JPG diff --git a/Images/001/test3.JPEG b/Images/001/test3.JPG similarity index 100% rename from Images/001/test3.JPEG rename to Images/001/test3.JPG diff --git a/Labels/001/test.txt b/Labels/001/test.txt index 808509e1..9aded27f 100644 --- a/Labels/001/test.txt +++ b/Labels/001/test.txt @@ -1,2 +1,3 @@ -1 -80 8 172 111 +2 +80 12 174 110 dog +17 49 72 110 cat diff --git a/Labels/001/test2.txt b/Labels/001/test2.txt deleted file mode 100644 index 22e8a683..00000000 --- a/Labels/001/test2.txt +++ /dev/null @@ -1,2 +0,0 @@ -1 -129 42 389 235 diff --git a/Labels/001/test3.txt b/Labels/001/test3.txt deleted file mode 100644 index e51f2880..00000000 --- a/Labels/001/test3.txt +++ /dev/null @@ -1,2 +0,0 @@ -1 -74 73 171 170 diff --git a/README.md b/README.md index c03ac4a3..64feac44 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,21 @@ +### Forked from [puzzledqs/BBox-Label-Tool](https://github.com/puzzledqs/BBox-Label-Tool) +## Improvements +1. Add multi-class support +2. Change some of the color-candidates for better display +3. Fix the 'Example' filepath for convenience +4. Change the image format from '.JPEG' to '.JPG' + +## New Usage +### For multi-class task, modify 'class.txt' with your own class-candidates and before labeling bbox, choose the 'Current Class' in the Combobox and make sure you click 'ComfirmClass' button. + +### The remaining usage is the same as the origin one. + +------------------------------------ + +**Contact info**: jxgu1016@gmail.com + +------------------------------------ + BBox-Label-Tool =============== diff --git a/class.txt b/class.txt new file mode 100644 index 00000000..a931f210 --- /dev/null +++ b/class.txt @@ -0,0 +1,3 @@ +dog +cat +baby \ No newline at end of file diff --git a/main.py b/main.py index e19a0888..df680920 100644 --- a/main.py +++ b/main.py @@ -10,12 +10,13 @@ from Tkinter import * import tkMessageBox from PIL import Image, ImageTk +import ttk import os import glob import random # colors for the bboxes -COLORS = ['red', 'blue', 'yellow', 'pink', 'cyan', 'green', 'black'] +COLORS = ['red', 'blue', 'olive', 'teal', 'cyan', 'green', 'black'] # image sizes for the examples SIZE = 256, 256 @@ -40,6 +41,9 @@ def __init__(self, master): self.imagename = '' self.labelfilename = '' self.tkimg = None + self.currentLabelclass = '' + self.cla_can_temp = [] + self.classcandidate_filename = 'class.txt' # initialize mouse state self.STATE = {} @@ -60,7 +64,7 @@ def __init__(self, master): self.entry = Entry(self.frame) self.entry.grid(row = 0, column = 1, sticky = W+E) self.ldBtn = Button(self.frame, text = "Load", command = self.loadDir) - self.ldBtn.grid(row = 0, column = 2, sticky = W+E) + self.ldBtn.grid(row = 0, column = 2,sticky = W+E) # main panel for labeling self.mainPanel = Canvas(self.frame, cursor='tcross') @@ -72,19 +76,35 @@ def __init__(self, master): self.parent.bind("d", self.nextImage) # press 'd' to go forward self.mainPanel.grid(row = 1, column = 1, rowspan = 4, sticky = W+N) + # choose class + self.classname = StringVar() + self.classcandidate = ttk.Combobox(self.frame,state='readonly',textvariable=self.classname) + self.classcandidate.grid(row=1,column=2) + if os.path.exists(self.classcandidate_filename): + with open(self.classcandidate_filename) as cf: + for line in cf.readlines(): + # print line + self.cla_can_temp.append(line.strip('\n')) + #print self.cla_can_temp + self.classcandidate['values'] = self.cla_can_temp + self.classcandidate.current(0) + self.currentLabelclass = self.classcandidate.get() #init + self.btnclass = Button(self.frame, text = 'ComfirmClass', command = self.setClass) + self.btnclass.grid(row=2,column=2,sticky = W+E) + # showing bbox info & delete bbox self.lb1 = Label(self.frame, text = 'Bounding boxes:') - self.lb1.grid(row = 1, column = 2, sticky = W+N) + self.lb1.grid(row = 3, column = 2, sticky = W+N) self.listbox = Listbox(self.frame, width = 22, height = 12) - self.listbox.grid(row = 2, column = 2, sticky = N) + self.listbox.grid(row = 4, column = 2, sticky = N+S) self.btnDel = Button(self.frame, text = 'Delete', command = self.delBBox) - self.btnDel.grid(row = 3, column = 2, sticky = W+E+N) + self.btnDel.grid(row = 5, column = 2, sticky = W+E+N) self.btnClear = Button(self.frame, text = 'ClearAll', command = self.clearBBox) - self.btnClear.grid(row = 4, column = 2, sticky = W+E+N) + self.btnClear.grid(row = 6, column = 2, sticky = W+E+N) # control panel for image navigation self.ctrPanel = Frame(self.frame) - self.ctrPanel.grid(row = 5, column = 1, columnspan = 2, sticky = W+E) + self.ctrPanel.grid(row = 7, column = 1, columnspan = 2, sticky = W+E) self.prevBtn = Button(self.ctrPanel, text='<< Prev', width = 10, command = self.prevImage) self.prevBtn.pack(side = LEFT, padx = 5, pady = 3) self.nextBtn = Button(self.ctrPanel, text='Next >>', width = 10, command = self.nextImage) @@ -98,6 +118,7 @@ def __init__(self, master): self.goBtn = Button(self.ctrPanel, text = 'Go', command = self.gotoImage) self.goBtn.pack(side = LEFT) + # example pannel for illustration self.egPanel = Frame(self.frame, border = 10) self.egPanel.grid(row = 1, column = 0, rowspan = 5, sticky = N) @@ -131,9 +152,12 @@ def loadDir(self, dbg = False): ## return # get image list self.imageDir = os.path.join(r'./Images', '%03d' %(self.category)) - self.imageList = glob.glob(os.path.join(self.imageDir, '*.JPEG')) + #print self.imageDir + #print self.category + self.imageList = glob.glob(os.path.join(self.imageDir, '*.JPG')) + #print self.imageList if len(self.imageList) == 0: - print 'No .JPEG images found in the specified dir!' + print 'No .JPG images found in the specified dir!' return # default to the 1st image in the collection @@ -146,10 +170,12 @@ def loadDir(self, dbg = False): os.mkdir(self.outDir) # load example bboxes - self.egDir = os.path.join(r'./Examples', '%03d' %(self.category)) + #self.egDir = os.path.join(r'./Examples', '%03d' %(self.category)) + self.egDir = os.path.join(r'./Examples/demo') + print os.path.exists(self.egDir) if not os.path.exists(self.egDir): return - filelist = glob.glob(os.path.join(self.egDir, '*.JPEG')) + filelist = glob.glob(os.path.join(self.egDir, '*.JPG')) self.tmp = [] self.egList = [] random.shuffle(filelist) @@ -187,15 +213,18 @@ def loadImage(self): if i == 0: bbox_cnt = int(line.strip()) continue - tmp = [int(t.strip()) for t in line.split()] -## print tmp + # tmp = [int(t.strip()) for t in line.split()] + tmp = line.split() + #print tmp self.bboxList.append(tuple(tmp)) - tmpId = self.mainPanel.create_rectangle(tmp[0], tmp[1], \ - tmp[2], tmp[3], \ + tmpId = self.mainPanel.create_rectangle(int(tmp[0]), int(tmp[1]), \ + int(tmp[2]), int(tmp[3]), \ width = 2, \ outline = COLORS[(len(self.bboxList)-1) % len(COLORS)]) + # print tmpId self.bboxIdList.append(tmpId) - self.listbox.insert(END, '(%d, %d) -> (%d, %d)' %(tmp[0], tmp[1], tmp[2], tmp[3])) + self.listbox.insert(END, '%s : (%d, %d) -> (%d, %d)' %(tmp[4],int(tmp[0]), int(tmp[1]), \ + int(tmp[2]), int(tmp[3]))) self.listbox.itemconfig(len(self.bboxIdList) - 1, fg = COLORS[(len(self.bboxIdList) - 1) % len(COLORS)]) def saveImage(self): @@ -212,10 +241,10 @@ def mouseClick(self, event): else: x1, x2 = min(self.STATE['x'], event.x), max(self.STATE['x'], event.x) y1, y2 = min(self.STATE['y'], event.y), max(self.STATE['y'], event.y) - self.bboxList.append((x1, y1, x2, y2)) + self.bboxList.append((x1, y1, x2, y2, self.currentLabelclass)) self.bboxIdList.append(self.bboxId) self.bboxId = None - self.listbox.insert(END, '(%d, %d) -> (%d, %d)' %(x1, y1, x2, y2)) + self.listbox.insert(END, '%s : (%d, %d) -> (%d, %d)' %(self.currentLabelclass,x1, y1, x2, y2)) self.listbox.itemconfig(len(self.bboxIdList) - 1, fg = COLORS[(len(self.bboxIdList) - 1) % len(COLORS)]) self.STATE['click'] = 1 - self.STATE['click'] @@ -279,6 +308,10 @@ def gotoImage(self): self.cur = idx self.loadImage() + def setClass(self): + self.currentLabelclass = self.classcandidate.get() + print 'set label class to :',self.currentLabelclass + ## def setImage(self, imagepath = r'test2.png'): ## self.img = Image.open(imagepath) ## self.tkimg = ImageTk.PhotoImage(self.img) From 2db49c69ca48e673698367d562b6c8dbbd6f86da Mon Sep 17 00:00:00 2001 From: tgandor Date: Tue, 9 Jan 2018 15:14:36 +0100 Subject: [PATCH 2/5] compatibility with python3 --- main.py | 30 ++++++++++++++++++------------ requirements.txt | 1 + 2 files changed, 19 insertions(+), 12 deletions(-) create mode 100644 requirements.txt diff --git a/main.py b/main.py index e19a0888..bf82909b 100644 --- a/main.py +++ b/main.py @@ -1,14 +1,21 @@ -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ # Name: Object bounding box label tool # Purpose: Label object bboxes for ImageNet Detection data # Author: Qiushi # Created: 06/06/2014 # -#------------------------------------------------------------------------------- -from __future__ import division -from Tkinter import * -import tkMessageBox +# ------------------------------------------------------------------------------ + +from __future__ import division, print_function + +try: + from Tkinter import * + import tkMessageBox +except ImportError: + from tkinter import * + from tkinter import messagebox as tkMessageBox + from PIL import Image, ImageTk import os import glob @@ -130,10 +137,10 @@ def loadDir(self, dbg = False): ## tkMessageBox.showerror("Error!", message = "The specified dir doesn't exist!") ## return # get image list - self.imageDir = os.path.join(r'./Images', '%03d' %(self.category)) + self.imageDir = os.path.join(r'./Images', '%03d' % (self.category)) self.imageList = glob.glob(os.path.join(self.imageDir, '*.JPEG')) if len(self.imageList) == 0: - print 'No .JPEG images found in the specified dir!' + print('No .JPEG images found in the specified dir <%s>!' % self.imageDir) return # default to the 1st image in the collection @@ -141,7 +148,7 @@ def loadDir(self, dbg = False): self.total = len(self.imageList) # set up output dir - self.outDir = os.path.join(r'./Labels', '%03d' %(self.category)) + self.outDir = os.path.join(r'./Labels', '%03d' % (self.category)) if not os.path.exists(self.outDir): os.mkdir(self.outDir) @@ -164,7 +171,7 @@ def loadDir(self, dbg = False): self.egLabels[i].config(image = self.egList[-1], width = SIZE[0], height = SIZE[1]) self.loadImage() - print '%d images loaded from %s' %(self.total, s) + print('%d images loaded from %s' % (self.total, s)) def loadImage(self): # load image @@ -173,7 +180,7 @@ def loadImage(self): self.tkimg = ImageTk.PhotoImage(self.img) self.mainPanel.config(width = max(self.tkimg.width(), 400), height = max(self.tkimg.height(), 400)) self.mainPanel.create_image(0, 0, image = self.tkimg, anchor=NW) - self.progLabel.config(text = "%04d/%04d" %(self.cur, self.total)) + self.progLabel.config(text="%04d/%04d" % (self.cur, self.total)) # load labels self.clearBBox() @@ -203,8 +210,7 @@ def saveImage(self): f.write('%d\n' %len(self.bboxList)) for bbox in self.bboxList: f.write(' '.join(map(str, bbox)) + '\n') - print 'Image No. %d saved' %(self.cur) - + print('Image No. %d saved' % (self.cur)) def mouseClick(self, event): if self.STATE['click'] == 0: diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..7e2fba5e --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +Pillow From da3eca3aedbf3382d62967aa4d4627ac837c9b64 Mon Sep 17 00:00:00 2001 From: tgandor Date: Mon, 15 Jan 2018 12:00:13 +0100 Subject: [PATCH 3/5] added support for other formats of images --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index bf82909b..36496c7f 100644 --- a/main.py +++ b/main.py @@ -138,7 +138,7 @@ def loadDir(self, dbg = False): ## return # get image list self.imageDir = os.path.join(r'./Images', '%03d' % (self.category)) - self.imageList = glob.glob(os.path.join(self.imageDir, '*.JPEG')) + self.imageList = glob.glob(os.path.join(self.imageDir, '*.*')) if len(self.imageList) == 0: print('No .JPEG images found in the specified dir <%s>!' % self.imageDir) return From deb943f7d938fc637590f6a4e36468785bb5d9e0 Mon Sep 17 00:00:00 2001 From: tgandor Date: Sun, 28 Jan 2018 19:37:49 +0100 Subject: [PATCH 4/5] format cleanup and restoring Py3 compatiblity --- main.py | 179 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 88 insertions(+), 91 deletions(-) diff --git a/main.py b/main.py index 6be28f1c..b3f0bc11 100644 --- a/main.py +++ b/main.py @@ -12,12 +12,13 @@ try: from Tkinter import * import tkMessageBox + import ttk except ImportError: from tkinter import * from tkinter import messagebox as tkMessageBox + from tkinter import ttk from PIL import Image, ImageTk -import ttk import os import glob import random @@ -27,6 +28,7 @@ # image sizes for the examples SIZE = 256, 256 + class LabelTool(): def __init__(self, master): # set up the main frame @@ -34,11 +36,11 @@ def __init__(self, master): self.parent.title("LabelTool") self.frame = Frame(self.parent) self.frame.pack(fill=BOTH, expand=1) - self.parent.resizable(width = FALSE, height = FALSE) + self.parent.resizable(width=FALSE, height=FALSE) # initialize global state self.imageDir = '' - self.imageList= [] + self.imageList = [] self.egDir = '' self.egList = [] self.outDir = '' @@ -66,12 +68,12 @@ def __init__(self, master): # ----------------- GUI stuff --------------------- # dir entry & load - self.label = Label(self.frame, text = "Image Dir:") - self.label.grid(row = 0, column = 0, sticky = E) + self.label = Label(self.frame, text="Image Dir:") + self.label.grid(row=0, column=0, sticky=E) self.entry = Entry(self.frame) - self.entry.grid(row = 0, column = 1, sticky = W+E) - self.ldBtn = Button(self.frame, text = "Load", command = self.loadDir) - self.ldBtn.grid(row = 0, column = 2,sticky = W+E) + self.entry.grid(row=0, column=1, sticky=W + E) + self.ldBtn = Button(self.frame, text="Load", command=self.loadDir) + self.ldBtn.grid(row=0, column=2, sticky=W + E) # main panel for labeling self.mainPanel = Canvas(self.frame, cursor='tcross') @@ -79,84 +81,84 @@ def __init__(self, master): self.mainPanel.bind("", self.mouseMove) self.parent.bind("", self.cancelBBox) # press to cancel current bbox self.parent.bind("s", self.cancelBBox) - self.parent.bind("a", self.prevImage) # press 'a' to go backforward - self.parent.bind("d", self.nextImage) # press 'd' to go forward - self.mainPanel.grid(row = 1, column = 1, rowspan = 4, sticky = W+N) + self.parent.bind("a", self.prevImage) # press 'a' to go backforward + self.parent.bind("d", self.nextImage) # press 'd' to go forward + self.mainPanel.grid(row=1, column=1, rowspan=4, sticky=W + N) # choose class self.classname = StringVar() - self.classcandidate = ttk.Combobox(self.frame,state='readonly',textvariable=self.classname) - self.classcandidate.grid(row=1,column=2) + self.classcandidate = ttk.Combobox(self.frame, state='readonly', textvariable=self.classname) + self.classcandidate.grid(row=1, column=2) if os.path.exists(self.classcandidate_filename): - with open(self.classcandidate_filename) as cf: - for line in cf.readlines(): - # print line - self.cla_can_temp.append(line.strip('\n')) - #print self.cla_can_temp + with open(self.classcandidate_filename) as cf: + for line in cf.readlines(): + # print line + self.cla_can_temp.append(line.strip('\n')) + # print self.cla_can_temp self.classcandidate['values'] = self.cla_can_temp self.classcandidate.current(0) - self.currentLabelclass = self.classcandidate.get() #init - self.btnclass = Button(self.frame, text = 'ComfirmClass', command = self.setClass) - self.btnclass.grid(row=2,column=2,sticky = W+E) + self.currentLabelclass = self.classcandidate.get() # init + self.btnclass = Button(self.frame, text='ComfirmClass', command=self.setClass) + self.btnclass.grid(row=2, column=2, sticky=W + E) # showing bbox info & delete bbox - self.lb1 = Label(self.frame, text = 'Bounding boxes:') - self.lb1.grid(row = 3, column = 2, sticky = W+N) - self.listbox = Listbox(self.frame, width = 22, height = 12) - self.listbox.grid(row = 4, column = 2, sticky = N+S) - self.btnDel = Button(self.frame, text = 'Delete', command = self.delBBox) - self.btnDel.grid(row = 5, column = 2, sticky = W+E+N) - self.btnClear = Button(self.frame, text = 'ClearAll', command = self.clearBBox) - self.btnClear.grid(row = 6, column = 2, sticky = W+E+N) + self.lb1 = Label(self.frame, text='Bounding boxes:') + self.lb1.grid(row=3, column=2, sticky=W + N) + self.listbox = Listbox(self.frame, width=22, height=12) + self.listbox.grid(row=4, column=2, sticky=N + S) + self.btnDel = Button(self.frame, text='Delete', command=self.delBBox) + self.btnDel.grid(row=5, column=2, sticky=W + E + N) + self.btnClear = Button(self.frame, text='ClearAll', command=self.clearBBox) + self.btnClear.grid(row=6, column=2, sticky=W + E + N) # control panel for image navigation self.ctrPanel = Frame(self.frame) - self.ctrPanel.grid(row = 7, column = 1, columnspan = 2, sticky = W+E) - self.prevBtn = Button(self.ctrPanel, text='<< Prev', width = 10, command = self.prevImage) - self.prevBtn.pack(side = LEFT, padx = 5, pady = 3) - self.nextBtn = Button(self.ctrPanel, text='Next >>', width = 10, command = self.nextImage) - self.nextBtn.pack(side = LEFT, padx = 5, pady = 3) - self.progLabel = Label(self.ctrPanel, text = "Progress: / ") - self.progLabel.pack(side = LEFT, padx = 5) - self.tmpLabel = Label(self.ctrPanel, text = "Go to Image No.") - self.tmpLabel.pack(side = LEFT, padx = 5) - self.idxEntry = Entry(self.ctrPanel, width = 5) - self.idxEntry.pack(side = LEFT) - self.goBtn = Button(self.ctrPanel, text = 'Go', command = self.gotoImage) - self.goBtn.pack(side = LEFT) - + self.ctrPanel.grid(row=7, column=1, columnspan=2, sticky=W + E) + self.prevBtn = Button(self.ctrPanel, text='<< Prev', width=10, command=self.prevImage) + self.prevBtn.pack(side=LEFT, padx=5, pady=3) + self.nextBtn = Button(self.ctrPanel, text='Next >>', width=10, command=self.nextImage) + self.nextBtn.pack(side=LEFT, padx=5, pady=3) + self.progLabel = Label(self.ctrPanel, text="Progress: / ") + self.progLabel.pack(side=LEFT, padx=5) + self.tmpLabel = Label(self.ctrPanel, text="Go to Image No.") + self.tmpLabel.pack(side=LEFT, padx=5) + self.idxEntry = Entry(self.ctrPanel, width=5) + self.idxEntry.pack(side=LEFT) + self.goBtn = Button(self.ctrPanel, text='Go', command=self.gotoImage) + self.goBtn.pack(side=LEFT) # example pannel for illustration - self.egPanel = Frame(self.frame, border = 10) - self.egPanel.grid(row = 1, column = 0, rowspan = 5, sticky = N) - self.tmpLabel2 = Label(self.egPanel, text = "Examples:") - self.tmpLabel2.pack(side = TOP, pady = 5) + self.egPanel = Frame(self.frame, border=10) + self.egPanel.grid(row=1, column=0, rowspan=5, sticky=N) + self.tmpLabel2 = Label(self.egPanel, text="Examples:") + self.tmpLabel2.pack(side=TOP, pady=5) self.egLabels = [] for i in range(3): self.egLabels.append(Label(self.egPanel)) - self.egLabels[-1].pack(side = TOP) + self.egLabels[-1].pack(side=TOP) # display mouse position self.disp = Label(self.ctrPanel, text='') - self.disp.pack(side = RIGHT) + self.disp.pack(side=RIGHT) - self.frame.columnconfigure(1, weight = 1) - self.frame.rowconfigure(4, weight = 1) + self.frame.columnconfigure(1, weight=1) + self.frame.rowconfigure(4, weight=1) # for debugging -## self.setImage() -## self.loadDir() - def loadDir(self, dbg = False): + ## self.setImage() + ## self.loadDir() + + def loadDir(self, dbg=False): if not dbg: s = self.entry.get() self.parent.focus() self.category = int(s) else: s = r'D:\workspace\python\labelGUI' -## if not os.path.isdir(s): -## tkMessageBox.showerror("Error!", message = "The specified dir doesn't exist!") -## return + ## if not os.path.isdir(s): + ## tkMessageBox.showerror("Error!", message = "The specified dir doesn't exist!") + ## return self.imageList = glob.glob(os.path.join(self.imageDir, '*.*')) if len(self.imageList) == 0: print('No .JPEG images found in the specified dir <%s>!' % self.imageDir) @@ -166,15 +168,15 @@ def loadDir(self, dbg = False): self.cur = 1 self.total = len(self.imageList) - # set up output dir + # set up output dir self.outDir = os.path.join(r'./Labels', '%03d' % (self.category)) if not os.path.exists(self.outDir): os.mkdir(self.outDir) # load example bboxes - #self.egDir = os.path.join(r'./Examples', '%03d' %(self.category)) + # self.egDir = os.path.join(r'./Examples', '%03d' %(self.category)) self.egDir = os.path.join(r'./Examples/demo') - print os.path.exists(self.egDir) + print(os.path.exists(self.egDir)) if not os.path.exists(self.egDir): return filelist = glob.glob(os.path.join(self.egDir, '*.JPG')) @@ -189,7 +191,7 @@ def loadDir(self, dbg = False): new_size = int(r * im.size[0]), int(r * im.size[1]) self.tmp.append(im.resize(new_size, Image.ANTIALIAS)) self.egList.append(ImageTk.PhotoImage(self.tmp[-1])) - self.egLabels[i].config(image = self.egList[-1], width = SIZE[0], height = SIZE[1]) + self.egLabels[i].config(image=self.egList[-1], width=SIZE[0], height=SIZE[1]) self.loadImage() print('%d images loaded from %s' % (self.total, s)) @@ -199,8 +201,8 @@ def loadImage(self): imagepath = self.imageList[self.cur - 1] self.img = Image.open(imagepath) self.tkimg = ImageTk.PhotoImage(self.img) - self.mainPanel.config(width = max(self.tkimg.width(), 400), height = max(self.tkimg.height(), 400)) - self.mainPanel.create_image(0, 0, image = self.tkimg, anchor=NW) + self.mainPanel.config(width=max(self.tkimg.width(), 400), height=max(self.tkimg.height(), 400)) + self.mainPanel.create_image(0, 0, image=self.tkimg, anchor=NW) self.progLabel.config(text="%04d/%04d" % (self.cur, self.total)) # load labels @@ -217,21 +219,22 @@ def loadImage(self): continue # tmp = [int(t.strip()) for t in line.split()] tmp = line.split() - #print tmp + # print tmp self.bboxList.append(tuple(tmp)) tmpId = self.mainPanel.create_rectangle(int(tmp[0]), int(tmp[1]), \ int(tmp[2]), int(tmp[3]), \ - width = 2, \ - outline = COLORS[(len(self.bboxList)-1) % len(COLORS)]) + width=2, \ + outline=COLORS[(len(self.bboxList) - 1) % len(COLORS)]) # print tmpId self.bboxIdList.append(tmpId) - self.listbox.insert(END, '%s : (%d, %d) -> (%d, %d)' %(tmp[4],int(tmp[0]), int(tmp[1]), \ - int(tmp[2]), int(tmp[3]))) - self.listbox.itemconfig(len(self.bboxIdList) - 1, fg = COLORS[(len(self.bboxIdList) - 1) % len(COLORS)]) + self.listbox.insert(END, '%s : (%d, %d) -> (%d, %d)' % (tmp[4], int(tmp[0]), int(tmp[1]), \ + int(tmp[2]), int(tmp[3]))) + self.listbox.itemconfig(len(self.bboxIdList) - 1, + fg=COLORS[(len(self.bboxIdList) - 1) % len(COLORS)]) def saveImage(self): with open(self.labelfilename, 'w') as f: - f.write('%d\n' %len(self.bboxList)) + f.write('%d\n' % len(self.bboxList)) for bbox in self.bboxList: f.write(' '.join(map(str, bbox)) + '\n') print('Image No. %d saved' % (self.cur)) @@ -245,26 +248,26 @@ def mouseClick(self, event): self.bboxList.append((x1, y1, x2, y2, self.currentLabelclass)) self.bboxIdList.append(self.bboxId) self.bboxId = None - self.listbox.insert(END, '%s : (%d, %d) -> (%d, %d)' %(self.currentLabelclass,x1, y1, x2, y2)) - self.listbox.itemconfig(len(self.bboxIdList) - 1, fg = COLORS[(len(self.bboxIdList) - 1) % len(COLORS)]) + self.listbox.insert(END, '%s : (%d, %d) -> (%d, %d)' % (self.currentLabelclass, x1, y1, x2, y2)) + self.listbox.itemconfig(len(self.bboxIdList) - 1, fg=COLORS[(len(self.bboxIdList) - 1) % len(COLORS)]) self.STATE['click'] = 1 - self.STATE['click'] def mouseMove(self, event): - self.disp.config(text = 'x: %d, y: %d' %(event.x, event.y)) + self.disp.config(text='x: %d, y: %d' % (event.x, event.y)) if self.tkimg: if self.hl: self.mainPanel.delete(self.hl) - self.hl = self.mainPanel.create_line(0, event.y, self.tkimg.width(), event.y, width = 2) + self.hl = self.mainPanel.create_line(0, event.y, self.tkimg.width(), event.y, width=2) if self.vl: self.mainPanel.delete(self.vl) - self.vl = self.mainPanel.create_line(event.x, 0, event.x, self.tkimg.height(), width = 2) + self.vl = self.mainPanel.create_line(event.x, 0, event.x, self.tkimg.height(), width=2) if 1 == self.STATE['click']: if self.bboxId: self.mainPanel.delete(self.bboxId) self.bboxId = self.mainPanel.create_rectangle(self.STATE['x'], self.STATE['y'], \ - event.x, event.y, \ - width = 2, \ - outline = COLORS[len(self.bboxList) % len(COLORS)]) + event.x, event.y, \ + width=2, \ + outline=COLORS[len(self.bboxList) % len(COLORS)]) def cancelBBox(self, event): if 1 == self.STATE['click']: @@ -275,7 +278,7 @@ def cancelBBox(self, event): def delBBox(self): sel = self.listbox.curselection() - if len(sel) != 1 : + if len(sel) != 1: return idx = int(sel[0]) self.mainPanel.delete(self.bboxIdList[idx]) @@ -290,13 +293,13 @@ def clearBBox(self): self.bboxIdList = [] self.bboxList = [] - def prevImage(self, event = None): + def prevImage(self, event=None): self.saveImage() if self.cur > 1: self.cur -= 1 self.loadImage() - def nextImage(self, event = None): + def nextImage(self, event=None): self.saveImage() if self.cur < self.total: self.cur += 1 @@ -304,24 +307,18 @@ def nextImage(self, event = None): def gotoImage(self): idx = int(self.idxEntry.get()) - if 1 <= idx and idx <= self.total: + if 1 <= idx <= self.total: self.saveImage() self.cur = idx self.loadImage() def setClass(self): - self.currentLabelclass = self.classcandidate.get() - print 'set label class to :',self.currentLabelclass + self.currentLabelclass = self.classcandidate.get() + print('set label class to :', self.currentLabelclass) -## def setImage(self, imagepath = r'test2.png'): -## self.img = Image.open(imagepath) -## self.tkimg = ImageTk.PhotoImage(self.img) -## self.mainPanel.config(width = self.tkimg.width()) -## self.mainPanel.config(height = self.tkimg.height()) -## self.mainPanel.create_image(0, 0, image = self.tkimg, anchor=NW) if __name__ == '__main__': root = Tk() tool = LabelTool(root) - root.resizable(width = True, height = True) + root.resizable(width=True, height=True) root.mainloop() From 4a85e19c8b90fefbc711a4a851ceac966aaeea06 Mon Sep 17 00:00:00 2001 From: tgandor Date: Wed, 21 Feb 2018 20:41:04 +0100 Subject: [PATCH 5/5] moving toward sensible image loading / output directories --- class.txt | 4 +- main.py | 132 +++++++++++++++++++++++++++++------------------------- 2 files changed, 75 insertions(+), 61 deletions(-) diff --git a/class.txt b/class.txt index a931f210..0e678053 100644 --- a/class.txt +++ b/class.txt @@ -1,3 +1,5 @@ + +person dog cat -baby \ No newline at end of file +baby diff --git a/main.py b/main.py index b3f0bc11..40e4a391 100644 --- a/main.py +++ b/main.py @@ -9,6 +9,8 @@ from __future__ import division, print_function +import itertools + try: from Tkinter import * import tkMessageBox @@ -39,8 +41,8 @@ def __init__(self, master): self.parent.resizable(width=FALSE, height=FALSE) # initialize global state - self.imageDir = '' - self.imageList = [] + self.image_dir = 'Images' + self.image_list = [] self.egDir = '' self.egList = [] self.outDir = '' @@ -50,14 +52,12 @@ def __init__(self, master): self.imagename = '' self.labelfilename = '' self.tkimg = None - self.currentLabelclass = '' + self.current_label_class = '' self.cla_can_temp = [] self.classcandidate_filename = 'class.txt' # initialize mouse state - self.STATE = {} - self.STATE['click'] = 0 - self.STATE['x'], self.STATE['y'] = 0, 0 + self.STATE = {'click': 0, 'x': 0, 'y': 0} # reference to bbox self.bboxIdList = [] @@ -71,18 +71,19 @@ def __init__(self, master): self.label = Label(self.frame, text="Image Dir:") self.label.grid(row=0, column=0, sticky=E) self.entry = Entry(self.frame) + self.entry.insert(0, self.image_dir) self.entry.grid(row=0, column=1, sticky=W + E) - self.ldBtn = Button(self.frame, text="Load", command=self.loadDir) + self.ldBtn = Button(self.frame, text="Load", command=self.load_dir) self.ldBtn.grid(row=0, column=2, sticky=W + E) # main panel for labeling self.mainPanel = Canvas(self.frame, cursor='tcross') self.mainPanel.bind("", self.mouseClick) self.mainPanel.bind("", self.mouseMove) - self.parent.bind("", self.cancelBBox) # press to cancel current bbox - self.parent.bind("s", self.cancelBBox) - self.parent.bind("a", self.prevImage) # press 'a' to go backforward - self.parent.bind("d", self.nextImage) # press 'd' to go forward + self.parent.bind("", self.cancelBBox) # press to cancel current bbox + self.mainPanel.bind("s", self.cancelBBox) + self.mainPanel.bind("a", self.prevImage) # press 'a' to go backward + self.mainPanel.bind("d", self.nextImage) # press 'd' to go forward self.mainPanel.grid(row=1, column=1, rowspan=4, sticky=W + N) # choose class @@ -97,8 +98,8 @@ def __init__(self, master): # print self.cla_can_temp self.classcandidate['values'] = self.cla_can_temp self.classcandidate.current(0) - self.currentLabelclass = self.classcandidate.get() # init - self.btnclass = Button(self.frame, text='ComfirmClass', command=self.setClass) + self.current_label_class = self.classcandidate.get() # init + self.btnclass = Button(self.frame, text='Confirm Class', command=self.set_class) self.btnclass.grid(row=2, column=2, sticky=W + E) # showing bbox info & delete bbox @@ -108,7 +109,7 @@ def __init__(self, master): self.listbox.grid(row=4, column=2, sticky=N + S) self.btnDel = Button(self.frame, text='Delete', command=self.delBBox) self.btnDel.grid(row=5, column=2, sticky=W + E + N) - self.btnClear = Button(self.frame, text='ClearAll', command=self.clearBBox) + self.btnClear = Button(self.frame, text='Clear All', command=self.clearBBox) self.btnClear.grid(row=6, column=2, sticky=W + E + N) # control panel for image navigation @@ -144,46 +145,37 @@ def __init__(self, master): self.frame.columnconfigure(1, weight=1) self.frame.rowconfigure(4, weight=1) - # for debugging + def load_dir(self): + value = self.entry.get() + if not os.path.isdir(value): + print('{} is not a directory'.format(value)) + return - ## self.setImage() - ## self.loadDir() + self.image_dir = value - def loadDir(self, dbg=False): - if not dbg: - s = self.entry.get() - self.parent.focus() - self.category = int(s) - else: - s = r'D:\workspace\python\labelGUI' - ## if not os.path.isdir(s): - ## tkMessageBox.showerror("Error!", message = "The specified dir doesn't exist!") - ## return - self.imageList = glob.glob(os.path.join(self.imageDir, '*.*')) - if len(self.imageList) == 0: - print('No .JPEG images found in the specified dir <%s>!' % self.imageDir) + self.image_list = glob_images(self.image_dir) + if len(self.image_list) == 0: + print('No images found in the specified dir <%s>!' % self.image_dir) return # default to the 1st image in the collection self.cur = 1 - self.total = len(self.imageList) + self.total = len(self.image_list) # set up output dir - self.outDir = os.path.join(r'./Labels', '%03d' % (self.category)) + self.outDir = './Labels' if not os.path.exists(self.outDir): os.mkdir(self.outDir) # load example bboxes - # self.egDir = os.path.join(r'./Examples', '%03d' %(self.category)) - self.egDir = os.path.join(r'./Examples/demo') - print(os.path.exists(self.egDir)) - if not os.path.exists(self.egDir): - return - filelist = glob.glob(os.path.join(self.egDir, '*.JPG')) + self.egDir = os.path.join(os.path.dirname(__file__), 'Examples/demo') + assert os.path.exists(self.egDir) + + file_list = glob.glob(os.path.join(self.egDir, '*.JPG')) self.tmp = [] self.egList = [] - random.shuffle(filelist) - for (i, f) in enumerate(filelist): + random.shuffle(file_list) + for (i, f) in enumerate(file_list): if i == 3: break im = Image.open(f) @@ -194,11 +186,11 @@ def loadDir(self, dbg=False): self.egLabels[i].config(image=self.egList[-1], width=SIZE[0], height=SIZE[1]) self.loadImage() - print('%d images loaded from %s' % (self.total, s)) + print('%d images loaded from %s' % (self.total, self.image_dir)) def loadImage(self): # load image - imagepath = self.imageList[self.cur - 1] + imagepath = self.image_list[self.cur - 1] self.img = Image.open(imagepath) self.tkimg = ImageTk.PhotoImage(self.img) self.mainPanel.config(width=max(self.tkimg.width(), 400), height=max(self.tkimg.height(), 400)) @@ -207,27 +199,25 @@ def loadImage(self): # load labels self.clearBBox() - self.imagename = os.path.split(imagepath)[-1].split('.')[0] + self.imagename = extract_basename(imagepath) labelname = self.imagename + '.txt' self.labelfilename = os.path.join(self.outDir, labelname) - bbox_cnt = 0 if os.path.exists(self.labelfilename): with open(self.labelfilename) as f: for (i, line) in enumerate(f): if i == 0: - bbox_cnt = int(line.strip()) + # bbox_cnt = int(line.strip()) continue - # tmp = [int(t.strip()) for t in line.split()] + tmp = line.split() - # print tmp self.bboxList.append(tuple(tmp)) - tmpId = self.mainPanel.create_rectangle(int(tmp[0]), int(tmp[1]), \ - int(tmp[2]), int(tmp[3]), \ - width=2, \ + tmpId = self.mainPanel.create_rectangle(int(tmp[0]), int(tmp[1]), + int(tmp[2]), int(tmp[3]), + width=2, outline=COLORS[(len(self.bboxList) - 1) % len(COLORS)]) # print tmpId self.bboxIdList.append(tmpId) - self.listbox.insert(END, '%s : (%d, %d) -> (%d, %d)' % (tmp[4], int(tmp[0]), int(tmp[1]), \ + self.listbox.insert(END, '%s : (%d, %d) -> (%d, %d)' % (tmp[4], int(tmp[0]), int(tmp[1]), int(tmp[2]), int(tmp[3]))) self.listbox.itemconfig(len(self.bboxIdList) - 1, fg=COLORS[(len(self.bboxIdList) - 1) % len(COLORS)]) @@ -237,7 +227,7 @@ def saveImage(self): f.write('%d\n' % len(self.bboxList)) for bbox in self.bboxList: f.write(' '.join(map(str, bbox)) + '\n') - print('Image No. %d saved' % (self.cur)) + print('Labels for image no. {} ({}) saved'.format(self.cur, self.imagename)) def mouseClick(self, event): if self.STATE['click'] == 0: @@ -245,10 +235,10 @@ def mouseClick(self, event): else: x1, x2 = min(self.STATE['x'], event.x), max(self.STATE['x'], event.x) y1, y2 = min(self.STATE['y'], event.y), max(self.STATE['y'], event.y) - self.bboxList.append((x1, y1, x2, y2, self.currentLabelclass)) + self.bboxList.append((x1, y1, x2, y2, self.current_label_class)) self.bboxIdList.append(self.bboxId) self.bboxId = None - self.listbox.insert(END, '%s : (%d, %d) -> (%d, %d)' % (self.currentLabelclass, x1, y1, x2, y2)) + self.listbox.insert(END, '%s : (%d, %d) -> (%d, %d)' % (self.current_label_class, x1, y1, x2, y2)) self.listbox.itemconfig(len(self.bboxIdList) - 1, fg=COLORS[(len(self.bboxIdList) - 1) % len(COLORS)]) self.STATE['click'] = 1 - self.STATE['click'] @@ -264,9 +254,9 @@ def mouseMove(self, event): if 1 == self.STATE['click']: if self.bboxId: self.mainPanel.delete(self.bboxId) - self.bboxId = self.mainPanel.create_rectangle(self.STATE['x'], self.STATE['y'], \ - event.x, event.y, \ - width=2, \ + self.bboxId = self.mainPanel.create_rectangle(self.STATE['x'], self.STATE['y'], + event.x, event.y, + width=2, outline=COLORS[len(self.bboxList) % len(COLORS)]) def cancelBBox(self, event): @@ -312,9 +302,31 @@ def gotoImage(self): self.cur = idx self.loadImage() - def setClass(self): - self.currentLabelclass = self.classcandidate.get() - print('set label class to :', self.currentLabelclass) + def set_class(self): + self.current_label_class = self.classcandidate.get() + print("set label class to : '{}'".format(self.current_label_class)) + + +# utils + +IMAGE_EXTENSIONS = [ + '*.jpg', + '*.jpeg', + '*.png', + '*.gif', + '*.bmp' +] + + +def glob_images(directory): + return list( + itertools.chain(*[glob.glob(os.path.join(directory, extension)) + for extension in IMAGE_EXTENSIONS]) + ) + + +def extract_basename(imagepath): + return os.path.split(imagepath)[-1].split('.')[0] if __name__ == '__main__':