From 0aab1ce11195c1a05d870f413ed2aedf2e37c08b Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 6 Dec 2018 14:19:21 -0700 Subject: [PATCH] Annotation Tool --- pageCropAnnotator.py | 333 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 333 insertions(+) create mode 100644 pageCropAnnotator.py diff --git a/pageCropAnnotator.py b/pageCropAnnotator.py new file mode 100644 index 0000000..893c882 --- /dev/null +++ b/pageCropAnnotator.py @@ -0,0 +1,333 @@ +#!/usr/bin/python + +#This takes a list of image files and acts as a tool to mark the crop region of the page + +import re +import xml.etree.ElementTree as ET +import os +import sys +from StringIO import StringIO +import cv2 + + + +def showControls(): + print(' -----------------------------------------------') + print('| CONTROLS: |') + print('| * set new corner (base on loc): left-click |') + print('| * set new seam corner (two-page): middle-click|') + print('| * confirm corners: enter |') + print('| * mark page as abnormal: a |') + print('| * undo: backspace |') + print('| * start previous page over: backspace(+)|') + #print('| * start current page over: delete |') + print('| * exit: esc |') + print('| |') + print(' -----------------------------------------------') + + + +lastDidList=[] +tl=(-1,-1) +tr=(-1,-1) +bl=(-1,-1) +br=(-1,-1) +tm=(-1,-1) +bm=(-1,-1) + +image=None +orig=None + +abnorm=False + +def draw(): + global image,tl,tr,bl,br,tm,bm,abnorm + if tm[0]>=0 and bm[0]>=0: + cv2.line(image, tm, bm, (0,255,0), 2) + if tm[0]>=0 and tl[0]>=0: + cv2.line(image, tm, tl, (0,255,0), 2) + if tm[0]>=0 and tr[0]>=0: + cv2.line(image, tm, tr, (0,255,0), 2) + if bm[0]>=0 and bl[0]>=0: + cv2.line(image, bm, bl, (0,255,0), 2) + if bm[0]>=0 and br[0]>=0: + cv2.line(image, bm, br, (0,255,0), 2) + if tl[0]>=0 and tr[0]>=0: + cv2.line(image, tl, tr, (255,0,0), 2) + if br[0]>=0 and tr[0]>=0: + cv2.line(image, br, tr, (255,0,0), 2) + if br[0]>=0 and bl[0]>=0: + cv2.line(image, br, bl, (255,0,0), 2) + if tl[0]>=0 and bl[0]>=0: + cv2.line(image, tl, bl, (255,0,0), 2) + + + if tl[0]>=0: + image[tl[1],tl[0]]=(0,0,255) + cv2.circle(image, tl, 2, (0,0,200), 1) + cv2.circle(image, tl, 5, (0,0,200), 2) + if tr[0]>=0: + image[tr[1],tr[0]]=(0,0,255) + cv2.circle(image, tr, 2, (0,0,200), 1) + cv2.circle(image, tr, 5, (0,0,200), 2) + if bl[0]>=0: + image[bl[1],bl[0]]=(0,0,255) + cv2.circle(image, bl, 2, (0,0,200), 1) + cv2.circle(image, bl, 5, (0,0,200), 2) + if br[0]>=0: + image[br[1],br[0]]=(0,0,255) + cv2.circle(image, br, 2, (0,0,200), 1) + cv2.circle(image, br, 5, (0,0,200), 2) + if tm[0]>=0: + image[tm[1],tm[0]]=(0,0,255) + cv2.circle(image, tm, 2, (0,100,200), 1) + cv2.circle(image, tm, 5, (0,100,200), 2) + if bm[0]>=0: + image[bm[1],bm[0]]=(0,0,255) + cv2.circle(image, bm, 2, (0,100,200), 1) + cv2.circle(image, bm, 5, (0,100,200), 2) + + if abnorm: + cv2.putText(image, 'ABNORMAL', (image.shape[1]/2,image.shape[0]/2), cv2.FONT_HERSHEY_PLAIN, 2, (0,0,255)) + + + cv2.imshow("image", image) + +bimage=None +def clicker(event, x, y, flags, param): + # grab references to the global variables + global image,tl,tr,bl,br,tm,bm,lastDidList,orig + + """if event == cv2.EVENT_LBUTTONDOWN: + if len(segPts)>0: + #change last boundary + image=bimage.copy() + segPts[-1]=x + ll=max(0,segPts[-1]-1) + rr=min(image.shape[1], segPts[-1]+1) + image[:,ll:rr,0] = color[(colorIdx+len(color)-1)%len(color)][0] * image[:,ll:rr,0] + image[:,ll:rr,1] = color[(colorIdx+len(color)-1)%len(color)][1] * image[:,ll:rr,1] + image[:,ll:rr,2] = color[(colorIdx+len(color)-1)%len(color)][2] * image[:,ll:rr,2] + cv2.imshow("image", image) +""" + if event == cv2.EVENT_LBUTTONDOWN: + # a new boundary + if ximage.shape[1]/2 and yimage.shape[0]/2: + bl=(x,y) + if 2 in lastDidList: + image=orig.copy() + lastDidList.remove(2) + lastDidList.append(2) + if x>image.shape[1]/2 and y>image.shape[0]/2: + br=(x,y) + if 3 in lastDidList: + image=orig.copy() + lastDidList.remove(3) + lastDidList.append(3) + draw() + + elif event == cv2.EVENT_MBUTTONDOWN: + # a new boundary + if yimage.shape[0]/2: + bm=(x,y) + if 3 in lastDidList: + image=orig.copy() + lastDidList.remove(3) + lastDidList.append(5) + draw() + +def segmenter(imDir,imagePath,dispHeight): + global image,tl,tr,bl,br,tm,bm,lastDidList,orig,abnorm + print 'opening '+imDir+imagePath + orig = cv2.imread(imDir+imagePath) + scale = orig.shape[0]/dispHeight + orig = cv2.resize(orig,(0,0),None,1.0/scale,1.0/scale) + #print 'opened' + assert orig is not None + redo=True + while redo: #undo loop + abnorm=False + lastDidList=[] + tl=(-1,-1) + tr=(-1,-1) + bl=(-1,-1) + br=(-1,-1) + tm=(-1,-1) + bm=(-1,-1) + redo=False + image = orig.copy() + draw() + while True: + # display the imageWork and wait for a keypress + key = cv2.waitKey(33) & 0xFF #so it is robust on all systems + #print key + if key == 13 and tl[0]>=0 and tr[0]>=0 and bl[0]>=0 and br[0]>=0: #enter + toWrite = imagePath+','+str(int(scale*tl[0]))+','+str(int(scale*tl[1]))+','+str(int(scale*tr[0]))+','+str(int(scale*tr[1]))+','+str(int(scale*br[0]))+','+str(int(scale*br[1]))+','+str(int(scale*bl[0]))+','+str(int(scale*bl[1])) + if abnorm: + if tm[0]>=0 and bm[0]>=0: + toWrite += ',ABNORMAL,'+str(int(scale*tm[0]))+','+str(int(scale*tm[1]))+','+str(int(scale*bm[0]))+','+str(int(scale*bm[1])) + else: + toWrite += ',ABNORMAL' + else: + if tm[0]>=0 and bm[0]>=0: + toWrite += ',DOUBLE,'+str(int(scale*tm[0]))+','+str(int(scale*tm[1]))+','+str(int(scale*bm[0]))+','+str(int(scale*bm[1])) + else: + toWrite += ',SINGLE' + toWrite+='\n'; + return toWrite, False, False + elif key == 8: #backspace + if len(lastDidList)>0: + imageWork = orig.copy() + lastDid=lastDidList.pop() + if lastDid==0: + tl=(-1,-1) + elif lastDid==1: + tr=(-1,-1) + elif lastDid==2: + bl=(-1,-1) + elif lastDid==3: + br=(-1,-1) + elif lastDid==4: + tm=(-1,-1) + elif lastDid==5: + bm=(-1,-1) + image=orig.copy() + draw() + else: + return '', True, False + elif key == 127: #del + #if len(lastDidList)>0: + print('[CLEAR]') + redo=True + break + #else: + # return '', True, False + elif key == 27: #esc + print('esc') + return '', False, True + #exit(0) + #break + elif key == 97: #'a' + #return imagePath+',-1,-1,-1,-1,-1,-1,-1,-1,ABNORMAL\n', False, False + abnorm = not abnorm + image=orig.copy() + draw() + + #return newWords, newWordBoxes + +if len(sys.argv)<4: + print 'usage: '+sys.argv[0]+' imgDir imgList outAnn.csv [displayHeight]' + print 'output format: imageFile, tlx, tly, trx, try, brx, bry, blx, bly, type (,tmx, tmy, bmx, bmy)' + exit(0) + +inFile = sys.argv[2] +imDir = sys.argv[1] +if imDir[-1]!='/': + imDir+='/' +outFile = sys.argv[3] +dispHeight=500.0 +if len(sys.argv)>4: + dispHeight=float(sys.argv[4]) + +cv2.namedWindow("image") +cv2.setMouseCallback("image", clicker) + +didCount=0 +did=[] +try: + check = open(outFile,'r') + did = check.read().splitlines() + didCount=len(did) + check.close() + print 'found '+outFile+', appending. Note: this is sychronizing based on count alone, if '+inFile+' hash changed, but sure to align '+outFile +except IOError: + print ('making new out:'+outFile) + +out = open(outFile,'w') + +print ' =============================================== ' +print ' !!! INSTRUCTIONS !!!' +print ' If the page does not contain a single page, or ' +print ' an open book, mark as abnormal with INSERT (e.g.' +print ' two seperate pages).' +print ' Click on the four corners to include all the ' +print ' full pages in the image (including two pages if ' +print ' fully present).' +print ' If two pages a full present also mark page seam ' +print ' (middle-click).' +print ' On placing points, prioritize the following to ' +print ' be included/discluded from the polygons in the ' +print ' following order:' +print ' 1. Including the present page(s) content.' +print ' 2. Discluding other pages and background.' +print ' 3. Discluding the present page(s) boudary.' +print ' 4. Including the present page(s) white area.' +#print ' book). If a corner is torn, click where it ought' +#print ' to be, based on page edges. The page seem on an ' +#print ' open book is the page edge.' +print ' Use ESC to exit or the latest page you finished ' +print ' will be lost.' + +#i=didCount +i=0 +#pageCount=-1 +prevSeg='' +seg='' +showControls() +inF = open(inFile,'r') +images = inF.read().splitlines() +end=False +doneOne=False +while ii: + line = did[i].strip().split(',') + typ = line[8] + #print typ + if typ != '-1': + out.write(did[i].strip()+'\n') + i+=1 + continue + seg, undo, end = segmenter(imDir, images[i],dispHeight) + out.write(seg) + seg='' + i+=1 + else: + + + seg, undo, end = segmenter(imDir, images[i],dispHeight) + if len(seg)>0: + doneOne=True + + if undo and i>0 and doneOne: + prevSeg='' + print(str(i)+' of '+str(len(images))) + prevSeg, undo, end = segmenter(imDir, images[i-1],dispHeight) + else: + out.write(prevSeg) + prevSeg=seg + seg='' + i+=1 +out.write(prevSeg) +out.write(seg)